### Question:
GcListener implements NotificationListener, NotificationFilter { @Override public void handleNotification(Notification notification, Object handback) { Object notificationData = notification.getUserData(); if (!(notificationData instanceof CompositeData)) { return; } CompositeData userData = (CompositeData) notificationData; Map<String, MemoryUsage> afterGc = null; try { @SuppressWarnings("unchecked") Map<String, MemoryUsage> usage = (Map<String, MemoryUsage>) getMemoryUsage.invoke(userData); afterGc = usage; } catch (Throwable throwable) { if (this.reflectionWarningEmitted.compareAndSet(false, true)) { log.warn("Could not convert the notification data into a memory usage map", throwable); } } if (afterGc == null) { return; } long freeAfterGc = 0L; for (String poolName : this.poolNames) { MemoryUsage usageAfterGc = afterGc.get(poolName); if (usageAfterGc != null) { long maxPoolSize = usageAfterGc.getMax(); if (maxPoolSize != -1L) { freeAfterGc += maxPoolSize; } freeAfterGc -= usageAfterGc.getUsed(); } } log.debug("Free pool memory after GC: %d", freeAfterGc); this.freeMemory.set(freeAfterGc); } GcListener(
        Log log,
        AtomicLong freeMemory,
        String[] poolNames,
        String gcNotificationName,
        MethodHandle getMemoryUsage
    ); @Override boolean isNotificationEnabled(Notification notification); @Override void handleNotification(Notification notification, Object handback);  }### Answer:
@Test void testListenToEvent() throws OpenDataException { MemoryUsage heapUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); long expectedFree = heapUsage.getMax() - heapUsage.getUsed(); AtomicLong actualFree = new AtomicLong(-1); NotificationListener listener = new GcListener( NullLog.getInstance(), actualFree, new String[]{"global"}, "42", testResultHandle("global", heapUsage) ); Notification notification = new Notification("42", "1337", 1337); notification.setUserData(compositeData()); listener.handleNotification(notification, null); assertEquals(expectedFree, actualFree.get()); }
@Test void testCombineMultipleUsages() throws OpenDataException { AtomicLong actualFree = new AtomicLong(-1); NotificationListener listener = new GcListener( NullLog.getInstance(), actualFree, new String[]{ "usedWithMax", "usedWithoutMax", "unusedWithMax", "unusedWithoutMax", }, "42", testResultHandle( "usedWithMax", new MemoryUsage(0, 42, 42, 1337), "usedWithoutMax", new MemoryUsage(0, 42, 42, -1), "unusedWithMax", new MemoryUsage(0, 0, 42, 1337), "unusedWithoutMax", new MemoryUsage(0, 0, 42, -1) ) ); Notification notification = new Notification("42", "1337", 1337); notification.setUserData(compositeData()); listener.handleNotification(notification, null); long expectedMax = 1337 + 1337; long expectedUsed = 42 + 42; long expectedFree = expectedMax - expectedUsed; assertEquals(expectedFree, actualFree.get()); }