JDK-8280772 : newInstanceFromSystemProperty() loads class in different ClassLoader with javac
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 12,17,18
  • Priority: P3
  • Status: Resolved
  • Resolution: Not an Issue
  • OS: generic
  • CPU: generic
  • Submitted: 2022-01-25
  • Updated: 2022-11-08
  • Resolved: 2022-11-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 20
20Resolved
Description
A DESCRIPTION OF THE PROBLEM :
When we launch a Java class with "java FunBug.java", the Java class is loaded in the com.sun.tools.javac.launcher.Main$MemoryClassLoader. Furthermore, if we load the same class using reflectively using newInstanceFromSystemProperty() inside the ForkJoinPool, for example when we specify a threadFactory, then it will use the same class that we have loaded in the MemoryClassLoader and create an
instance to use as a thread factory. However, if we compile the class, and then run the Java class with "java FunBug.java", then the Java class is loaded in the MemoryClassLoader as before, but the class for the thread factory is loaded in the AppClassLoader. This means that we now have multiple class instances and
any static variables would be defined twice. It works fine if we run the Java class with "java FunBug" (without the .java extension) or if we do not compile it and run it with "java FunBug.java" (with the .java extension).

In Java 11, this condition caused the following error:

java -Djava.util.concurrent.ForkJoinPool.common.threadFactory=FunBug FunBug.java 
Created FunBug class in jdk.internal.loader.ClassLoaders$AppClassLoader@77556fd
        of jdk.internal.loader.ClassLoaders$PlatformClassLoader@3270d194
error: class found on application class path: FunBug

Since Java 12, it would instead load the class twice, giving mysterious bugs

REGRESSION : Last worked in version 11.0.14

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Compile the FunBug.java file with javac.
2. Run the code with java -Djava.util.concurrent.ForkJoinPool.common.threadFactory=FunBug FunBug.java


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Either:

Created FunBug class in com.sun.tools.javac.launcher.Main$MemoryClassLoader@35e2d654
        of jdk.internal.loader.ClassLoaders$AppClassLoader@77556fd
        of jdk.internal.loader.ClassLoaders$PlatformClassLoader@14cd1699

or 

Created FunBug class in jdk.internal.loader.ClassLoaders$AppClassLoader@5c29bfd
        of jdk.internal.loader.ClassLoaders$PlatformClassLoader@3a4afd8d
ACTUAL -
Created FunBug class in com.sun.tools.javac.launcher.Main$MemoryClassLoader@6b0c2d26
        of jdk.internal.loader.ClassLoaders$AppClassLoader@75b84c92
        of jdk.internal.loader.ClassLoaders$PlatformClassLoader@646be2c3
Created FunBug class in jdk.internal.loader.ClassLoaders$AppClassLoader@75b84c92
        of jdk.internal.loader.ClassLoaders$PlatformClassLoader@646be2c3
Exception in thread "main" java.lang.NullPointerException
        at FunBug.newThread(FunBug.java:21)
        at java.base/java.util.concurrent.ForkJoinPool.createWorker(ForkJoinPool.java:1328)
        at java.base/java.util.concurrent.ForkJoinPool.tryAddWorker(ForkJoinPool.java:1352)
        at java.base/java.util.concurrent.ForkJoinPool.signalWork(ForkJoinPool.java:1476)
        at java.base/java.util.concurrent.ForkJoinPool.externalPush(ForkJoinPool.java:1903)
        at java.base/java.util.concurrent.ForkJoinTask.fork(ForkJoinTask.java:704)
        at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:306)
        at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
        at java.base/java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:408)
        at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:736)
        at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:159)
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateParallel(ForEachOps.java:188)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
        at java.base/java.util.stream.IntPipeline.forEach(IntPipeline.java:439)
        at java.base/java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:596)
        at FunBug.main(FunBug.java:28)


---------- BEGIN SOURCE ----------
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.IntStream;

