Site icon JVM Advent

Peaceful and Bright Future of Integrity by Default in Java

As Java continues to evolve, its commitment to stability and robustness grows stronger. Recent and upcoming JDK releases reflect this approach by gradually enhancing boundaries of unsafe APIs: restricting dynamic agent loading, deprecating potentially unsafe memory-access methods, and preparing for stricter Java Native Interface (JNI) usage. Introducing the Foreign Function and Memory (FFM) API in JDK 22 simplified access to native code while offering a much safer approach to preserve Java‘s resilience, reflecting even more the shift to integrity by default. This article explores these changes that embody a thoughtful transition to a more predictable and reliable Java ecosystem.

Integrity in the Java Platform

In software, integrity refers to being whole and undivided, ensuring that the constructs from which we build programs are complete and sound. This means that the Java Platform’s specifications are exhaustive (completeness), and its implementations adhere strictly to them (soundness). This foundational integrity enables you to build your application logic with confidence upon the Java platform’s constructs.

The “Integrity by Default” JDK Enhancement Proposal (JEP) emphasizes the importance of protecting code and data against unwanted or unwise use. While encapsulation is the fundamental tool for establishing integrity, the Java Platform contains certain unsafe APIs that can undermine this expectation, potentially affecting the correctness, maintainability, scalability, security, and performance of your application or library:

To prevent misuse of these unsafe APIs, the proposal recommends restricting direct access to them so that libraries, frameworks, and tools cannot use them by default. Moreover, a series of JEPs gradually address each pain point when using unsafe APIs, providing migration alternatives and allowing you to override this default restriction when necessary.

Gradual Limitation of Access to Unsafe APIs And Alternatives

Organize to Disallow Dynamic Loading of Agents

Building upon the integrity theme, a JDK 21 enhancement (JEP 451) focused on the implications of dynamic loading of agents into a running Java Virtual Machine (JVM). Agents are a component introduced in JDK 5 and allow for the instrumentation of classes, enabling tools like profilers to monitor applications. For example, to debug a remote Java application you probably configured it with the -agentlib:jdwp option in order to enable at startup an agent built into the JVM. Under the hood, the javalauncher uses the Attach API that allows a tool launched with appropriate operating-system privileges to connect to a running JVM.

However, certain libraries exploited the Attach API to silently connect to the JVMs in which they run, load agents dynamically and gain code-altering superpowers. In consequence, dynamically loading agents poses risks to application integrity. Since JDK 21, the JVM issues warnings when agents are loaded dynamically to alert you of these risks, but also in order for you to prepare for a future release where such actions may be disallowed by default. This change does not affect the majority of tools that do not need to load agents dynamically.

💡 The gracious manner for libraries to employ an agent is to load it at startup with the -javaagent/-agentlib options.

This approach balances the demand for serviceability with the need to maintain your application integrity.

Act upon Unsafe Memory-Access Methods Usage

Throughout time, the sun.misc.Unsafe class has provided developers access to low-level operations, particularly for tasks like:

Yet, utilizing these methods comes with risks:

Starting with Java 23, the JDK is phasing out the memory-access methods in sun.misc.Unsafe, due to the risks and limitations associated with these unsafe operations. Instead, the recommended alternatives are the VarHandle API (introduced in JDK 9) and the  Foreign Function and Memory API (introduced in JDK 22).  For example, you might have used Unsafe for atomic updates in case of on-heap memory operations. To avoid such a dangerous practice, you can migrate from using Unsafeto achieve the same with VarHandle:

/// Migration example from Unsafe
private static final Unsafe UNSAFE = ...;
private static final long OFFSET;

static {
    try {
       OFFSET = UNSAFE.objectFieldOffset(
             Point.class.getDeclaredField("x")
       );
    } catch (Exception ex) {
       throw new AssertionError(ex);
    }
}

private int x;

public boolean update(int newValue) {
   return UNSAFE.compareAndSwapInt(this, OFFSET, x, newValue);
}

/// Use VarHandle to achieve the same
private static final VarHandle HANDLE = MethodHandles.lookup()
        .findVarHandle(Point.class, "x", int.class);

public boolean update(int newValue) {
   return HANDLE.compareAndSet(this, x, newValue);
}

Similarly, in case you used Unsafe for off-heap memory operations, you can migrate to MemorySegment :

// Using Unsafe for off-heap memory
long address = UNSAFE.allocateMemory(1024);
UNSAFE.putInt(address, 42);
int value = UNSAFE.getInt(address);
UNSAFE.freeMemory(address);

