Search code examples
javagarbage-collection

How to detect JVM Garbage Collection cycle from INSIDE the app?


I just wondered if there's a way to detect a Garbage Collection cycle from within the code/JVM that's being gc'd.

Timing does not play a role. So whether it's an event that occurs sometime before the actual cycle or afterwards is not important. (Having an event DURING the cycle seems highly unlikely and is probably dangerous too, depending on the GC implementation used).

All I could find were apps that can be used in parallel to the running JVM, such as jstat: https://dzone.com/articles/how-monitor-java-garbage with the source code here, for example: https://github.com/eagle518/jdk-source-code/blob/master/jdk5.0_src/j2se/src/share/classes/sun/tools/jstat/Jstat.java

All I see is that they use

    MonitoredHost monitoredHost = MonitoredHost.getMonitoredHost(vmId);
    MonitoredVm monitoredVm = monitoredHost.getMonitoredVm(vmId, interval);

So my problem is based on 2 questions. If the second question has a solution, we don't need to answer Q 1:

  1. my knowledge of MonitoredVm is not not good enough to know how to access the local JVM. Usually there's the VM ID required. How could I address that reliably, or is there another way that I can get the 'local' MonitoredVm?
  2. and would it even be necessary to use MonitoredVm, or is there anything a lot easier, like one can 'install' a shutdown hook and detect that event (Runtime.getRuntime().addShutdownHook(hookThread);)? Java Reflection is also a welcome solution!

Solution

  • GarbageCollectorMXBean is pointing into the right direction. What’s not obvious, is that this bean implements NotificationEmitter which allows notifications about garbage collections:

    static volatile Object PREVENT_ESCAPE_ANALYSIS;
    
    public static void main(String[] args) {
        List<GarbageCollectorMXBean> gc = ManagementFactory.getGarbageCollectorMXBeans();
        for(GarbageCollectorMXBean b: gc) {
            ((NotificationEmitter)b).addNotificationListener((n, handback) -> {
                GarbageCollectionNotificationInfo gcni
                    = GarbageCollectionNotificationInfo.from((CompositeData)n.getUserData());
                GcInfo info = gcni.getGcInfo();
                System.out.printf("%tT.%1$tL %dms %s %s %s%n", info.getStartTime(),
                   info.getDuration(),gcni.getGcName(),gcni.getGcAction(),gcni.getGcCause());
                Map<String, MemoryUsage> before = info.getMemoryUsageBeforeGc();
                info.getMemoryUsageAfterGc().forEach((s, u) -> {
                    long bu = before.get(s).getUsed(), au = u.getUsed();
                    if(bu != au) System.out.println("\t" + s + " " + bu + " -> " + au);
                });
            }, null, b);
        }
    
        for(;;) {
            PREVENT_ESCAPE_ANALYSIS = new Object();
        }
    }
    

    Demo on Ideone

    00:00:00.203 2ms Copy end of minor GC Allocation Failure
        Eden Space 70516736 -> 0
        Survivor Space 0 -> 795176
    00:00:00.271 1ms Copy end of minor GC Allocation Failure
        Eden Space 70516736 -> 0
        Survivor Space 795176 -> 862960
    00:00:00.355 2ms Copy end of minor GC Allocation Failure
        Eden Space 70516736 -> 0
        Survivor Space 862960 -> 931952
    00:00:00.412 2ms Copy end of minor GC Allocation Failure
        Eden Space 70516736 -> 0
        Survivor Space 931952 -> 1004784
    00:00:00.497 2ms Copy end of minor GC Allocation Failure
        Eden Space 70516736 -> 0
        Survivor Space 1004784 -> 1379400
    00:00:00.574 3ms Copy end of minor GC Allocation Failure
        Eden Space 70516736 -> 0
        Survivor Space 1379400 -> 1551944
    00:00:00.632 2ms Copy end of minor GC Allocation Failure
        Eden Space 70516736 -> 0
        Survivor Space 1551944 -> 1541144
    …