public class FunBug implements ForkJoinPool.ForkJoinWorkerThreadFactory {
    static {
        System.out.println("Created FunBug class in " +
                FunBug.class.getClassLoader());
        ClassLoader parent = FunBug.class.getClassLoader().getParent();
        while (parent != null) {
            System.out.println("\tof " + parent);
            parent = parent.getParent();
        }
    }

    private static ForkJoinPool.ForkJoinWorkerThreadFactory defaultFactory;

    @Override
    public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
        return defaultFactory.newThread(pool);
    }

    public static void main(String... args) {
        defaultFactory = ForkJoinPool.defaultForkJoinWorkerThreadFactory;
        IntStream.range(0, Runtime.getRuntime().availableProcessors() * 8)
                .parallel()
                .forEach(i -> LockSupport.parkNanos(100_000_000));
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Don't run the javac launcher with compiled classes. Use either "java FunBug" with compiled classes or "java FunBug.java" without compiled classes

FREQUENCY : always



Comments
ForkJoinPool uses ClassLoader::getSystemClassLoader to load class specified in java.util.concurrent.ForkJoinPool.common.threadFactory property. From the perspective of JEP 330 this class is not accessible in source mode, so described case: "It works fine if we do not compile it and run it with "java FunBug.java" (with the .java extension)." is not true. FunBug.java cannot be declared as java.util.concurrent.ForkJoinPool.common.threadFactory as it is not reachable by ClassLoader::getSystemClassLoader. FunBug.class can be declared as java.util.concurrent.ForkJoinPool.common.threadFactory if present on class path, however it cannot access FunBug.java launched in source mode according to JEP 330 and it cannot assume its main method is called.
08-11-2022

This case fits exactly into the JEP 330 case describing classes appearing on the application class path trying to access classes declared in the source file: "The compiled classes are loaded by a custom class loader, that delegates to the application class loader. (This implies that classes appearing on the application class path cannot refer to any classes declared in the source file.)"
07-11-2022

the cause is related to the decision made in JDK-8210839
07-11-2022

Assigning to Jon for now as this seems to be the source code launcher and CL delegation when the same class is visible to both the memory CL and the application class loader.
27-01-2022

This has nothing to do with hotspot. Re-assigning to the launcher as it is an issue related to "source mode".
27-01-2022

Issue is reproduced. OS: Windows 10 JDK 11.0.4: Pass Output: Created FunBug class in jdk.internal.loader.ClassLoaders$AppClassLoader@707f7052 of jdk.internal.loader.ClassLoaders$PlatformClassLoader@65fb9ffc error: class found on application class path: FunBug JDK 12ea11: Pass JDK 12ea 13: Fail Output: Created FunBug class in com.sun.tools.javac.launcher.Main$MemoryClassLoader@1283bb96 of jdk.internal.loader.ClassLoaders$AppClassLoader@21588809 of jdk.internal.loader.ClassLoaders$PlatformClassLoader@7b98f307 Created FunBug class in jdk.internal.loader.ClassLoaders$AppClassLoader@21588809 of jdk.internal.loader.ClassLoaders$PlatformClassLoader@7b98f307 Exception in thread "main" java.lang.NullPointerException at FunBug.newThread(FunBug.java:21) at java.base/java.util.concurrent.ForkJoinPool.createWorker(ForkJoinPool.java:1328) at java.base/java.util.concurrent.ForkJoinPool.tryAddWorker(ForkJoinPool.java:1352) at java.base/java.util.concurrent.ForkJoinPool.signalWork(ForkJoinPool.java:1476) at java.base/java.util.concurrent.ForkJoinPool.externalPush(ForkJoinPool.java:1903) at java.base/java.util.concurrent.ForkJoinTask.fork(ForkJoinTask.java:704) at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:306) at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746) at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290) at java.base/java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:408) at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:736) at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:159) at java.base/java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateParallel(ForEachOps.java:188) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233) at java.base/java.util.stream.IntPipeline.forEach(IntPipeline.java:439) at java.base/java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:596) at FunBug.main(FunBug.java:28) JDK 17.0.2 :Fail JDK 18ea31: Fail Moving it to dev team for further analysis.
27-01-2022