Metaspace leak with instrumentation.retransform

David Holmes david.holmes at oracle.com
Wed May 24 05:02:37 UTC 2023


Hi,

On 23/05/2023 10:25 pm, Jean-Philippe Bempel wrote:
> 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() {

I don't see this ever getting called. Is some code missing?

>          TWR var0 = new TWR();
>          try {
>              var0.process();
>          } catch (Throwable var4) {
>              try {
>                  var0.close();
>              } catch (Throwable var3) {
>                  var4.addSuppressed(var3);

If commenting this removes the problem then it seems likely the actual 
Throwable being thrown as var3 is holding onto some references which 
keeps the old class alive. But as close() does nothing I can't see how 
it can throw anything. ??

>              }
>              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);

What ClassfileTransformer is actually in use here?

>                      //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.

It sounds to me like this isn't a leak as such but rather the exception 
keeps things alive. But we need the missing details.

Cheers,
David

> 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