Premise: I have had 2 classes loaded by different class loaders - one from the app classloader and the other, a custom class loader. The class loaded by the custom class loader is trying to reflectively access a private field in the class loaded via the appclass loader. In this case, I get a runtime error along the lines of "Failed to access class A from class B; A is in unnamed module of loader 'app' and B is in unnamed module of loader 'Custom'. The error goes away after adding an '--add-opens java.base/java.lang=ALL-UNNAMED'
Questions : I understand that there can be up to one unnamed module per classloader in the VM.
What part of the JLS or JDK implementation specific to hotspot VM talks about interactions between two unnamed modules across loaders ? I can see a lot of unnamed to named module interactions etc in the document, but not between two unnamed.
I understand why add-opens is solving the issue in general( the code is being invoked reflectively and end calls into JDK happen via java.lang.reflect APIs?). However, unclear again as to how the add-opens works across loaders - does
--add-opens=ALL-UNNAMEDexpected to open the packages in the specified module to all unnamed modules across loaders in the VM or only to that loader?
Using Java 17 + hotspot VM.
After a lot of trial and error, I finally got a reproducer - but I had to make quite a few assumptions.
Reproducer:
Assumptions:
Ais NOTpublicThis is supported by the error message - because nowAandBare in different runtime packages,Bcan't access a non-public class in the other runtime package. The error message matches.BreferencesAdirectly. Reflective access (Class.forName) would still succeed.ClassLoaderyou (or the library you use) first tries to useClassLoader.defineClass. This would explain why adding--add-opens java.base/java.lang=ALL-UNNAMEDwould exhibit different behavior. (Not an uncommon thingRunning this code yields:
B should get S3CR3T parameter: Got S3CR3T classForName: Got S3CR3T direct: Got exception when trying to access private field: java.lang.IllegalAccessError: failed to access class A from class B (A is in unnamed module of loader 'app'; B is in unnamed module of loader 'Custom' @3b07d329) at Custom//B.getAClass(A.java:82) at Custom//B.accessField(A.java:70) at Custom//B.doIt(A.java:65) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at A.main(A.java:17)while running it with
--add-opens java.base/java.lang=ALL-UNNAMEDyieldsI already hinted in my assumptions about the cause for this:
AandBare in different runtime packages, andAis not public - which meansAis not accessible toB.This has nothing to do with modules.
To answer your other questions:
(Emphasis mine)
No,
java.langandjava.lang.reflectare two different packages. They have nothing to do with each other - except that they are in the same module, part of the platform...It is more likely that you or a library that you use first tries to hack into
java.lang.ClassLoader.defineClass- which resides in thejava.langpackage - and falls back to creating their ownClassLoader.Good question.
ALL-UNNAMEDimplies ALL unnamed modules.We can test this with this code (and reusing the
CustomClassLoaderfrom the reproducer:When we run this code with
--add-opens java.base/java.lang=ALL-UNNAMEDthis yields:So the answer is: Yes,
ALL-UNNAMEDopens the package to all unnamed modules, for everyClassLoader.