Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Native Image] -H:+AllowJRTFileSystem reads from GraalVM instead of the provided JAVA_HOME #10013

Open
2 tasks done
cushon opened this issue Nov 1, 2024 · 5 comments
Open
2 tasks done
Assignees

Comments

@cushon
Copy link

cushon commented Nov 1, 2024

Describe the Issue

Using -H:+AllowJRTFileSystem creates a native image that reads from the Graal JDK's module files, instead of reading from the provided JAVA_HOME.

Using the latest version of GraalVM can resolve many issues.

GraalVM Version

java version "23.0.1" 2024-10-15
Java(TM) SE Runtime Environment Oracle GraalVM 23.0.1+11.1 (build 23.0.1+11-jvmci-b01)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 23.0.1+11.1 (build 23.0.1+11-jvmci-b01, mixed mode, sharing)

Operating System and Version

x86_64 GNU/Linux

Diagnostic Flag Confirmation

  • I tried the -H:ThrowMissingRegistrationErrors= flag.

Run Command

./graalvm-jdk-23.0.1+11.1/bin/native-image -H:ThrowMissingRegistrationErrors= --verbose --no-fallback -H:+AllowJRTFileSystem -jar j.jar

Expected Behavior

I expected the native image to use the jrt filesystem to read from the provided JAVA_HOME

Actual Behavior

The native image reads from the JAVA_HOME of the GraalVM used at image built time, and fails if the GraalVM is removed.

Steps to Reproduce

Use this test program:

import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

public class JRTFS {
  public static void main(String[] args) throws IOException {
    String javaHome = args[0];
    FileSystem fileSystem =
        FileSystems.newFileSystem(URI.create("jrt:/"), Map.of("java.home", javaHome));
    Path path = fileSystem.getPath("/modules/java.base/module-info.class");
    System.err.printf("%s %s\n", path, Files.size(path));
  }
}

Running on OpenJDK shows the program loading a file using the jrt filesystem:

$ javac JRTFS.java
$ jar cvfe j.jar JRTFS JRTFS.class
$ $JAVA11_HOME/bin/java -fullversion
openjdk full version "11.0.16+8"
$ java -jar j.jar $JAVA11_HOME
/modules/java.base/module-info.class 11056

Building with native-image:

$ wget https://download.oracle.com/graalvm/23/latest/graalvm-jdk-23_linux-x64_bin.tar.gz
$ tar xzvf graalvm-jdk-23_linux-x64_bin.tar.gz
$ ./graalvm-jdk-23.0.1+11.1/bin/native-image -H:ThrowMissingRegistrationErrors= --verbose --no-fallback -H:+AllowJRTFileSystem -jar j.jar
$ ./j $JAVA11_HOME
/modules/java.base/module-info.class 12508

If the GraalVM install is removed, the application fails, it appears to contain references to the GraalVM runtime that was used to create the native image.

$ rm -rf graalvm-jdk-23.0.1+11.1/
$ ./j $JAVA11_HOME
Exception in thread "main" java.nio.file.NoSuchFileException: /tmp/tmp.IWs9yCuUBd/graalvm-jdk-23.0.1+11.1/lib/modules
        at [email protected]/sun.nio.fs.UnixFileSystemProvider.newFileChannel(UnixFileSystemProvider.java:225)
        at [email protected]/java.nio.channels.FileChannel.open(FileChannel.java:309)
        at [email protected]/java.nio.channels.FileChannel.open(FileChannel.java:369)
        at [email protected]/jdk.internal.jimage.BasicImageReader.<init>(BasicImageReader.java:106)
        at [email protected]/jdk.internal.jimage.ImageReader$SharedImageReader.<init>(ImageReader.java:229)
        at [email protected]/jdk.internal.jimage.ImageReader$SharedImageReader.open(ImageReader.java:243)
        at [email protected]/jdk.internal.jimage.ImageReader.open(ImageReader.java:67)
        at [email protected]/jdk.internal.jimage.ImageReader.open(ImageReader.java:71)
        at [email protected]/jdk.internal.jrtfs.SystemImage.open(SystemImage.java:60)
        at [email protected]/jdk.internal.jrtfs.JrtFileSystem.<init>(JrtFileSystem.java:90)
        at [email protected]/jdk.internal.jrtfs.JrtFileSystemProvider.newFileSystem(JrtFileSystemProvider.java:109)
        at [email protected]/jdk.internal.jrtfs.JrtFileSystemProvider.newFileSystem(JrtFileSystemProvider.java:128)
        at [email protected]/jdk.internal.jrtfs.JrtFileSystemProvider.newFileSystem(JrtFileSystemProvider.java:107)
        at [email protected]/java.nio.file.FileSystems.newFileSystem(FileSystems.java:339)
        at [email protected]/java.nio.file.FileSystems.newFileSystem(FileSystems.java:288)
        at JRTFS.main(JRTFS.java:13)
        at [email protected]/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)

