Skip to main content

Understanding subtle new behaviours of JDK 7

Posted by fabriziogiudici on May 7, 2012 at 1:13 PM PDT

As I said in my previous post, a few months ago I've put under my CI a number of projects of mine to be tested in parallel with JDK 6 and JDK 7. After a few minor issues, they were ok and have been working even in production under (Open)JDK 7 since the end of the past year. Given that now Oracle has started releasing JDK 7 for Mac OS X too and that the latest NetBeans 7.1.2 works fine under Mac OS X and JDK 7, I'm going to start dropping support for JDK 6 and gradually move all my projects to JDK 7. This means to set -source 1.7 and -target 1.7, so I'll be able to start using the new Java 7 features.

But yesterday night the new -source and -target options gave me a strange result: all of a sudden, some tests failed with funny messages such as:

Failed tests:   setUp(it.tidalwave.northernwind.core.impl.filter.NodeLinkMacroFilterTest):
Expecting a stackmap frame at branch target 94 in method
it.tidalwave.northernwind.core.impl.filter.MacroFilter.filter(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
at offset 22   setupFixture(it.tidalwave.northernwind.core.impl.filter.XsltMacroFilterTest):
Expecting a stackmap frame at branch target 21 in method it.tidalwave.northernwind.core.impl.filter.XsltMacroFilter.filter(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
at offset 6   setupFixture(it.tidalwave.northernwind.core.impl.model.DefaultSiteProviderTest):
Expecting a stackmap frame at branch target 14 in method it.tidalwave.northernwind.core.impl.model.DefaultSite.r(Ljava/lang/String;)Ljava/lang/String;
at offset 6
...

Turns out they are error from the bytecode verifier. Now, before starting the migration to JDK 7 you should read the document prepared by Oracle: at section 4.1.1 there's something about the new bytecode format and bytecode verifier. Trying to keep the things simple, the new bytecode verifier performs stricter checks and relies upon changed data format in the bytecode. The VM in JDK 7 uses the new verifier for code compiled in Java 7 mode, but falls back to the old one for code compiled in Java 6 mode. Thus, in theory there should be no problem.

Problems actually arise if you're using bytecode manipulating tools, such as AspectJ in static weaving mode - I do - that haven't been updated yet. They basically read the bytecode, tagged as Java 7 bytecode, and perform changes in Java 6 mode, saving the results still tagged in Java 7 mode. Thus, the VM in JDK 7 sees Java 7 bytecode and activates the new Java 7 verifier, which fails (or can fail) when it meet the bytecode manipulated in Java 6 mode.

It seems complicated, but the simple solution is to force the use of the old verifier in JDK 7 by adding this VM runtime option: -XX:-UseSplitVerifier.

When AspectJ is updated (AspectJ 1.7.0.M1 is the first release targeted at JDK 7, but it's still a milestone so it's probably not a good idea to try it in production) everything will be fine again and there will be no more need for the -XX option.

 

Related Topics >>

Comments

The proper document to read before starting the migration to ...

The proper document to read before starting the migration to JDK 7 is "Java SE 7 and JDK 7 Compatibility" at http://www.oracle.com/technetwork/java/javase/compatibility-417013.html.

The very first item is about class file verification. I would add that -XX:-UseSplitVerifier is rather dangerous because the old verifier doesn't understand the invokedynamic instruction which may appear in Java 7 bytecode (i.e. in 51.0 classfiles).

Thanks. I've read and was referring to the right document, ...

Thanks. I've read and was referring to the right document, but I copypasted the wrong URL.

I know that there *could* be problems. But without that switch there *are* problems almost for sure if you use the current, non milestone AspectJ. Furthermore, AFAIK invokedynamic is only for non statically-typed languages compiled on the JVM. Theoretically I could be using some jar of an external library that wasn't done in Java and I'm not aware of it. In any case, fully testing the application, as usual, is the correct way to go. If you fully test it with UseSplitVeirier and it's ok, well, it's ok.