Talk about jvm’s -XX:MaxDirectMemorySize

  jvm

Order

This article mainly studies jvm’s -XX:MaxDirectMemorySize

-XX:MaxDirectMemorySize

-XX:MaxDirectMemorySize=size for setting New I/O (java.nio) the maximum size of direct-buffer allocations, and the unit of size can be k/K, m/M, g/g; If this parameter is not set, the default value is 0, which means that the JVM itself automatically selects the maximum size for NIO direct-buffer allocations.

System.initPhase1

java.base/java/lang/System.java

public final class System {
    /* Register the natives via the static initializer.
     *
     * VM will invoke the initializeSystemClass method to complete
     * the initialization for this class separated from clinit.
     * Note that to use properties set by the VM, see the constraints
     * described in the initializeSystemClass method.
     */
    private static native void registerNatives();
    static {
        registerNatives();
    }

    /** Don't let anyone instantiate this class */
    private System() {
    }

    /**
     * Initialize the system class.  Called after thread initialization.
     */
    private static void initPhase1() {
        // VM might invoke JNU_NewStringPlatform() to set those encoding
        // sensitive properties (user.home, user.name, boot.class.path, etc.)
        // during "props" initialization.
        // The charset is initialized in System.c and does not depend on the Properties.
        Map<String, String> tempProps = SystemProps.initProperties();
        VersionProps.init(tempProps);

        // There are certain system configurations that may be controlled by
        // VM options such as the maximum amount of direct memory and
        // Integer cache size used to support the object identity semantics
        // of autoboxing.  Typically, the library will obtain these values
        // from the properties set by the VM.  If the properties are for
        // internal implementation use only, these properties should be
        // masked from the system properties.
        //
        // Save a private copy of the system properties object that
        // can only be accessed by the internal implementation.
        VM.saveProperties(tempProps);
        props = createProperties(tempProps);

        StaticProperty.javaHome();          // Load StaticProperty to cache the property values

        lineSeparator = props.getProperty("line.separator");

        FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
        FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
        FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
        setIn0(new BufferedInputStream(fdIn));
        setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
        setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));

        // Setup Java signal handlers for HUP, TERM, and INT (where available).
        Terminator.setup();

        // Initialize any miscellaneous operating system settings that need to be
        // set for the class libraries. Currently this is no-op everywhere except
        // for Windows where the process-wide error mode is set before the java.io
        // classes are used.
        VM.initializeOSEnvironment();

        // The main thread is not added to its thread group in the same
        // way as other threads; we must do it ourselves here.
        Thread current = Thread.currentThread();
        current.getThreadGroup().add(current);

        // register shared secrets
        setJavaLangAccess();

        // Subsystems that are invoked during initialization can invoke
        // VM.isBooted() in order to avoid doing things that should
        // wait until the VM is fully initialized. The initialization level
        // is incremented from 0 to 1 here to indicate the first phase of
        // initialization has completed.
        // IMPORTANT: Ensure that this remains the last initialization action!
        VM.initLevel(1);
    }

    //......
}

The initPhase1 method of the System will call the VM.saveProperties(tempProps) method to save a system configuration for internal implementation. Where tempProps is SystemProps.initProperties ()

jvm.cpp

hotspot/share/prims/jvm.cpp

  // Convert the -XX:MaxDirectMemorySize= command line flag
  // to the sun.nio.MaxDirectMemorySize property.
  // Do this after setting user properties to prevent people
  // from setting the value with a -D option, as requested.
  // Leave empty if not supplied
  if (!FLAG_IS_DEFAULT(MaxDirectMemorySize)) {
    char as_chars[256];
    jio_snprintf(as_chars, sizeof(as_chars), JULONG_FORMAT, MaxDirectMemorySize);
    Handle key_str = java_lang_String::create_from_platform_dependent_str("sun.nio.MaxDirectMemorySize", CHECK_NULL);
    Handle value_str  = java_lang_String::create_from_platform_dependent_str(as_chars, CHECK_NULL);
    result_h->obj_at_put(ndx * 2,  key_str());
    result_h->obj_at_put(ndx * 2 + 1, value_str());
    ndx++;
  }

Jvm.cpp contains a code to convert the -XX:MaxDirectMemorySize command parameter into an attribute whose key is sun.nio.MaxDirectMemorySize

VM.saveProperties

java.base/jdk/internal/misc/VM.java

public class VM {

