Java Hidden Classes: Enhancing Runtime Class Management
Explore Java 15's innovative hidden classes designed for frameworks that generate runtime classes. Understand their unique access control, benefits for dynamic class generation, and how they can be unloaded independently, enhancing application performance and security.
Java - Hidden Classes
Java 15 has introduced hidden classes that cannot be used directly by other classes' bytecode. These hidden classes are intended for use by frameworks that generate classes at runtime and utilize them through reflection.
A hidden class is defined as a member of the Based Access Control Context and can be unloaded independently of other classes.
This proposal, JEP 371, aims to enhance all languages on the JVM by providing a standard API for defining hidden classes that are not discoverable and have a limited lifecycle. Both JDK frameworks and external frameworks can dynamically generate hidden classes.
JVM languages rely heavily on dynamic class generation for flexibility and efficiency.
Goals
- Frameworks should define classes as non-discoverable implementation details, meaning these classes cannot be linked to other classes or discovered using reflection.
- Extend Access Control Nest with non-discoverable classes.
- Aggressive unloading of hidden classes will allow frameworks to define numerous hidden classes without degrading performance.
- Deprecate the non-standard API,
misc.Unsafe::defineAnonymousClass
, which will be removed in future releases.
Creating a Hidden Class
To create a hidden class, we first need to create a Lookup
instance as shown below:
Code Snippet
MethodHandles.Lookup lookup = MethodHandles.lookup();
Once the lookup instance is available, we can use the defineHiddenClass()
method to create a hidden class using a byte array:
Code Snippet
Class> hiddenClass = lookup.defineHiddenClass(getByteArray(), true, ClassOption.NESTMATE).lookupClass();
The byte array of the hidden class can be retrieved using the classpath of the hidden class:
Code Snippet
public static byte[] getByteArray() throws IOException {
InputStream stream = Util.class.getClassLoader().getResourceAsStream("com/tutorialsarena/Util.class");
byte[] bytes = stream.readAllBytes();
return bytes;
}
Once the hiddenClass
is loaded, we can create its instance using the getConstructor()
method:
Code Snippet
Object hiddenClassObj = hiddenClass.getConstructor().newInstance();
Using the hidden class instance, we can get the method and execute it as shown below:
Code Snippet
Method method = hiddenClassObj.getClass().getDeclaredMethod("square", Integer.class);
Object result = method.invoke(hiddenClassObj, 3);
As the hidden class is hidden and cannot be instantiated using reflection, its hidden property is true
and the canonical name is null
.
Example to Create and Use a Hidden Class
The following example shows the creation and use of a hidden class. First, we've created a public class Util
as shown below:
Code Snippet
package com.tutorialsarena;
public class Util {
public Integer square(Integer n) {
return n * n;
}
}
Now, we've created this class as a hidden class and accessed its method to get the square of a number:
Code Snippet
package com.tutorialsarena;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup.ClassOption;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Tester {
public static void main(String args[]) throws IllegalAccessException, IOException, InstantiationException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
// create the lookup object
MethodHandles.Lookup lookup = MethodHandles.lookup();
// define the hidden class using the byte array of Util class
// Using NESTMATE option so that hidden class has access to
// private members of classes in the same nest
Class> hiddenClass = lookup.defineHiddenClass(getByteArray(), true, ClassOption.NESTMATE).lookupClass();
// get the hidden class object
Object hiddenClassObj = hiddenClass.getConstructor().newInstance();
// get the hidden class method
Method method = hiddenClassObj.getClass().getDeclaredMethod("square", Integer.class);
// call the method and get result
Object result = method.invoke(hiddenClassObj, 3);
// print the result
System.out.println(result);
// as hidden class is not visible to jvm, it will print hidden
System.out.println(hiddenClass.isHidden());
// canonical name is null thus this class cannot be instantiated using reflection
System.out.println(hiddenClass.getCanonicalName());
}
public static byte[] getByteArray() throws IOException {
InputStream stream = Util.class.getClassLoader().getResourceAsStream("com/tutorialsarena/Util.class");
byte[] bytes = stream.readAllBytes();
return bytes;
}
}
Output
Output
9
true
null