-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Describe the issue
NI boot module layer currently only contains modules which contents are reachable after analysis. This behavior follows the closed-world assumption and is naturally translated to module system as well. However, in the case of modules, it can in some cases cause unintended results. This issue shows a simple example, with the goal to open a discussion about potential changes in the current closed-world approach when it comes to modules.
Describe GraalVM and your environment:
- This example assumes that graal repository is even with latest
master
, or that it is based on a commit after the merge of [GR-30957] Implement Module/ModuleLayer substitutions #3445
Steps to reproduce the issue
A simple example can be a modularized application with two modules - one of which will be a "library" module exporting a package that will be used by the "main" module.
The "library" module can be defined as:
module core.app {
exports core.util;
}
It contains a class core.util.WorkerUtil
with the following definition:
package core.util;
public class WorkerUtil {
public static void doSomething() {
System.out.println("WorkerUtil working...");
}
}
The "main" module can be defined as:
module main.app {
requires core.app;
}
Our main method, contained in main.app
module, can be defined as:
package example.app;
import java.lang.ModuleLayer;
import core.util.WorkerUtil;
public class AppMain {
// This function can also be in another module
public static void useLibrary() {
WorkerUtil.doSomething();
}
public static void main(String[] args) {
useLibrary();
assert ModuleLayer.boot().modules()
.stream()
.anyMatch(m -> m.getName().equals("core.app"));
}
}
We can build an image of this application (in this example all jar files that are created by javac
are located in pkg
directory):
$ USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM=true native-image -ea -p pkg -m main.app/example.app.AppMain
We can now verify that the assertion will hold by running the image. If we do not invoke the useLibrary()
method, the entire core.app
module will not be included in the image, as types from that module are not used. Re-running native-image
in the same way as above (however this time not invoking useLibrary()
method), and invoking the generated executable will lead to an AssertionError
:
Exception in thread "main" java.lang.AssertionError
at example.app.AppMain.main(AppMain.java:14)
This way modules can unintentionally break dependant modules simply by removing dependencies.
More details
For this example, the size of an executable which only contains only reachable modules (14 in this case): 11481768 B
(~11M
)
For this example, the size of an executable which contains all modules on the module path (26 in this case): 11485864 B
(~11M
)
From here, we can see that the difference is 4096 B
, which averages to ~341 B
per module (this value correlates to the number of packages contained in the module).