    // the init level when the VM is fully initialized
    private static final int JAVA_LANG_SYSTEM_INITED     = 1;
    private static final int MODULE_SYSTEM_INITED        = 2;
    private static final int SYSTEM_LOADER_INITIALIZING  = 3;
    private static final int SYSTEM_BOOTED               = 4;
    private static final int SYSTEM_SHUTDOWN             = 5;


    // 0, 1, 2, ...
    private static volatile int initLevel;
    private static final Object lock = new Object();

    //......

    // A user-settable upper limit on the maximum amount of allocatable direct
    // buffer memory.  This value may be changed during VM initialization if
    // "java" is launched with "-XX:MaxDirectMemorySize=<size>".
    //
    // The initial value of this field is arbitrary; during JRE initialization
    // it will be reset to the value specified on the command line, if any,
    // otherwise to Runtime.getRuntime().maxMemory().
    //
    private static long directMemory = 64 * 1024 * 1024;

    // Returns the maximum amount of allocatable direct buffer memory.
    // The directMemory variable is initialized during system initialization
    // in the saveAndRemoveProperties method.
    //
    public static long maxDirectMemory() {
        return directMemory;
    }

    //......

    // Save a private copy of the system properties and remove
    // the system properties that are not intended for public access.
    //
    // This method can only be invoked during system initialization.
    public static void saveProperties(Map<String, String> props) {
        if (initLevel() != 0)
            throw new IllegalStateException("Wrong init level");

        // only main thread is running at this time, so savedProps and
        // its content will be correctly published to threads started later
        if (savedProps == null) {
            savedProps = props;
        }

        // Set the maximum amount of direct memory.  This value is controlled
        // by the vm option -XX:MaxDirectMemorySize=<size>.
        // The maximum amount of allocatable direct buffer memory (in bytes)
        // from the system property sun.nio.MaxDirectMemorySize set by the VM.
        // If not set or set to -1, the max memory will be used
        // The system property will be removed.
        String s = props.get("sun.nio.MaxDirectMemorySize");
        if (s == null || s.isEmpty() || s.equals("-1")) {
            // -XX:MaxDirectMemorySize not given, take default
            directMemory = Runtime.getRuntime().maxMemory();
        } else {
            long l = Long.parseLong(s);
            if (l > -1)
                directMemory = l;
        }

        // Check if direct buffers should be page aligned
        s = props.get("sun.nio.PageAlignDirectMemory");
        if ("true".equals(s))
            pageAlignDirectMemory = true;
    }

    //......
}

VM’s saveProperties method reads the sun.nio.MaxDirectMemorySize property, and if it is null or empty or -1, it is set to runtime.getruntime (). maxmemory (); If MaxdirectMemorySize is set and the value is greater than -1, then the value is used as the value of DirectMemory; While VM’s maxDirectMemory method returns the value of directMemory

Gets the value of maxDirectMemory

Example

    public BufferPoolMXBean getDirectBufferPoolMBean(){
        return ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class)
                .stream()
                .filter(e -> e.getName().equals("direct"))
                .findFirst()
                .orElseThrow();
    }

    public JavaNioAccess.BufferPool getNioBufferPool(){
        return SharedSecrets.getJavaNioAccess().getDirectBufferPool();
    }

    /**
     * -XX:MaxDirectMemorySize=60M
     */
    @Test
    public void testGetMaxDirectMemory(){
        ByteBuffer.allocateDirect(25*1024*1024);
        System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024.0);
        System.out.println(VM.maxDirectMemory() / 1024.0 / 1024.0);
        System.out.println(getDirectBufferPoolMBean().getTotalCapacity() / 1024.0 / 1024.0);
        System.out.println(getNioBufferPool().getTotalCapacity() / 1024.0 / 1024.0);
    }

Output

The output is as follows:

4096.0
60.0
25.0
25.0
  • Because java9 is modularized, VM is changed from sun.misc.VM to jdk.internal.misc.VM; under java.base module; The above code defaults to unamed module. To use jdk.internal.misc.VM, you need to use-ADD-EXPORTS java.base/jdk.internal.misc=ALL-UNNAMED to export it to UNNAMED so that it can run.
  • Similarly, after java9 was modularized, SharedSecrets changed from sun.misc.SharedSecrets to jdk.internal.access.sharedsecures under java.base module. To use-add-exports java.base/ jdk.internal.access=ALL-UNNAMED to export it to UNNAMED so that it can run
  • As can be seen from the output results, the value output by Runtime.getRuntime().maxMemory () is correct, while BufferPoolMXBean and JavaNioAccess.BufferPool’s getTotalCapacity return the directBuffer size instead of the max value.

