Skip to content

Commit 4d205e0

Browse files
committed
Adding the experimental option -H:Preserve
The `-H:Preserve` flag preserves all metadata from module, class-path entry, or a package in the image. This will allow users to more easily support third-party libraries and get more user-friendly experience with Native Image. The flag can be used in following ways: 1. -H:Preserve=all preserves all elements from the JDK and from the classpath 2. -H:Preserve=module=<module> preserves all elements from a given module 3. -H:Preserve=module=ALL-UNNAMED preserves all elements from the classpath 4. -H:Preserve=package=<package> preserves all elements from a given package 5. -H:Preserve=package=<package-wildcard> preserves all elements from packages captured by the wildcard. For example, -H:Preserve=package=my.app.*. 6. -H:Preserve=path=<cp-entry> preserves all elements from a given class-path entry 7. A combination of any of the previous uses, for example, -H:Preserve=path=<cp-entry>,module<module>
1 parent b0b8730 commit 4d205e0

File tree

26 files changed

+768
-252
lines changed

26 files changed

+768
-252
lines changed

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -221,7 +221,7 @@ public static void registerAllDeclaredConstructors(Class<?> declaringClass) {
221221
* @since 23.0
222222
*/
223223
public static void registerAllFields(Class<?> declaringClass) {
224-
ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass);
224+
ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllFields(ConfigurationCondition.alwaysTrue(), declaringClass);
225225
}
226226

227227
/**
@@ -231,7 +231,7 @@ public static void registerAllFields(Class<?> declaringClass) {
231231
* @since 23.0
232232
*/
233233
public static void registerAllDeclaredFields(Class<?> declaringClass) {
234-
ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass);
234+
ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredFields(ConfigurationCondition.alwaysTrue(), declaringClass);
235235
}
236236

237237
/**

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java

+57-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -40,15 +40,22 @@
4040
*/
4141
package org.graalvm.nativeimage.impl;
4242

43+
import java.lang.reflect.Constructor;
44+
import java.lang.reflect.Method;
45+
import java.lang.reflect.Modifier;
46+
47+
import org.graalvm.nativeimage.hosted.RuntimeJNIAccess;
48+
import org.graalvm.nativeimage.hosted.RuntimeProxyCreation;
49+
4350
public interface RuntimeReflectionSupport extends ReflectionRegistry {
4451
// needed as reflection-specific ImageSingletons key
4552
void registerAllMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz);
4653

4754
void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz);
4855

49-
void registerAllFieldsQuery(ConfigurationCondition condition, Class<?> clazz);
56+
void registerAllFields(ConfigurationCondition condition, Class<?> clazz);
5057

51-
void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class<?> clazz);
58+
void registerAllDeclaredFields(ConfigurationCondition condition, Class<?> clazz);
5259

5360
void registerAllConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz);
5461

