Skip to content

Commit

Permalink
Adding the experimental option -H:Preserve
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
vjovanov committed Mar 7, 2025
1 parent 2e20bdc commit 7c04e39
Show file tree
Hide file tree
Showing 25 changed files with 744 additions and 250 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -221,7 +221,7 @@ public static void registerAllDeclaredConstructors(Class<?> declaringClass) {
* @since 23.0
*/
public static void registerAllFields(Class<?> declaringClass) {
ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass);
ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllFields(ConfigurationCondition.alwaysTrue(), declaringClass);
}

/**
Expand All @@ -231,7 +231,7 @@ public static void registerAllFields(Class<?> declaringClass) {
* @since 23.0
*/
public static void registerAllDeclaredFields(Class<?> declaringClass) {
ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass);
ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredFields(ConfigurationCondition.alwaysTrue(), declaringClass);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -40,15 +40,23 @@
*/
package org.graalvm.nativeimage.impl;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.graalvm.nativeimage.hosted.RuntimeJNIAccess;
import org.graalvm.nativeimage.hosted.RuntimeProxyCreation;

public interface RuntimeReflectionSupport extends ReflectionRegistry {
// needed as reflection-specific ImageSingletons key
void registerAllMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz);

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

void registerAllFieldsQuery(ConfigurationCondition condition, Class<?> clazz);
void registerAllFields(ConfigurationCondition condition, Class<?> clazz);

void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class<?> clazz);
void registerAllDeclaredFields(ConfigurationCondition condition, Class<?> clazz);

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

Expand All @@ -67,4 +75,49 @@ public interface RuntimeReflectionSupport extends ReflectionRegistry {
void registerAllSignersQuery(ConfigurationCondition condition, Class<?> clazz);

void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t);

default void registerClassFully(ConfigurationCondition condition, Class<?> clazz) {
boolean unsafeAllocated = !(clazz.isArray() || clazz.isInterface() || clazz.isPrimitive() || Modifier.isAbstract(clazz.getModifiers()));
register(condition, unsafeAllocated, clazz);

// GR-62143 Register all fields is very slow.
// registerAllDeclaredFields(condition, clazz);
// registerAllFields(condition, clazz);
registerAllDeclaredMethodsQuery(condition, false, clazz);
registerAllMethodsQuery(condition, false, clazz);
registerAllDeclaredConstructorsQuery(condition, false, clazz);
registerAllConstructorsQuery(condition, false, clazz);
registerAllClassesQuery(condition, clazz);
registerAllDeclaredClassesQuery(condition, clazz);
registerAllNestMembersQuery(condition, clazz);
registerAllPermittedSubclassesQuery(condition, clazz);
registerAllRecordComponentsQuery(condition, clazz);
registerAllSignersQuery(condition, clazz);

/* Register every single-interface proxy */
// GR-62293 can't register proxies from jdk modules.
if (clazz.getModule() == null && clazz.isInterface()) {
RuntimeProxyCreation.register(clazz);
}

RuntimeJNIAccess.register(clazz);
try {
for (Method declaredMethod : clazz.getDeclaredMethods()) {
RuntimeJNIAccess.register(declaredMethod);
}
for (Constructor<?> declaredConstructor : clazz.getDeclaredConstructors()) {
RuntimeJNIAccess.register(declaredConstructor);
}
for (Field declaredField : clazz.getDeclaredFields()) {
RuntimeJNIAccess.register(declaredField);
// GR-62143 Registering all fields is very slow.
// RuntimeReflection.register(declaredField);
}
} catch (LinkageError e) {
/* If we can't link we can not register for JNI */
}

// GR-62143 Registering all fields is very slow.
// RuntimeSerialization.register(clazz);
}
}
3 changes: 2 additions & 1 deletion substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ This changelog summarizes major changes to GraalVM Native Image.

