<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, May 24, 2023 at 7:03 AM David Holmes <<a href="mailto:david.holmes@oracle.com">david.holmes@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hi,<br>
<br>
On 23/05/2023 10:25 pm, Jean-Philippe Bempel wrote:<br>
> Hi all,<br>
> <br>
> We have just identified a Metaspace leak in a very specific case when<br>
> a class has a method using a try-with-resources construct (or similar<br>
> with try-catch) and re-transforming this class in a loop. It is<br>
> reproducible from jdk8 to jdk20.<br>
> Here the steps to reproduce:<br>
> <br>
>   1. create a java file with following content:<br>
> <br>
> public class RetransformLeak {<br>
>      public static void main(String[] args) throws Exception {<br>
>          new MyClass();<br>
>          while (true) {<br>
>              Thread.sleep(1000);<br>
>          }<br>
>      }<br>
> }<br>
> <br>
> class MyClass {<br>
>      private static void writeFile() {<br>
<br>
I don't see this ever getting called. Is some code missing?<br>
<br>
>          TWR var0 = new TWR();<br>
>          try {<br>
>              var0.process();<br>
>          } catch (Throwable var4) {<br>
>              try {<br>
>                  var0.close();<br>
>              } catch (Throwable var3) {<br>
>                  var4.addSuppressed(var3);<br>
<br>
If commenting this removes the problem then it seems likely the actual <br>
Throwable being thrown as var3 is holding onto some references which <br>
keeps the old class alive. But as close() does nothing I can't see how <br>
it can throw anything. ??<br>
<br>
>              }<br>
>              throw var4;<br>
>          }<br>
>          var0.close();<br>
> //        try (TWR twr = new TWR()) {<br>
> //            twr.process();<br>
> //        }<br>
>      }<br>
> <br>
>      static class TWR implements AutoCloseable {<br>
>          public void process() {}<br>
>          @Override<br>
>          public void close() {}<br>
>      }<br>
> }<br>
> <br>
> 2. compile it: javac RetransformLeak.java<br>
> 3. create a java file Agent.java with the following content that will<br>
> be our java agent performing re-transformation:<br>
> <br>
> public class Agent {<br>
>      public static void premain(String arg, Instrumentation inst) {<br>
>          new Thread(() -> retransformLoop(inst, arg)).start();<br>
>      }<br>
> <br>
>      private static void retransformLoop(Instrumentation<br>
> instrumentation, String className) {<br>
>          Class<?> classToRetransform = null;<br>
>          while (true) {<br>
>              if (classToRetransform == null) {<br>
>                  for (Class<?> clazz : instrumentation.getAllLoadedClasses()) {<br>
>                      if (clazz.getName().equals(className)) {<br>
>                          System.out.println("found class: " + className);<br>
>                          classToRetransform = clazz;<br>
>                          break;<br>
>                      }<br>
>                  }<br>
>              }<br>
>              if (classToRetransform != null) {<br>
>                  try {<br>
>                      instrumentation.retransformClasses(classToRetransform);<br>
<br>
What ClassfileTransformer is actually in use here?<br>
<br>
>                      //Thread.sleep(1);<br>
>                  } catch (Exception e) {<br>
>                      throw new RuntimeException(e);<br>
>                  }<br>
>              }<br>
>          }<br>
>      }<br>
> }<br>
> <br>
>   4. Compile it: javac Agent.java<br>
>   5. create a Manifest.txt file for the java agent:<br>
> <br>
> Premain-Class: Agent<br>
> Can-Retransform-Classes: true<br>
> <br>
>   6. create java agent jar: jar cfm agent.jar Manifest.txt Agent.class<br>
>   7. execute the RetransformLeak class with a max metaspace size:<br>
> java -javaagent:agent.jar=MyClass -XX:MaxMetaspaceSize=128M -cp .<br>
> RetransformLeak<br>
> <br>
> output:<br>
> found class: MyClass<br>
> Exception in thread "Thread-0" java.lang.OutOfMemoryError<br>
>    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native<br>
> Method)<br>
>    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:169)<br>
>    at Agent.retransformLoop(Agent.java:22)<br>
>    at Agent.lambda$premain$0(Agent.java:5)<br>
>    at java.base/java.lang.Thread.run(Thread.java:1623)<br>
> <br>
> If you comment the line:<br>
>                  var4.addSuppressed(var3);<br>
> in MyClass#writeFile method, no OOME will be thrown and Metaspace will<br>
> remain stable.<br>
<br>
It sounds to me like this isn't a leak as such but rather the exception <br>
keeps things alive. But we need the missing details.<br>
<br></blockquote><div><br></div><div>Yes, that would be helpful.</div><br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
Cheers,<br>
David<br>
<br>
> You can also directly use a try-with-resources construct to reproduce<br>
> the leak but I have decomposed it with try catch to be able to<br>
> pinpoint more precisely which bytecode may generate the leak.<br>
> <br>
> I can file a bug in OpenJDK jira if needed.<br>
> Thanks<br>
> Jean-Philippe Bempel<br></blockquote><div><br></div><div><div><br></div><div>My first thought was that this is not a leak but a case of class redefinition, where the bytecode is rewritten. The space for the old bytecode gets salvaged and would be re-used for metaspace allocations from the same loader, save that said re-use never happens for some reason. The output of "jcmd VM.metaspace" at the end of the program run would be helpful too.<br></div><div> </div></div><div> </div></div></div>