Missing class unload events over JDI with Java 17

David Holmes david.holmes at oracle.com
Thu Sep 2 13:13:16 UTC 2021


On 2/09/2021 10:05 pm, S A wrote:
> Hi all,
> 
> bisection lead to: 
> https://github.com/openjdk/jdk16/commit/ba721f5f2fbbf08e22a9993dce0087f46b1f5552 
> <https://github.com/openjdk/jdk16/commit/ba721f5f2fbbf08e22a9993dce0087f46b1f5552>
> 
> I've opened: https://bugzilla.redhat.com/show_bug.cgi?id=2000557 
> <https://bugzilla.redhat.com/show_bug.cgi?id=2000557>

private void unloadClasses() throws Exception {
     urlClassLoader.close();
     urlClassLoader = null;
     System.gc();
     System.runFinalization();
}

The above is not sufficient to guarantee class unloading has occurred. 
There are no specified ways to guarantee class unloading. It will depend 
on the GC in use as to what will be sufficient to trigger class unloading.

Cheers,
David

> Best regards,
> Simeon
> 
> On Wed, 1 Sept 2021 at 16:12, S A <simeon.danailov.andreev at gmail.com 
> <mailto:simeon.danailov.andreev at gmail.com>> wrote:
> 
>     Hi all,
> 
>     my team is currently evaluating OpenJDK 17 (the latest early access
>     build) for a part of our application. I've been able to get all our
>     relevant tests to work, except for one that checks class unload
>     events sent over JDI.
> 
>     Originally we added the test due to:
>     https://bugs.openjdk.java.net/browse/JDK-8227269
>     <https://bugs.openjdk.java.net/browse/JDK-8227269> (we wanted to be
>     sure no class unload events are lost despite the fix)
> 
>     The test is Eclipse based (Eclipse has its own JDI implementation)
>     and expects to receive a certain set of class unload events under
>     controlled debugging conditions. The test has been running on
>     OpenJDK 8 and OpenJDK 11 for over an year now, without failures. On
>     OpenJDK 17 however, the test seems to fail nearly always (but not at
>     100% rate).
> 
>     I've created a plain Java snippet and was able to reproduce the
>     problem with jdb, see instructions below.
> 
>     In essence, I see all class unloading is done (I'm checking with "
>     -Xlog:class+load,class+unload") at the expected time. No amount of
>     waiting in the test or in the snippet (e.g. a loop with 50 ms sleeps
>     for several seconds after class unloading is triggered) helps with
>     receiving all expected class unload JDI events though. Sometimes all
>     of them are sent, sometimes none are sent and sometimes only a
>     partial set of events is sent.
> 
>     Could someone knowledgeable take a look?
> 
>     I'll try to find the changes that cause the problem (so far I've not
>     checked whether earlier OpenJDK releases are affected). I'll also
>     open a RHEL support case for this, but those take a while to get in
>     contact with OpenJDK developers.
> 
>     Best regards and thanks,
>     Simeon
> 
> 
>     *Reproduction:*
>     *
>     *
>     First, I've made a change to OpenJDK code to check whether all class
>     unload events are being sent (in case the Eclipse JDI implementation
>     broke for some reason with OpenJDK 17). Its a printf in eventHandler.c :
> 
>     diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.c
>     b/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.c
>     index 2a5912f0777..19a91126e18 100644
>     --- a/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.c
>     +++ b/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.c
>     @@ -500,6 +500,7 @@ synthesizeUnloadEvent(void *signatureVoid, void
>     *envVoid)
>                   char *durableSignature =
>     jvmtiAllocate((int)strlen(signature)+1);
>                   (void)strcpy(durableSignature, signature);
> 
>     +            printf("Sending class unload event for: %s\n",
>     durableSignature);
>                   eventHelper_recordClassUnload(node->handlerID,
>                                                 durableSignature,
>                                                 eventBag);
> 
>     The snippet I use to reproduce the problem loads a few classes, then
>     unloads them. It is:
> 
>     import java.io.File;
>     import java.net.URL;
>     import java.net.URLClassLoader;
> 
>     public class TestClassUnloadEvents {
> 
>     public static void main(String[] args) throws Exception {
>     new TestClassUnloadEvents().execute();
>     }
> 
>     private URLClassLoader urlClassLoader;
> 
>     public void execute() throws Exception {
>              beforeClassLoading();
>              loadClasses();
>              afterClassLoading();
>              unloadClasses();
>              afterClassUnloading();
>     }
> 
>     private void loadClasses() throws Exception {
>         int numberOfClassesToLoad = Integer.valueOf("10");
>         String libraryPath = "classes.jar";
>         URL[] urls = {
>                 new File(libraryPath).toURI().toURL()
>         };
>         urlClassLoader = new URLClassLoader(urls, null);
>         for (int i = 0; i < numberOfClassesToLoad; ++i) {
>             String className = "sample.C" + i;
>             Class<?> loadClass = urlClassLoader.loadClass(className);
>             loadClass.newInstance();
>         }
>     }
> 
>     private void unloadClasses() throws Exception {
>         urlClassLoader.close();
>         urlClassLoader = null;
>         System.gc();
>         System.runFinalization();
>     }
> 
>     public static void beforeClassLoading() {
>         System.out.println("BEFORE CLASS LOADING");
>     }
> 
>     public static void afterClassLoading() {
>         System.out.println("AFTER CLASS LOADING");
>     }
> 
>     public static void afterClassUnloading() {
>         System.out.println("AFTER CLASS UNLOADING");
>     }
>     }
> 
>     Create a jar file from the attached "classes.zip" (I would attach a
>     .jar directly but gmail is blocking jar attachments, even archived
>     ones; sorry about this). Place the "classes.jar" jar file in the
>     same folder as the snippet and from that folder run the snippet. E.g.:
> 
>     /data/git/jdk17/build/linux-x86_64-server-release/jdk/bin/java
>     -Xlog:class+load,class+unload
>     -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8080
>     TestClassUnloadEvents.java
> 
>     Then attach jdb with:
> 
>     /data/git/jdk17/build/linux-x86_64-server-release/jdk/bin/jdb
>     -attach 8080
> 
>     Input "resume", to let the application finish.
> 
>     Expected output that I sometimes see (with Java 11 I always see all
>     expected prints from the change, where event order is not relevant):
> 
>     ...
>     AFTER CLASS LOADING
>     [6.312s][info][class,load] java.io.RandomAccessFile$1 source:
>     /data/git/jdk17/build/linux-x86_64-server-release/jdk/modules/java.base
>     [6.315s][info][class,unload] unloading class sample.C9
>     0x00000008001c5c30
>     [6.315s][info][class,unload] unloading class sample.C8
>     0x00000008001c5a28
>     [6.315s][info][class,unload] unloading class sample.C7
>     0x00000008001c5820
>     [6.315s][info][class,unload] unloading class sample.C6
>     0x00000008001c5618
>     [6.315s][info][class,unload] unloading class sample.C5
>     0x00000008001c5410
>     [6.315s][info][class,unload] unloading class sample.C4
>     0x00000008001c5208
>     [6.315s][info][class,unload] unloading class sample.C3
>     0x00000008001c5000
>     [6.315s][info][class,unload] unloading class sample.C2
>     0x00000008001c4c10
>     [6.315s][info][class,unload] unloading class sample.C1
>     0x00000008001c4a08
>     [6.315s][info][class,unload] unloading class sample.C0
>     0x00000008001c4800
>     [6.325s][info][class,load  ] java.lang.ref.Finalizer$2 source:
>     /data/git/jdk17/build/linux-x86_64-server-release/jdk/modules/java.base
>     Sending class unload event for: Lsample/C9;
>     Sending class unload event for: Lsample/C7;
>     Sending class unload event for: Lsample/C5;
>     Sending class unload event for: Lsample/C3;
>     Sending class unload event for: Lsample/C1;
>     Sending class unload event for: Lsample/C0;
>     Sending class unload event for: Lsample/C8;
>     Sending class unload event for: Lsample/C6;
>     Sending class unload event for: Lsample/C4;
>     Sending class unload event for: Lsample/C2;
>     [6.325s][info][class,load  ] java.lang.ref.Finalizer$1 source:
>     /data/git/jdk17/build/linux-x86_64-server-release/jdk/modules/java.base
>     AFTER CLASS UNLOADING
>     ...
> 
>     Example output that I (often) see, which is unexpected:
> 
>     ...
>     AFTER CLASS LOADING
>     [4.863s][info][class,load] java.io.RandomAccessFile$1 source:
>     /data/git/jdk17/build/linux-x86_64-server-release/jdk/modules/java.base
>     [4.867s][info][class,unload] unloading class sample.C9
>     0x00000008001c5c30
>     [4.867s][info][class,unload] unloading class sample.C8
>     0x00000008001c5a28
>     [4.867s][info][class,unload] unloading class sample.C7
>     0x00000008001c5820
>     [4.867s][info][class,unload] unloading class sample.C6
>     0x00000008001c5618
>     [4.867s][info][class,unload] unloading class sample.C5
>     0x00000008001c5410
>     [4.867s][info][class,unload] unloading class sample.C4
>     0x00000008001c5208
>     [4.867s][info][class,unload] unloading class sample.C3
>     0x00000008001c5000
>     [4.867s][info][class,unload] unloading class sample.C2
>     0x00000008001c4c10
>     [4.867s][info][class,unload] unloading class sample.C1
>     0x00000008001c4a08
>     [4.867s][info][class,unload] unloading class sample.C0
>     0x00000008001c4800
>     [4.876s][info][class,load  ] java.lang.ref.Finalizer$2 source:
>     /data/git/jdk17/build/linux-x86_64-server-release/jdk/modules/java.base
>     Sending class unload event for: Lsample/C9;
>     Sending class unload event for: Lsample/C7;
>     Sending class unload event for: Lsample/C5;
>     Sending class unload event for: Lsample/C3;
>     [4.876s][info][class,load  ] java.lang.ref.Finalizer$1 source:
>     /data/git/jdk17/build/linux-x86_64-server-release/jdk/modules/java.base
>     AFTER CLASS UNLOADING
>     ...
> 


More information about the serviceability-dev mailing list