## GraalVM for JDK 25
* (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.
* (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`.
* (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`.
* (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.
* (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.
* (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.
* (GR-61492) The experimental JDWP option is now present in standard GraalVM builds.
* (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>][,...]`.

## GraalVM for JDK 24 (Internal Version 24.2.0)
* (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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,28 @@ public void setBigBang(BigBang bb) {
this.bb = bb;
}

public static boolean isClassIncludedBase(Class<?> cls) {
if (Feature.class.isAssignableFrom(cls)) {
return false;
}

if (AnnotationAccess.isAnnotationPresent(cls, TargetClass.class)) {
return false;
}
try {
Class<?> enclosingClass = cls.getEnclosingClass();
return enclosingClass == null || isClassIncludedBase(enclosingClass);
} catch (LinkageError e) {
return true;
}
}

/**
* Determine if the given class needs to be included in the image according to the policy.
*/
public boolean isClassIncluded(Class<?> cls) {
Class<?> enclosingClass = cls.getEnclosingClass();
return !Feature.class.isAssignableFrom(cls) && !AnnotationAccess.isAnnotationPresent(cls, TargetClass.class) && (enclosingClass == null || isClassIncluded(enclosingClass));
return isClassIncludedBase(cls) && (enclosingClass == null || isClassIncluded(enclosingClass));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ public void onTypeInstantiated(AnalysisType type) {
/* Register the type as instantiated with all its super types. */

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

TypeState typeState = TypeState.forExactType(this, type, true);
TypeState typeStateNonNull = TypeState.forExactType(this, type, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1313,8 +1313,8 @@ public enum ReportingMode {
@Option(help = "Deprecated, option no longer has any effect.", deprecated = true, deprecationMessage = "It no longer has any effect, and no replacement is available")//
public static final HostedOptionKey<Boolean> UseOldMethodHandleIntrinsics = new HostedOptionKey<>(false);

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

@Option(help = "Force include include all public types and methods that can be reached using normal Java access rules.")//
public static final HostedOptionKey<Boolean> UseBaseLayerInclusionPolicy = new HostedOptionKey<>(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Makes the program work correctly without providing reachability metadata.
This is achieved by preserving all class elements, resources, and reflection metadata in the image.
Usage of this options can cause a large increase in image size, and a small decrease in overall performance.

Usage: -H:Preserve=[all|none|module=<module>|package=<package>|package=<package-wildcard>|path=<cp-entry>][,...]

The flag can be used in following ways:
1. -H:Preserve=all preserves all elements from the JDK, the classpath, and the module path
2. -H:Preserve=module=<module> preserves all elements from a given module
3. -H:Preserve=module=ALL-UNNAMED preserves all elements from all class-path entries
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. -H:Preserve=none disables all previous selections for preservation
8. A comma-separated list of the previous cases. For example, -H:Preserve=path=<cp-entry>,module=<module>,package=<package>

-H:Preserve is only allowed on the native-image command line and cannot be embedded in the native-image.properties files.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

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

import java.lang.reflect.Modifier;
import java.util.EnumSet;
import java.util.Objects;

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

@Platforms(Platform.HOSTED_ONLY.class)
public void registerUnsafeAllocated(ConfigurationCondition condition, Class<?> clazz) {
if (!clazz.isArray()) {
if (!clazz.isArray() && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) {
/* Otherwise, UNSAFE.allocateInstance results in InstantiationException */
var conditionSet = unsafeInstantiatedClasses.putIfAbsent(clazz, RuntimeConditionSet.createHosted(condition));
if (conditionSet != null) {
conditionSet.addCondition(condition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
import com.oracle.svm.core.option.OptionOrigin;
import com.oracle.svm.core.option.OptionUtils;
import com.oracle.svm.driver.NativeImage.ArgumentQueue;
import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.ExtendedOption;
import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.LayerOption;
import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.PackageOptionValue;
import com.oracle.svm.hosted.driver.IncludeOptionsSupport;
import com.oracle.svm.hosted.driver.IncludeOptionsSupport.ExtendedOption;
import com.oracle.svm.hosted.driver.LayerOptionsSupport.LayerOption;
import com.oracle.svm.util.LogUtils;

class DefaultOptionHandler extends NativeImage.OptionHandler<NativeImage> {
Expand Down Expand Up @@ -151,7 +151,7 @@ public boolean consume(ArgumentQueue args) {
List<String> layerCreateValue = OptionUtils.resolveOptionValuesRedirection(SubstrateOptions.LayerCreate, rawLayerCreateValue, OptionOrigin.from(args.argumentOrigin));
LayerOption layerOption = LayerOption.parse(layerCreateValue);
for (ExtendedOption option : layerOption.extendedOptions()) {
var packageOptionValue = PackageOptionValue.from(option);
var packageOptionValue = IncludeOptionsSupport.PackageOptionValue.from(option);
if (packageOptionValue == null) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ public void collectResources(ResourceCollector resourceCollector) {

/* Collect remaining resources from classpath */
classLoaderSupport.classpath().stream().parallel().forEach(classpathFile -> {
boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile) || classLoaderSupport.includeAllFromClassPath();
boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile) ||
classLoaderSupport.getClassPathEntriesToPreserve().contains(classpathFile) || classLoaderSupport.preserveAll();
try {
if (Files.isDirectory(classpathFile)) {
scanDirectory(classpathFile, resourceCollector, includeCurrent);
Expand All @@ -132,7 +133,8 @@ public void collectResources(ResourceCollector resourceCollector) {
private void collectResourceFromModule(ResourceCollector resourceCollector, ResourceLookupInfo info) {
ModuleReference moduleReference = info.resolvedModule.reference();
try (ModuleReader moduleReader = moduleReference.open()) {
boolean includeCurrent = classLoaderSupport.getJavaModuleNamesToInclude().contains(info.resolvedModule().name());
boolean includeCurrent = classLoaderSupport.getJavaModuleNamesToInclude().contains(info.resolvedModule().name()) ||
classLoaderSupport.getJavaModuleNamesToPreserve().contains(info.resolvedModule().name());
List<ConditionalResource> resourcesFound = new ArrayList<>();
moduleReader.list().forEach(resourceName -> {
var conditionsWithOrigins = shouldIncludeEntry(info.module, resourceCollector, resourceName, moduleReference.location().orElse(null), includeCurrent);
Expand Down
Loading

0 comments on commit 7c04e39

Please sign in to comment.