I just tried to run my server with Java 9 and got next warning:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective acce
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Main {
@SuppressWarnings("unchecked")
public static void disableAccessWarnings() {
try {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Object unsafe = field.get(null);
Method putObjectVolatile = unsafeClass.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class);
Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);
Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
Field loggerField = loggerClass.getDeclaredField("logger");
Long offset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
putObjectVolatile.invoke(unsafe, loggerClass, offset, null);
} catch (Exception ignored) {
}
}
public static void main(String[] args) {
disableAccessWarnings();
}
}
It works for me in JAVA 11.
There is another option that does not come with any need for stream suppression and that does not rely on undocumented or unsupported APIs. Using a Java agent, it is possible to redefine modules to export/open the required packages. The code for this would look something like this:
void exportAndOpen(Instrumentation instrumentation) {
Set<Module> unnamed =
Collections.singleton(ClassLoader.getSystemClassLoader().getUnnamedModule());
ModuleLayer.boot().modules().forEach(module -> instrumentation.redefineModule(
module,
unnamed,
module.getPackages().stream().collect(Collectors.toMap(
Function.identity(),
pkg -> unnamed
)),
module.getPackages().stream().collect(Collectors.toMap(
Function.identity(),
pkg -> unnamed
)),
Collections.emptySet(),
Collections.emptyMap()
));
}
You can now run any illegal access without the warning as your application is contained in the unnamed module as for example:
Method method = ClassLoader.class.getDeclaredMethod("defineClass",
byte[].class, int.class, int.class);
method.setAccessible(true);
In order to get hold of the Instrumentation
instance, you can either write a Java agent what is quite simple and specify it on the command line (rather than the classpath) using -javaagent:myjar.jar
. The agent would only contain an premain
method as follows:
public class MyAgent {
public static void main(String arg, Instrumentation inst) {
exportAndOpen(inst);
}
}
Alternatively, you can attach dynamically using the attach API which is made accessible conveniently by the byte-buddy-agent project (which I authored):
exportAndOpen(ByteBuddyAgent.install());
which you would need to call prior to the illegal access. Note that this is only available on JDKs and on Linux VM whereas you would need to supply the Byte Buddy agent on the command line as a Java agent if you needed it on other VMs. This can be convenient when you want the self-attachment on test and development machines where JDKs are typically installed.
As others pointed out, this should only serve as an intermediate solution but I fully understand that the current behavior often breaks logging crawlers and console apps which is why I have used this myself in production environments as a short-term solution to using Java 9 and so long I did not encounter any problems.
The good thing, however, is that this solution is robust towards future updates as any operation, even the dynamic attachment is legal. Using a helper process, Byte Buddy even works around the normally forbidden self-attachment.
In case anybody would like to redirect the log messages instead of discarding them, this works for me in Java 11. It replaces the stream the illegal access logger writes to.
public class AccessWarnings {
public static void redirectToStdOut() {
try {
// get Unsafe
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Object unsafe = field.get(null);
// get Unsafe's methods
Method getObjectVolatile = unsafeClass.getDeclaredMethod("getObjectVolatile", Object.class, long.class);
Method putObject = unsafeClass.getDeclaredMethod("putObject", Object.class, long.class, Object.class);
Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);
Method objectFieldOffset = unsafeClass.getDeclaredMethod("objectFieldOffset", Field.class);
// get information about the global logger instance and warningStream fields
Class<?> loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
Field loggerField = loggerClass.getDeclaredField("logger");
Field warningStreamField = loggerClass.getDeclaredField("warningStream");
Long loggerOffset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
Long warningStreamOffset = (Long) objectFieldOffset.invoke(unsafe, warningStreamField);
// get the global logger instance
Object theLogger = getObjectVolatile.invoke(unsafe, loggerClass, loggerOffset);
// replace the warningStream with System.out
putObject.invoke(unsafe, theLogger, warningStreamOffset, System.out);
} catch (Throwable ignored) {
}
}
}