// Using MemorySegment to achieve the same
try (Arena arena = Arena.ofShared()) {
    long byteSize = ValueLayout.JAVA_INT.byteSize();
    MemorySegment segment = arena.allocate(byteSize);
    segment.set(ValueLayout.JAVA_INT, 0, 42);
    int value = segment.get(ValueLayout.JAVA_INT, 0);
}

These standard APIs offer safe, performant replacements for most use cases, ensuring compatibility with modern and future Java versions.

💡 There are a few tools that can further help you identify dependencies on Unsafe:

With each JDK release, you can assess how the deprecation and removal of these methods will affect libraries by running with a new command line option: --sun-misc-unsafe-memory-access={allow|warn|debug|deny}.  The upcoming JDK releases will deprecate the memory-access methods in sun.misc.Unsafe in phases, currently envisioned as :

  1. JDK 23 introduced deprecation with warnings at compile and runtime (by default--sun-misc-unsafe-memory-access=allow).
  2. As of JDK 24, through JEP 498, runtime warnings become default (by default--sun-misc-unsafe-memory-access=warn).
  3. As of JDK 26 unsupported operations throw exceptions by default(by default--sun-misc-unsafe-memory-access=deny).
  4. After JDK 26 the methods are removed entirely and the JVM ignores --sun-misc-unsafe-memory-access option.

When you migrate from sun.misc.Unsafe memory access methods, please do not rely on unsupported JDK internals, as this will increase risks of breaking changes.

PreparE to Restrict the Use of JNI

The iconic enhancement of JDK 22 was the graduation to final feature of the  Foreign Function & Memory (FFM) API. This API facilitates Java programs to invoke code outside the JVM (foreign functions) and to safely access memory not managed by the JVM (foreign memory).

Yet, since JDK 1.1, the Java Native Interface (JNI) facilitated interoperability between Java code and native code. While helpful, JNI interactions can compromise application integrity:

Native access restrictions pertain to the processes of loading and linking native libraries. Given this potential for undefined behavior and JVM crashes, application developers must be carefully enabling native access for specific Java code at startup. This action acknowledges the necessity to load and link native libraries, thereby lifting the imposed restrictions.

💡When a library utilizes JNI or the FFM API, its documentation should inform its users (e.g., application developers) about the need to enable native access. If you are the developer or deployer who uses such a library, you are responsible for allowing native access. For example, if your application code uses a library that requires --enable-native-access=ALL-UNNAMED option at runtime ,you should be aware that this lifts native access restrictions on JNI and the FFM API for all classes on the class path.

As the --enable-native-access=ALL-UNNAMED option is broad, you can minimize risk and enhance integrity by moving JAR files that use JNI or the FFM API to the module path. This strategy allows native access explicitly enabled for those JAR files rather than the entire class path. If you move a JAR file from the class path to the module path without being modularized, the Java runtime will treat it as an automatic module, naming it based on its filename.

If a module does not have native access enabled, any code in that module that tries to perform a restricted operation is considered illegal. The Java runtime’s response to such operations is governed by the --illegal-native-access command-line option and works as follows:

In JNI, the JVM loads native libraries using the load and loadLibrary methods of the java.lang.Runtime class. The identically named methods in the java.lang.System class invoke the corresponding Runtime methods. Loading a native library is risky because it can execute native code through initialization functions defined in the library or via a JNI_OnLoad function invoked by the Java runtime. Due to these risks, JDK 24 restricts load and loadLibrary methods , similar to the SymbolLookup::libraryLookup methods in the FFM API.

Before JDK 24, If you enabled native access for one or more modules via the --enable-native-access option, the attempts to call restricted FFM methods from any other module would result in an IllegalCallerException. This behavior has been relaxed in JDK 24 to align the FFM API with JNI. So, illegal native access operations in the FFM API get the same treatment as in JNI, resulting in warnings rather than exceptions. To restore the previous behavior, you can use the following combination of options:

java --enable-native-access=Module1,... --illegal-native-access=deny ...

In order to prepare your code for upcoming changes, you should run existing code with deny value for --enable-native-access to identify any code that requires native access.

Conclusion

Java Platform’s commitment to integrity is evident through JDK enhancement proposals and with every improvement integrated in its releases. By introducing modern, safe alternatives while slowly deprecating unsafe features, the platform aligns with the evolving best practices in software development.

 

Author: Ana-Maria Mihalceanu

Ana is a Java Champion Alumni, Developer Advocate, guest author of the book “DevOps tools for Java Developers”, and a constant adopter of challenging technical scenarios involving Java-based frameworks and multiple cloud providers. She actively supports technical communities’ growth through knowledge sharing and enjoys curating content for conferences as a program committee member. To learn more about/from her, follow her on Twitter @ammbra1508 or on Mastodon @ammbra1508.mastodon.social.

Exit mobile version