4

I'm looking for code examples where the result depends on things like:

  • Java Version
  • VM
  • Java options / JVM options

On example would be the following code:

Integer foo = 400;
Integer bar = 400;
System.out.println(foo == bar)

Looking at that code, lot of people will guess, it's false, but it depends on the value of IntegerCache.high, which can be changed with -Djava.lang.Integer.IntegerCache.high=1000

I guess, there are many examples where you change the behaviour with memory limits, but are there more unexpected examples?

Thomas M.
  • 1,496
  • 1
  • 10
  • 21

1 Answers1

3

Java version influence:

1) Take a look at reader like this:

public class Reader {
   private final String input;
   private int currentPosition;
   private Pattern numberPattern = Pattern.compile(...);

   public String readNextInt() {
       return numberPattern.matcher(input.substring(currentPosition)).group();
   }
}

In Java versions older than seven this code works fine because String.substring() just shares its internal buffer and stores two pointers to buffer start/end. But in Java 7 this behavior was changed and now every substring() call creates a new String object with its own buffer, which affects performance of this code dramatically, especially in the case of larger strings.

2) When Java 1.2 was released, all intermediate floating point operations were represented only as single or double precision, which caused multiple rounding errors. After 1.2 JVM could represent intermediate result as 80-bit extended double, which which greatly improves precision. Thus you can observe different floating point results on different processors and different versions of java.

For backward compatibility or for compatibility between different architectures there is strictfp modifier.

3) In Java 8 all reflection-related code was highly optimized, so switching to a newer version will noticeably improve the performance of any reflection-dependent code. Take a look at some benchmarks here.

4) Java 5 and JSR-133. JSR-133 was a brand new memory model devoid of the previous one's flaws. Final and volatile field semantics were changed. Moreover, many aspects of concurrent programming in Java were changed as well, so you can expect completely different behavior of your multithreaded program before and after Java 5. You can read more about JSR-133 here.

5) Mergesort implementation was changed in Java 7. It can cause old code to throw an IllegalArgumentException. Read more here.

6) Every new java release introduces some performance improvements or bug fixes, so usually you can see some minor improvements (or even not, e.g. heavily enhanced escape analysis in Java 8 to avoid allocation of lambdas on heap), but in rare cases slowdowns are possible too.

VM: I don't know much about VM other than hotspot, but you can expect some JIT improvements/slowdowns, different memory consumption and GC stalls (like Azul Zulu with its pauseless c4 collector and high memory requirements) or even static ahead-of-time compilation, like in Excelsior JET. But usually you know exactly what you are doing when switching between JVM implementations.

Java options / JVM options:

1) Biased locking. Flags like -XX:+UseBiasedLocking and -XX:BiasedLockingStartupDelay=n can change performance of your application depending on your concurrency profile, so playing with those can both enhance and impair your application. E.g. in Azul Zulu engineers just gave biased locking up due to inevitable STW when you are trying to revoke biased lock (yes, for lock revocation enough to stop only two threads, but there is only global STW in hotspot by design).

2) XX:+AlwaysPreTouch flag touch and zero all memory pages at JVM startup to avoid penalty in runtime and prevent linux from stealing your pages.

4) -XX:+BindGCTaskThreadsToCPU is not implemented in JDK8 now, but maybe later it will affect your gc performance :)

5) -XX:+UseNUMA can improve your performance on NUMA architectures, JVM will try to partition heap for NUMA nodes. This article states that "About 40 percent increase in performance with NUMA-aware allocator".

6) -XX:+UseCondCardMark can help you in highly-concurrent application to prevent false sharing that you aren't expecting much. In java heap is separated on 512 bytes regions ("cards") and every write in your program causes a volatile write to corresponding card (address of outer object shifted by 9). So in average 64 cards are sharing the same L1 cache line. This means that writes within objects in one 32kByte (= 512 * 64) region causes writes to same cache line, which can slow down your app performance. -XX:+UseCondCardMark replaces those writes to card table with if (card[address >> 9] == 0) card[address >> 9] = 1, which can decrease amount of volatile writes in every contended 32kB region from hundreds to one.

7) There are many flags which can impact GC performance, but I'm sure you can dig out all about it yourself, because it's not interesting to write all advices for gc tuning here.

Community
  • 1
  • 1
qwwdfsad
  • 3,077
  • 17
  • 25