We dig into the memory of the JVM. Flag Manipulation



HotSpot JVM has many options to keep track of what is happening in the virtual machine: PrintGC , PrintCompilation , TraceClassLoading etc. They usually include the command line parameters, eg . nobr@0> -XX:+PrintGCDetails . However, sometimes it becomes necessary to enable or disable this flag directly during application operation, when restarting the JVM with other parameters is not possible. This can be achieved both by regular and hacker methods, the latter being both more powerful and interesting. However, both deserve attention.

From this article you will learn:

  • where to find all the JVM flags, and what types they are divided into;
  • how to read or set a flag programmatically using JMX ;
  • how to find the desired area in the memory of a virtual machine and mess up modify it.



What are the flags in HotSpot JVM


A list of all flags with explanations is available in the OpenJDK source: the main part in globals.hpp along with additional options for architecture , compiler and G1 collector . /nobr@1> .

As you can see, flags are defined by different macros:
  • product and product_rw flags can be set on the command line with the -XX switch;
  • develop and notproduct are uninteresting because in official releases JDKs are constants;
  • manageable flags are allowed to change at run-time via JMX;
  • experimental are not officially supported (in particular, due to insufficient testing), but may be included at your own risk. To modify these flags, you need to add a command line key UnlockExperimentalVMOptions , for example,
    -XX:+UnlockExperimentalVMOptions -XX:+TrustFinalNonStaticFields
  • diagnostic is not intended to be used except to investigate virtual machine problems. You can only enable them together with UnlockDiagnosticVMOptions , for example,
    -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly


In order to display all the flags available in your version of the JVM, together with their current values, start the JVM with the parameter PrintFlagsFinal :
java -XX:+PrintFlagsFinal


Work with flags through JMX


HotSpot allows you to programmatically read or set the values ​​of some flags through the Management API . Moreover, with Remote Management enabled, this can be done even on a remote server.

First of all, you need to get an instance of MXBean named com.sun.management:type=HotSpotDiagnostic .
import com.sun.management.HotSpotDiagnosticMXBean;
import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
...
    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
    HotSpotDiagnosticMXBean bean = ManagementFactory.newPlatformMXBeanProxy(
            server,
            "com.sun.management:type=HotSpotDiagnostic",
            HotSpotDiagnosticMXBean.class);


The method bean.getVMOption(String option) will let you know the current value of the JVM option,
and bean.setVMOption(String option, String newValue) - set a new one.
If you can read any flag, then only changeable manageable .
The method bean.getDiagnosticOptions() will return a list of all manageable options.

Example:
// Включение флага JVM, отвечающего за вывод ReentrantLock и т.п. в thread dump
bean.setVMOption("PrintConcurrentLocks", "true");


JVM direct memory access


Unfortunately, the set of options modified through JMX is small. But JVM flags are just ordinary variables located in the process address space. If you know the address of a variable, you can write a new value from it through the Unsafe API . It remains to find the address of the JVM flag. The task is not easy, because from launch to launch, the address will change at the will of the operating system. Fortunately, Linux is a very accommodating OS, and will be happy to provide us with all the necessary information, if asked correctly.

  1. First you need to find out where the library of the virtual machine lies libjvm.so , and at what address it is loaded. The proc virtual file system will help , in particular, the file /proc/self/maps that lists all the regions of the virtual address space of the current process. We find in it a line ending in /libjvm.so .
    2b6707956000-2b67084b8000 r-xp 00000000 68:02 1823284 /usr/java/jdk1.7.0_40/jre/lib/amd64/server/libjvm.so

    The first number (0x2b6707956000) will be the base address at which the library is loaded.

    Note that all this can be done in pure Java!
    private String findJvmMaps() throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader("/proc/self/maps"));
        try {
            for (String s; (s = reader.readLine()) != null; ) {
                if (s.endsWith("/libjvm.so")) {
                    return s;
                }
            }
            throw new IOException("libjvm.so not found");
        } finally {
            reader.close();
        }
    }
    

  2. The key moment has come: we will open the file libjvm.so for reading. Using our open-source ELF parser, we find the symbol section in the library, where all the JVM flags are present among the symbols. Again, no hacks, only pure Java.
    ElfReader elfReader = new ElfReader(jvmLibrary);
    ElfSymbolTable symtab = (ElfSymbolTable) elfReader.section(".symtab");
    

  3. Add to the symbol address the base address of the library obtained in step 1, and get the desired location in the memory where the flag values ​​are stored. Now we can easily change it through Unsafe.putInt :
    private ElfSymbol findSymbol(String name) {
        for (ElfSymbol symbol : symtab) {
            if (name.equals(symbol.name()) && symbol.type() == ElfSymbol.STT_OBJECT) {
                return symbol;
            }
        }
        throw new NoSuchElementException("Symbol not found: " + name);
    }
    
    public void setIntFlag(String name, int value) {
        ElfSymbol symbol = findSymbol(name);
        unsafe.putInt(baseAddress + symbol.value(), value);
    }
    
    public void setBooleanFlag(String name, boolean value) {
        setIntFlag(name, value ? 1 : 0);
    }
    



Conclusion


As you can see, in Java, without a single line of native code, you can control the runtime environment, including the virtual machine itself. However, remember that using undocumented methods is risky, and we in no way recommend them for use in production.

The full source code for the experiment is on GitHub .

If you want to know more, come to the Joker Java technology conference to be held October 15 in St. Petersburg. From Odnoklassniki three reports will be presented, including on JVM.