I expected the native image to use the jrt filesystem to read from the provided java.home, instead of reading from the GraalVM that was used to create the native image.

Additional Context

No response

Run-Time Log Output and Error Messages

No response

@cushon cushon changed the title [Native Image] -H:+AllowJRTFileSystem reads from the Graal JDK instead of the provided JAVA_HOME [Native Image] -H:+AllowJRTFileSystem reads from GraalVM instead of the provided JAVA_HOME Nov 1, 2024
@mukel
Copy link
Member

mukel commented Nov 2, 2024

It seems that the java.home property was somehow burned into the image at build time.
JRT uses java.home system property and not the JAVA_HOME env. variable as the FS root.
Can you try passing -Djava.home=/path/to/java/home at runtime and report back?

@mukel mukel self-assigned this Nov 2, 2024
@mukel
Copy link
Member

mukel commented Nov 2, 2024

To give you more context, here's my workaround for one of the Espresso demos:
https://github.com/graalvm/graalvm-demos/blob/9bb3aadb968b65554a513c521b39c89c093110d2/espresso-jshell/src/main/java/com/oracle/truffle/espresso/jshell/JavaShellLauncher.java#L55-L75
Note that I set the java.home property myself before using the JRT FS, you can do the same e.g. java.home to $JAVA_HOME early.

@cushon
Copy link
Author

cushon commented Nov 2, 2024

Thanks for taking a look, I tried adapting the repro to set the system properties on the command line and at runtime, and was still seeing the same issue.

Also note that the jrt filesystem supports setting the Java home as part of the environment passed to newFileSystem, I would have expected that call to use the Java home passed to newFileSystem and not consult the system properties: FileSystems.newFileSystem(URI.create("jrt:/"), Map.of("java.home", javaHome))


import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

public class JRTFS {
  public static void main(String[] args) throws IOException {
    String javaHome = args[0];
    System.setProperty("java.home", javaHome);
    System.setProperty("org.graalvm.home", javaHome);
    FileSystem fileSystem =
        FileSystems.newFileSystem(URI.create("jrt:/"), Map.of("java.home", javaHome));
    Path path = fileSystem.getPath("/modules/java.base/module-info.class");
    System.err.printf("%s %s\n", path, Files.size(path));
  }
}
./j -Dorg.graalvm.home=$JAVA11_HOME -Djava.home=$JAVA11_HOME $JAVA11_HOME
Exception in thread "main" java.nio.file.NoSuchFileException: /tmp/tmp.IWs9yCuUBd/graalvm-jdk-23.0.1+11.1/lib/modules
	at [email protected]/sun.nio.fs.UnixFileSystemProvider.newFileChannel(UnixFileSystemProvider.java:225)

@mukel
Copy link
Member

mukel commented Nov 2, 2024

Hey @cushon, I debugged the issue, but I can only foresee a partial fix.

Passing a java.home is a nice feature, sadly the way it's implemented cannot be supported by Native Image, please see offending code here:
https://github.com/openjdk/jdk/blob/29882bfe7b7e76446a96862cd0a5e81c7e054415/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java#L114-L134

It relies on dynamically loading the JrtFileSystemProvider class from the provided java.home/lib/jrt-fs.jar and using that to access it's own JRT FS; it makes sense to always ensure compatibility, but Native Image cannot do that.

ATM, Native Image JRT support is meant to access the same VM used to build the image (specified by -Djava.home=...), specifying a different VM may not work.
We could improve usability by removing the dynamic class loading (only for Native Image) and always using the JRT support included in image to read arbitrary Java homes; but it may cause some compatibility issues.

Can you please provide more details on your use case?

Note: I found another bug that breaks the intended behavior of accessing the JRT FS for the provided java.home, this will be fixed ASAP.

@cushon
Copy link
Author

cushon commented Nov 3, 2024

Thanks for the explanation, I'd forgotten about how the jrt filesystem dynamically loads lib/jrt-fs.jar.

The use-case is a build tool that needs to resolve definitions of JDK APIs, conceptually it's trying to do the same thing as javac's --system flag.

We could improve usability by removing the dynamic class loading (only for Native Image) and always using the JRT support included in image to read arbitrary Java homes; but it may cause some compatibility issues.

That sounds interesting. I wonder how compatible it would be in practice, I don't know much the format has changed between JDK versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants