Hidden classes

Java 15 feature

Posted by Vipin Sharma on Sunday, September 13, 2020 Tags: java JDK15   6 minute read

The initial draft, work in progress.

What are Hidden classes in Java

Classes that cannot be used directly by the bytecode of other classes, are hidden classes.

Hidden classes cannot be symbolically referenced by other classes and hence a hidden class

  1. cannot be named as a supertype
  2. cannot be a declaring field type,
  3. cannot be the parameter type or the return type.
  4. cannot be found by class loader via Class::forName, ClassLoader::loadClass, Lookup::findClass etc.

Hidden classes allow frameworks/jvm languages to define classes as non-discoverable implementation details, so that they cannot be linked against by other classes.


Why do we need this new language feature

Framework/Language implementors usually intend for a dynamically generated class to be logically part of the implementation of a statically generated class. For this intent following are properties that are desirable for dynamically generated classes:

  1. Non-discoverability. Dynamic generated classes should not be discoverable by other classes in JVM. (e.g. using Class::forName and ClassLoader::loadClass)

  2. Access control. It may be desirable to extend the access control context of the statically generated class to include the dynamically generated class.

  3. Lifecycle. Dynamically generated classes may only be needed for a limited time, so retaining them for the lifetime of the statically generated class might unnecessarily increase memory footprint. Existing workarounds for this situation, such as per-class class loaders, are cumbersome and inefficient.

Existing standard APIs ClassLoader::defineClass and Lookup::defineClass, always define a visible/discoverable class, and in this way classes have longer lifecycle than desired. Hidden classes all of these 3 features desired by Framework/Language implementors.

Many language implementations built on the JVM rely upon dynamic class generation for flexibility and efficiency. Before JDK15 dynamic classes were generated using non-standard API sun.misc.Unsafe::defineAnonymousClass. Unsafe APIs are not recommended.

Now we have standard API Lookup::defineHiddenClass to create Hidden classes. sun.misc.Unsafe::defineAnonymousClass is deprecated since Java 15.

Best example for use of Hidden classes is lambda expressions in Java language. JDK developers don’t want to expose classes generated by lambda expression so javac is not translating lambda expression into dedicated class, it generates bytecode that dynamically generates and instantiates a class to yield an object corresponding to the lambda expression when needed.

Before Java 15 for Lambda expressions sun.misc.Unsafe::defineAnonymousClass was used in JDK. Since Java 15 lambda expression are using Hidden classes.


How to write Hidden classes

Hidden class is created by invoking Lookup::defineHiddenClass. This causes the JVM to derive a hidden class from the supplied bytes, link the hidden class and return a lookup object that provides reflective access to the hidden class. The invoking program should store the lookup object carefully, as it is the only way to obtain the Class object of the hidden class.

public class HiddenClassDemo {
    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        ClassWriter cw =
                GenerateClass.getClassWriter(HiddenClassDemo.class);
        byte[] bytes = cw.toByteArray();

        Class<?> c = lookup.defineHiddenClass(bytes, true, NESTMATE).lookupClass();
        Constructor<?> constructor = c.getConstructor(null);
        Object object = constructor.newInstance(null);
        Test test = (Test) object;
        test.test(new String[]{""});
        System.out.println("End of main method in class " + HiddenClassDemo.class.getName());
    }
}
public interface Test {
    void test(String[] args);
}
public static ClassWriter getClassWriter(Class<HiddenClassDemo> ownerClassName) {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);

        cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, getHiddenClassName(ownerClassName),
                null, "java/lang/Object", new String[] {"com/vip/jfeatures/jdk15/hiddenclass/Test"});
        ...
        ...
        ...
            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "test",
                                "([Ljava/lang/String;)V", null, null);
            mv.visitCode();
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("Hello test");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(2, 1);
            mv.visitEnd();        

    }


At the end

Knowing language features like this helps you get the best java jobs, that’s why to help you I wrote ebook 5 steps to Best Java Jobs. Download this step by step guide for free!

Resources

  1. https://openjdk.java.net/jeps/371, This is Java enhancement proposal for Hidden classes in JDK15.
  2. https://github.com/Vipin-Sharma/JDK15Examples, this is link to code examples used in this post.
  3. https://mail.openjdk.java.net/pipermail/jdk-dev/2020-April/004183.html
  4. https://mail.openjdk.java.net/pipermail/valhalla-dev/2019-December/006630.html
  5. https://mail.openjdk.java.net/pipermail/serviceability-dev/2020-March/030835.html