@@ -67,4 +74,51 @@ public interface RuntimeReflectionSupport extends ReflectionRegistry {
6774
void registerAllSignersQuery(ConfigurationCondition condition, Class<?> clazz);
6875

6976
void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t);
77+
78+
default void registerClassFully(ConfigurationCondition condition, Class<?> clazz) {
79+
register(condition, false, clazz);
80+
81+
// GR-62143 Register all fields is very slow.
82+
// registerAllDeclaredFields(condition, clazz);
83+
// registerAllFields(condition, clazz);
84+
registerAllDeclaredMethodsQuery(condition, false, clazz);
85+
registerAllMethodsQuery(condition, false, clazz);
86+
registerAllDeclaredConstructorsQuery(condition, false, clazz);
87+
registerAllConstructorsQuery(condition, false, clazz);
88+
registerAllClassesQuery(condition, clazz);
89+
registerAllDeclaredClassesQuery(condition, clazz);
90+
registerAllNestMembersQuery(condition, clazz);
91+
registerAllPermittedSubclassesQuery(condition, clazz);
92+
registerAllRecordComponentsQuery(condition, clazz);
93+
registerAllSignersQuery(condition, clazz);
94+
95+
/* Register every single-interface proxy */
96+
// GR-62293 can't register proxies from jdk modules.
97+
if (clazz.getModule() == null && clazz.isInterface()) {
98+
RuntimeProxyCreation.register(clazz);
99+
}
100+
101+
RuntimeJNIAccess.register(clazz);
102+
try {
103+
for (Method declaredMethod : clazz.getDeclaredMethods()) {
104+
RuntimeJNIAccess.register(declaredMethod);
105+
}
106+
for (Constructor<?> declaredConstructor : clazz.getDeclaredConstructors()) {
107+
RuntimeJNIAccess.register(declaredConstructor);
108+
}
109+
// GR-62143 Registering all fields is very slow.
110+
// for (Field declaredField : clazz.getDeclaredFields()) {
111+
// RuntimeJNIAccess.register(declaredField);
112+
// RuntimeReflection.register(declaredField);
113+
// }
114+
} catch (LinkageError e) {
115+
/* If we can't link we can not register for JNI */
116+
}
117+
118+
// GR-62143 Registering all fields is very slow.
119+
// RuntimeSerialization.register(clazz);
120+
121+
// if we register unsafe allocated earlier there are build-time initialization errors
122+
register(condition, !(clazz.isArray() || clazz.isInterface() || clazz.isPrimitive() || Modifier.isAbstract(clazz.getModifiers())), clazz);
123+
}
70124
}

substratevm/CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ This changelog summarizes major changes to GraalVM Native Image.
44