Use apis to view directBuffer usage.

Example

    /**
     * -XX:MaxDirectMemorySize=60M
     */
    @Test
    public void testGetDirectMemoryUsage(){
        ByteBuffer.allocateDirect(30*1024*1024);
        System.out.println(getDirectBufferPoolMBean().getMemoryUsed() / 1024.0 / 1024.0);
        System.out.println(getNioBufferPool().getMemoryUsed() / 1024.0 / 1024.0);
    }

Output

The output is as follows:

30.0
30.0

You can see BufferPoolMXBean and JavaNioAccess.BufferPool’s getMemoryUsed can return directBuffer size.

OOM

java.lang.OutOfMemoryError: Direct buffer memory

    at java.base/java.nio.Bits.reserveMemory(Bits.java:175)
    at java.base/java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:118)
    at java.base/java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:317)

If the above ByteBuffer.allocateDirect is changed to allocate more than 60M, then run throws OutOfMemoryError.

Use NMT to view directBuffer usage

jcmd 3088 VM.native_memory scale=MB
3088:

Native Memory Tracking:

Total: reserved=5641MB, committed=399MB
-                 Java Heap (reserved=4096MB, committed=258MB)
                            (mmap: reserved=4096MB, committed=258MB)

-                     Class (reserved=1032MB, committed=6MB)
                            (classes #1609)
                            (  instance classes #1460, array classes #149)
                            (mmap: reserved=1032MB, committed=5MB)
                            (  Metadata:   )
                            (    reserved=8MB, committed=5MB)
                            (    used=3MB)
                            (    free=2MB)
                            (    waste=0MB =0.00%)
                            (  Class space:)
                            (    reserved=1024MB, committed=1MB)
                            (    used=0MB)
                            (    free=0MB)
                            (    waste=0MB =0.00%)

-                    Thread (reserved=18MB, committed=18MB)
                            (thread #18)
                            (stack: reserved=18MB, committed=18MB)

-                      Code (reserved=242MB, committed=7MB)
                            (mmap: reserved=242MB, committed=7MB)

-                        GC (reserved=203MB, committed=60MB)
                            (malloc=18MB #2443)
                            (mmap: reserved=185MB, committed=43MB)

-                  Internal (reserved=1MB, committed=1MB)
                            (malloc=1MB #1257)

-                     Other (reserved=30MB, committed=30MB)
                            (malloc=30MB #2)

-                    Symbol (reserved=1MB, committed=1MB)
                            (malloc=1MB #13745)

-        Shared class space (reserved=17MB, committed=17MB)
                            (mmap: reserved=17MB, committed=17MB)

From the Other part, it can be seen that its value is the same as that used by ByteBuffer. AllowedRect. If you change the value of ByteBuffer. AllowedRect and review it again, it can be found that the Other part changes accordingly. Therefore, it is preliminarily concluded that the Other part should reflect the usage size of direct memory.

Summary

  • -XX:MaxDirectMemorySize=size for setting New I/O (java.nio) the maximum size of direct-buffer allocations, and the unit of size can be k/K, m/M, g/g; If this parameter is not set, the default value is 0, which means that the JVM itself automatically selects the maximum size for NIO direct-buffer allocations; You can see from the code java.base/jdk/internal/misc/VM.java that the default is Runtime.getRuntime().maxMemory ()
  • The value of maxDirectMemory can be obtained by using jdk.internal.misc.vm.maxdirectmemory (); Because java9 is modularized, VM is changed from sun.misc.VM to jdk.internal.misc.VM; under java.base module; The above code defaults to unamed module. To use jdk.internal.misc.VM, you need to use-ADD-EXPORTS java.base/jdk.internal.misc=ALL-UNNAMED to export it to UNNAMED so that it can run.
  • BufferPoolMXBean and JavaNioAccess.BufferPool (Obtained through SharedSecrets) can get the size of direct memory; Among them, after java9 is modularized, SharedSecrets are changed from sun.misc.SharedSecrets to jdk.internal.access.sharedsecurities under java.base module; To use-add-exports java.base/jdk.internal.access=ALL-UNNAMED to export it to UNNAMED so that it can run

In addition, the use of direct memory can be viewed by using NMT, which is included in the Other section.

doc