Metaspace leak with instrumentation.retransform

Jean-Philippe Bempel jean-philippe.bempel at datadoghq.com
Tue May 23 12:25:23 UTC 2023


Hi all,

We have just identified a Metaspace leak in a very specific case when
a class has a method using a try-with-resources construct (or similar
with try-catch) and re-transforming this class in a loop. It is
reproducible from jdk8 to jdk20.
Here the steps to reproduce:

 1. create a java file with following content:

public class RetransformLeak {
    public static void main(String[] args) throws Exception {
        new MyClass();
        while (true) {
            Thread.sleep(1000);
        }
    }
}

class MyClass {
    private static void writeFile() {
        TWR var0 = new TWR();
        try {
            var0.process();
        } catch (Throwable var4) {
            try {
                var0.close();
            } catch (Throwable var3) {
                var4.addSuppressed(var3);
            }
            throw var4;
        }
        var0.close();
//        try (TWR twr = new TWR()) {
//            twr.process();
//        }
    }

    static class TWR implements AutoCloseable {
        public void process() {}
        @Override
        public void close() {}
    }
}

2. compile it: javac RetransformLeak.java
3. create a java file Agent.java with the following content that will
be our java agent performing re-transformation:

public class Agent {
    public static void premain(String arg, Instrumentation inst) {
        new Thread(() -> retransformLoop(inst, arg)).start();
    }

    private static void retransformLoop(Instrumentation
instrumentation, String className) {
        Class<?> classToRetransform = null;
        while (true) {
            if (classToRetransform == null) {
                for (Class<?> clazz : instrumentation.getAllLoadedClasses()) {
                    if (clazz.getName().equals(className)) {
                        System.out.println("found class: " + className);
                        classToRetransform = clazz;
                        break;
                    }
                }
            }
            if (classToRetransform != null) {
                try {
                    instrumentation.retransformClasses(classToRetransform);
                    //Thread.sleep(1);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

 4. Compile it: javac Agent.java
 5. create a Manifest.txt file for the java agent:

Premain-Class: Agent
Can-Retransform-Classes: true

 6. create java agent jar: jar cfm agent.jar Manifest.txt Agent.class
 7. execute the RetransformLeak class with a max metaspace size:
java -javaagent:agent.jar=MyClass -XX:MaxMetaspaceSize=128M -cp .
RetransformLeak

output:
found class: MyClass
Exception in thread "Thread-0" java.lang.OutOfMemoryError
  at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native
Method)
  at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:169)
  at Agent.retransformLoop(Agent.java:22)
  at Agent.lambda$premain$0(Agent.java:5)
  at java.base/java.lang.Thread.run(Thread.java:1623)

If you comment the line:
                var4.addSuppressed(var3);
in MyClass#writeFile method, no OOME will be thrown and Metaspace will
remain stable.
You can also directly use a try-with-resources construct to reproduce
the leak but I have decomposed it with try catch to be able to
pinpoint more precisely which bytecode may generate the leak.

I can file a bug in OpenJDK jira if needed.
Thanks
Jean-Philippe Bempel


More information about the hotspot-dev mailing list