55
## GraalVM for JDK 25
66
* (GR-58668) Enabled [Whole-Program Sparse Conditional Constant Propagation (WP-SCCP)](https://github.com/oracle/graal/pull/9821) by default, improving the precision of points-to analysis in Native Image. This optimization enhances static analysis accuracy and scalability, potentially reducing the size of the final native binary.
7-
* (GR-59313) Deprecated class-level metadata extraction using `native-image-inspect` and removed option `DumpMethodsData`. Use class-level SBOMs instead by passing `--enable-sbom=class-level,export` to the `native-image` builder. The default value of option `IncludeMethodData` was changed to `false`.
7+
* (GR-59313) Deprecated class-level metadata extraction using `native-image-inspect` and removed option `DumpMethodsData`. Use class-level SBOMs instead by passing `--enable-sbom=class-level,export` to the `native-image` builder. The default value of option `IncludeMethodData` was changed to `false`.
88
* (GR-52400) The build process now uses 85% of system memory in containers and CI environments. Otherwise, it tries to only use available memory. If less than 8GB of memory are available, it falls back to 85% of system memory. The reason for the selected memory limit is now also shown in the build resources section of the build output.
99
* (GR-59864) Added JVM version check to the Native Image agent. The agent will abort execution if the JVM major version does not match the version it was built with, and warn if the full JVM version is different.
1010
* (GR-59135) Verify if hosted options passed to `native-image` exist prior to starting the builder. Provide suggestions how to fix unknown options early on.
1111
* (GR-61492) The experimental JDWP option is now present in standard GraalVM builds.
1212
* (GR-55222) Enabled lazy deoptimization of runtime-compiled code, which reduces memory used for deoptimization. Can be turned off with `-H:-LazyDeoptimization`.
13+
* (GR-54953) Add the experimental option `-H:Preserve` that makes the program work correctly without providing reachability metadata. Correctness is achieved by preserving all classes, resources, and reflection metadata in the image. Usage: `-H:Preserve=[all|none|module=<module>|package=<package>|package=<package-wildcard>|path=<cp-entry>][,...]`.
1314

1415
## GraalVM for JDK 24 (Internal Version 24.2.0)
1516
* (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning.

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,28 @@ public void setBigBang(BigBang bb) {
5656
this.bb = bb;
5757
}
5858

59+
public static boolean isClassIncludedBase(Class<?> cls) {
60+
if (Feature.class.isAssignableFrom(cls)) {
61+
return false;
62+
}
63+
64+
if (AnnotationAccess.isAnnotationPresent(cls, TargetClass.class)) {
65+
return false;
66+
}
67+
try {
68+
Class<?> enclosingClass = cls.getEnclosingClass();
69+
return enclosingClass == null || isClassIncludedBase(enclosingClass);
70+
} catch (LinkageError e) {
71+
return true;
72+
}
73+
}
74+
5975
/**
6076
* Determine if the given class needs to be included in the image according to the policy.
6177
*/
6278
public boolean isClassIncluded(Class<?> cls) {
6379
Class<?> enclosingClass = cls.getEnclosingClass();
64-
return !Feature.class.isAssignableFrom(cls) && !AnnotationAccess.isAnnotationPresent(cls, TargetClass.class) && (enclosingClass == null || isClassIncluded(enclosingClass));
80+
return isClassIncludedBase(cls) && (enclosingClass == null || isClassIncluded(enclosingClass));
6581
}
6682

6783
/**

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ public void onTypeInstantiated(AnalysisType type) {
666666
/* Register the type as instantiated with all its super types. */
667667

668668
assert type.isInstantiated() : type;
669-
AnalysisError.guarantee(type.isArray() || (type.isInstanceClass() && !type.isAbstract()));
669+
AnalysisError.guarantee(type.isArray() || (type.isInstanceClass() && !type.isAbstract()), "Type %s must be either an array, or a non abstract instance class", type.getName());
670670

671671
TypeState typeState = TypeState.forExactType(this, type, true);
672672
TypeState typeStateNonNull = TypeState.forExactType(this, type, false);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1316,8 +1316,8 @@ public enum ReportingMode {
13161316
@Option(help = "Deprecated, option no longer has any effect.", deprecated = true, deprecationMessage = "It no longer has any effect, and no replacement is available")//
13171317
public static final HostedOptionKey<Boolean> UseOldMethodHandleIntrinsics = new HostedOptionKey<>(false);
13181318

1319-
@Option(help = "Include all classes, methods, fields, and resources from the class path", type = OptionType.Debug) //
1320-
public static final HostedOptionKey<Boolean> IncludeAllFromClassPath = new HostedOptionKey<>(false);
1319+
@Option(help = "file:doc-files/PreserveHelp.txt")//
1320+
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> Preserve = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());
13211321

13221322
@Option(help = "Force include include all public types and methods that can be reached using normal Java access rules.")//
13231323
public static final HostedOptionKey<Boolean> UseBaseLayerInclusionPolicy = new HostedOptionKey<>(false);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Preserves all classes and relevant resources from selected classpath entries in the image.
2+
For the selected classpath entries it is not necessary to provide reachability metadata because all classes, as well as resources not ending in '.class' or '.jar', are included into the image.
3+
Please note that using this option can result in a significant increase in build time and binary size, as well as a decrease in runtime performance.
4+
5+
Usage: -H:Preserve=[all|none|module=<module>|package=<package>|package=<package-wildcard>|path=<cp-entry>][,...]
6+
7+
The flag can be used in following ways:
8+
1. -H:Preserve=all preserves all elements from the JDK, the classpath, and the module path
9+
2. -H:Preserve=module=<module> preserves all elements from a given module
10+
3. -H:Preserve=module=ALL-UNNAMED preserves all elements from all class-path entries
11+
4. -H:Preserve=package=<package> preserves all elements from a given package
12+
5. -H:Preserve=package=<package-wildcard> preserves all elements from packages captured by the wildcard. For example, -H:Preserve=package=my.app.*
13+
6. -H:Preserve=path=<cp-entry> preserves all elements from a given class-path entry
14+
7. -H:Preserve=none disables all previous selections for preservation
15+
8. A comma-separated list of the previous cases. For example, -H:Preserve=path=<cp-entry>,module=<module>,package=<package>
16+
17+
'-H:Preserve' is only allowed on the native-image command line and cannot be embedded in the native-image.properties files.

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors;
2828

29+
import java.lang.reflect.Modifier;
2930
import java.util.EnumSet;
3031
import java.util.Objects;
3132

@@ -159,7 +160,8 @@ public void registerNegativeQuery(ConfigurationCondition condition, String class
159160

160161
@Platforms(Platform.HOSTED_ONLY.class)
161162
public void registerUnsafeAllocated(ConfigurationCondition condition, Class<?> clazz) {
162-
if (!clazz.isArray()) {
163+
if (!clazz.isArray() && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) {
164+
/* Otherwise, UNSAFE.allocateInstance results in InstantiationException */
163165
var conditionSet = unsafeInstantiatedClasses.putIfAbsent(clazz, RuntimeConditionSet.createHosted(condition));
164166
if (conditionSet != null) {
165167
conditionSet.addCondition(condition);

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@
3434
import com.oracle.svm.core.option.OptionOrigin;
3535
import com.oracle.svm.core.option.OptionUtils;
3636
import com.oracle.svm.driver.NativeImage.ArgumentQueue;
37-
import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.ExtendedOption;
38-
import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.LayerOption;
39-
import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.PackageOptionValue;
37+
import com.oracle.svm.hosted.driver.IncludeOptionsSupport;
38+
import com.oracle.svm.hosted.driver.IncludeOptionsSupport.ExtendedOption;
39+
import com.oracle.svm.hosted.driver.LayerOptionsSupport.LayerOption;
4040
import com.oracle.svm.util.LogUtils;
4141

4242
class DefaultOptionHandler extends NativeImage.OptionHandler<NativeImage> {
@@ -151,7 +151,7 @@ public boolean consume(ArgumentQueue args) {
151151
List<String> layerCreateValue = OptionUtils.resolveOptionValuesRedirection(SubstrateOptions.LayerCreate, rawLayerCreateValue, OptionOrigin.from(args.argumentOrigin));
152152
LayerOption layerOption = LayerOption.parse(layerCreateValue);
153153
for (ExtendedOption option : layerOption.extendedOptions()) {
154-
var packageOptionValue = PackageOptionValue.from(option);
154+
var packageOptionValue = IncludeOptionsSupport.PackageOptionValue.from(option);
155155
if (packageOptionValue == null) {
156156
continue;
157157
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ public void collectResources(ResourceCollector resourceCollector) {
116116

117117
/* Collect remaining resources from classpath */
118118
classLoaderSupport.classpath().stream().parallel().forEach(classpathFile -> {
119-
boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile) || classLoaderSupport.includeAllFromClassPath();
119+
boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile) ||
120+
classLoaderSupport.getClassPathEntriesToPreserve().contains(classpathFile);
120121
try {
121122
if (Files.isDirectory(classpathFile)) {
122123
scanDirectory(classpathFile, resourceCollector, includeCurrent);
@@ -132,7 +133,8 @@ public void collectResources(ResourceCollector resourceCollector) {
132133
private void collectResourceFromModule(ResourceCollector resourceCollector, ResourceLookupInfo info) {
133134
ModuleReference moduleReference = info.resolvedModule.reference();
134135
try (ModuleReader moduleReader = moduleReference.open()) {
135-
boolean includeCurrent = classLoaderSupport.getJavaModuleNamesToInclude().contains(info.resolvedModule().name());
136+
boolean includeCurrent = classLoaderSupport.getJavaModuleNamesToInclude().contains(info.resolvedModule().name()) ||
137+
classLoaderSupport.getJavaModuleNamesToPreserve().contains(info.resolvedModule().name());
136138
List<ConditionalResource> resourcesFound = new ArrayList<>();
137139
moduleReader.list().forEach(resourceName -> {
138140
var conditionsWithOrigins = shouldIncludeEntry(info.module, resourceCollector, resourceName, moduleReference.location().orElse(null), includeCurrent);

0 commit comments

Comments
 (0)