Friday, April 04, 2014

Java 8, bnd and references to compile-time constants

Java 8 was recently released and I wanted to test it out with the OSGi build. I installed JDK 8 on my Mac, pointed JAVA_HOME at it and started a clean build of OSGi. After the build completed, I ran the Core Compliance Tests to verify the build. Unfortunately several of the tests were now failing.

In order to diagnose the issue, I compared it to the same build built with JDK 7. The JDK 7 build passed all the tests running under JDK 7 or JDK 8. The JDK 8 build failed the same tests running under JDK 7 or JDK 8. So the issue was in the building with JDK 8 not the running under JDK 8. The failure was that one of the test bundles was failing to resolve. This was caused by an import for a  package that was not exported by any bundle. When building with JDK 8, bnd added a package to the test bundle's Import-Package statement that is not present in the Import-Package statement when building with JDK 7.

Digging further into the test bundle, the only reference to that package was to a static final String constant. During compilation, javac must copy the referenced string into the compiled class since the final String is a compile-time constant and thus the referenced field is not referenced at runtime.

So in the example code:
public class Referencer {
    public static void main(String[] args) {
class Constant {
    static final String    hello = "Hello, World!";
the class file for Referencer has its own copy of the string "Hello, World!" and does not access Constant at runtime to obtain the string to print it out. In fact, the Referencer class file compiled by JDK 7 has no references to Constant.

But when compiling with JDK 8, javac adds a constant pool entry for the class holding the referenced constant even though this class it not reference at runtime by the compiled class. So for the example above, the Referencer class file now has a constant pool entry for the Constant class.

During the building of a bundle, bnd analyzes the class files in the bundle to find class references to generate the necessary Import-Package statement.  So the presence of this new constant pool entry for the constant holding class caused bnd to add the package of that class to the Import-Package statement. bnd assumed that all class entries in the constant pool were runtime references.

An email conversation with Alex Buckley, JLS spec lead, confirmed that this new behavior for javac in JDK 8 is intentional. javac is now adding compile-time dependencies to the constant pool to support compile-time dependency analysis using class files.

This means that bnd's assumption that all class entries in the constant pool were runtime references is no longer valid for classes compiled by JDK 8. So Peter Kriens is making fixes to bnd for the 2.3 release to do deeper analysis so only runtime references to classes will result in their packages being added to the Import-Package statement. Compile-time only dependencies wont result in their packages being added to the Import-Package statement. So stay tuned for bnd 2.3 if you plan on using JDK 8 to build your bundles.

This also means that anyone doing bytecode analysis for runtime dependencies of class files needs to be aware that the constant pool can now also contain compile-time only dependencies.


Jesper S Møller said...

Note that the Eclipse Java 8 compiler does not produce this constant, so if you need Java 8 support together with bnd, you always have that option.

BJ Hargrave said...

Just discovered that Classycle also suffers from this problem as it also assumes all constant pool entries are for runtime references.

Unknown said...

I swear, some of the things that get added to Java intentionally truly baffle me. But what can you do? Just keep on and roll with the punches.

- Fred