diff --git a/.gradle/8.8/checksums/checksums.lock b/.gradle/8.8/checksums/checksums.lock new file mode 100644 index 0000000..8366b71 Binary files /dev/null and b/.gradle/8.8/checksums/checksums.lock differ diff --git a/.gradle/8.8/checksums/md5-checksums.bin b/.gradle/8.8/checksums/md5-checksums.bin new file mode 100644 index 0000000..1eb722e Binary files /dev/null and b/.gradle/8.8/checksums/md5-checksums.bin differ diff --git a/.gradle/8.8/checksums/sha1-checksums.bin b/.gradle/8.8/checksums/sha1-checksums.bin new file mode 100644 index 0000000..ef7c9ab Binary files /dev/null and b/.gradle/8.8/checksums/sha1-checksums.bin differ diff --git a/.gradle/8.8/dependencies-accessors/gc.properties b/.gradle/8.8/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/8.8/executionHistory/executionHistory.bin b/.gradle/8.8/executionHistory/executionHistory.bin new file mode 100644 index 0000000..c67430e Binary files /dev/null and b/.gradle/8.8/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.8/executionHistory/executionHistory.lock b/.gradle/8.8/executionHistory/executionHistory.lock new file mode 100644 index 0000000..a44f868 Binary files /dev/null and b/.gradle/8.8/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.8/fileChanges/last-build.bin b/.gradle/8.8/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/8.8/fileChanges/last-build.bin differ diff --git a/.gradle/8.8/fileHashes/fileHashes.bin b/.gradle/8.8/fileHashes/fileHashes.bin new file mode 100644 index 0000000..b9d646b Binary files /dev/null and b/.gradle/8.8/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.8/fileHashes/fileHashes.lock b/.gradle/8.8/fileHashes/fileHashes.lock new file mode 100644 index 0000000..66fbee1 Binary files /dev/null and b/.gradle/8.8/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.8/fileHashes/resourceHashesCache.bin b/.gradle/8.8/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..8be149f Binary files /dev/null and b/.gradle/8.8/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/8.8/gc.properties b/.gradle/8.8/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..9c8137c Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..7c1868f --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Sun Jun 14 15:45:41 CEST 2026 +gradle.version=8.8 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..ac1964e Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe new file mode 100644 index 0000000..d9ff95e Binary files /dev/null and b/.gradle/file-system.probe differ diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.tmp/jmb-all.txt b/.tmp/jmb-all.txt new file mode 100644 index 0000000..2266e56 --- /dev/null +++ b/.tmp/jmb-all.txt @@ -0,0 +1,195 @@ +> Task :compileJava UP-TO-DATE +> Task :processResources NO-SOURCE +> Task :classes UP-TO-DATE +> Task :compileTestJava UP-TO-DATE +> Task :processTestResources NO-SOURCE +> Task :testClasses UP-TO-DATE + +> Task :test + +TestCleanupDetector > isRunnableCalled() PASSED + +TestCleanupDetector > isPhantomRefCollectable() PASSED + +TestJMemoryBuddy > testCreateHeapDump() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-23.hprof + +TestJMemoryBuddy > testCreateHeapDump() PASSED + +TestJMemoryBuddy > simpleTest() PASSED + +TestJMemoryBuddy > simpleTest2() PASSED + +TestJMemoryBuddy > simpleTest3() PASSED + +TestJMemoryBuddy > multiHopPath() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-25.hprof + + JMemoryBuddy — path to GC root for leak: Object @7e016b900: + GC root [Java frame] TestJMemoryBuddy$Holder + ↓ TestJMemoryBuddy$Holder.field + Object ← leaked + + multiHopPath message: The following references should be collected: [java.lang.Object@67d18ed7] + +TestJMemoryBuddy > multiHopPath() PASSED + +TestJMemoryBuddy > negativeTest2() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-25.hprof + +TestJMemoryBuddy > negativeTest2() STANDARD_ERROR + java.io.IOException: File exists + at jdk.management/com.sun.management.internal.HotSpotDiagnostic.dumpHeap0(Native Method) + at jdk.management/com.sun.management.internal.HotSpotDiagnostic.dumpHeap(HotSpotDiagnostic.java:70) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:64) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at java.base/sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:97) + at java.management/com.sun.jmx.mbeanserver.ConvertingMethod.invokeWithOpenReturn(ConvertingMethod.java:193) + at java.management/com.sun.jmx.mbeanserver.ConvertingMethod.invokeWithOpenReturn(ConvertingMethod.java:175) + at java.management/com.sun.jmx.mbeanserver.MXBeanIntrospector.invokeM2(MXBeanIntrospector.java:115) + at java.management/com.sun.jmx.mbeanserver.MXBeanIntrospector.invokeM2(MXBeanIntrospector.java:52) + at java.management/com.sun.jmx.mbeanserver.MBeanIntrospector.invokeM(MBeanIntrospector.java:236) + at java.management/com.sun.jmx.mbeanserver.PerInterface.invoke(PerInterface.java:138) + at java.management/com.sun.jmx.mbeanserver.MBeanSupport.invoke(MBeanSupport.java:252) + at java.management/javax.management.StandardMBean.invoke(StandardMBean.java:405) + at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:803) + at java.management/com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:802) + at java.management/com.sun.jmx.mbeanserver.MXBeanProxy$InvokeHandler.invoke(MXBeanProxy.java:150) + at java.management/com.sun.jmx.mbeanserver.MXBeanProxy.invoke(MXBeanProxy.java:167) + at java.management/javax.management.MBeanServerInvocationHandler.invoke(MBeanServerInvocationHandler.java:258) + at jdk.proxy2/jdk.proxy2.$Proxy12.dumpHeap(Unknown Source) + at one.jpro.jmemorybuddy.JMemoryBuddy.createHeapDump(JMemoryBuddy.java:212) + at one.jpro.jmemorybuddy.JMemoryBuddy.memoryTest(JMemoryBuddy.java:193) + at one.jpro.jmemorybuddy.TestJMemoryBuddy.negativeTest2(TestJMemoryBuddy.java:90) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:767) + at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) + at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156) + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147) + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86) + at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103) + at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) + at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92) + at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$8(TestMethodTestDescriptor.java:217) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:156) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) + at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:119) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:94) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:89) + at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) + at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:119) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:66) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) + +TestJMemoryBuddy > negativeTest2() STANDARD_OUT + The following references should not be collected: [[someText]] + +TestJMemoryBuddy > negativeTest2() PASSED + +TestJMemoryBuddy > testErrorMessages1() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-26.hprof + JMemoryBuddy: no tracked references found in the dump; cannot compute a path to the GC root. + +TestJMemoryBuddy > testErrorMessages1() PASSED + +TestJMemoryBuddy > testErrorMessages2() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-27.hprof + + JMemoryBuddy — path to GC root for leak: Object @7e0175e48: + GC root [Java frame] Object + + +TestJMemoryBuddy > testErrorMessages2() PASSED + +TestJMemoryBuddy > clearReferenceOnFinalization() STANDARD_OUT + Finalized! + +TestJMemoryBuddy > clearReferenceOnFinalization() PASSED + +TestJMemoryBuddy > negativeTest() STANDARD_OUT + Warning test seems to be unstable. time used: 100% + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-28.hprof + + JMemoryBuddy — path to GC root for leak: String @7e016bb00: + GC root [Java frame] String + + The following references should be collected: [someText] + +TestJMemoryBuddy > negativeTest() PASSED + +TestJMemoryBuddy > simpleTestRepeated() PASSED + +TestJMemoryBuddyLive > testMarkKeepsGCBehaviour() PASSED + +Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0. + +You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins. + +For more on this, please refer to https://docs.gradle.org/8.8/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation. + +BUILD SUCCESSFUL in 7s +3 actionable tasks: 1 executed, 2 up-to-date diff --git a/.tmp/jmb-compile.txt b/.tmp/jmb-compile.txt new file mode 100644 index 0000000..772b68d --- /dev/null +++ b/.tmp/jmb-compile.txt @@ -0,0 +1,22 @@ +Downloading https://services.gradle.org/distributions/gradle-8.8-bin.zip +.............10%.............20%.............30%.............40%.............50%.............60%..............70%.............80%.............90%.............100% +WARNING: A restricted method in java.lang.System has been called +WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/floriankirmaier/.gradle/wrapper/dists/gradle-8.8-bin/dl7vupf4psengwqhwktix4v1/gradle-8.8/lib/native-platform-0.22-milestone-26.jar) +WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module +WARNING: Restricted methods will be blocked in a future release unless native access is enabled + +Starting a Gradle Daemon (subsequent builds will be faster) + +FAILURE: Build failed with an exception. + +* What went wrong: +BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 69 +> Unsupported class file major version 69 + +* Try: +> Run with --stacktrace option to get the stack trace. +> Run with --info or --debug option to get more log output. +> Run with --scan to get full insights. +> Get more help at https://help.gradle.org. + +BUILD FAILED in 26s diff --git a/.tmp/jmb-multi.txt b/.tmp/jmb-multi.txt new file mode 100644 index 0000000..3415f0d --- /dev/null +++ b/.tmp/jmb-multi.txt @@ -0,0 +1,38 @@ +> Task :compileJava UP-TO-DATE +> Task :processResources NO-SOURCE +> Task :classes UP-TO-DATE + +> Task :compileTestJava +/Users/floriankirmaier/projects/claude/JMemoryBuddy/src/test/java/one/jpro/jmemorybuddy/TestJMemoryBuddy.java:110: warning: [removal] finalize() in Object has been deprecated and marked for removal + protected void finalize() { + ^ +/Users/floriankirmaier/projects/claude/JMemoryBuddy/src/test/java/one/jpro/jmemorybuddy/TestJMemoryBuddy.java:160: warning: [removal] finalize() in Object has been deprecated and marked for removal + protected void finalize() { + ^ +2 warnings + +> Task :processTestResources NO-SOURCE +> Task :testClasses + +> Task :test + +TestJMemoryBuddy > multiHopPath() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-07.hprof + + JMemoryBuddy — path to GC root for leak: Object @58a903060: + GC root [Java frame] TestJMemoryBuddy$Holder + ↓ TestJMemoryBuddy$Holder.field + Object ← leaked + + multiHopPath message: The following references should be collected: [java.lang.Object@3bd323e9] + +TestJMemoryBuddy > multiHopPath() PASSED + +Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0. + +You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins. + +For more on this, please refer to https://docs.gradle.org/8.8/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation. + +BUILD SUCCESSFUL in 2s +3 actionable tasks: 2 executed, 1 up-to-date diff --git a/.tmp/jmb-test.txt b/.tmp/jmb-test.txt new file mode 100644 index 0000000..8ea10dc --- /dev/null +++ b/.tmp/jmb-test.txt @@ -0,0 +1,90 @@ +Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details + +> Task :compileJava +/Users/floriankirmaier/projects/claude/JMemoryBuddy/src/main/java/one/jpro/jmemorybuddy/JMemoryBuddy.java:92: warning: [removal] runFinalization() in System has been deprecated and marked for removal + System.runFinalization(); + ^ +/Users/floriankirmaier/projects/claude/JMemoryBuddy/src/main/java/one/jpro/jmemorybuddy/JMemoryBuddy.java:104: warning: [removal] runFinalization() in System has been deprecated and marked for removal + System.runFinalization(); + ^ +/Users/floriankirmaier/projects/claude/JMemoryBuddy/src/main/java/one/jpro/jmemorybuddy/JMemoryBuddy.java:133: warning: [removal] runFinalization() in System has been deprecated and marked for removal + System.runFinalization(); + ^ +3 warnings + +> Task :processResources NO-SOURCE +> Task :classes + +> Task :compileTestJava +/Users/floriankirmaier/projects/claude/JMemoryBuddy/src/test/java/one/jpro/jmemorybuddy/TestJMemoryBuddy.java:110: warning: [removal] finalize() in Object has been deprecated and marked for removal + protected void finalize() { + ^ +/Users/floriankirmaier/projects/claude/JMemoryBuddy/src/test/java/one/jpro/jmemorybuddy/TestJMemoryBuddy.java:160: warning: [removal] finalize() in Object has been deprecated and marked for removal + protected void finalize() { + ^ +2 warnings + +> Task :processTestResources NO-SOURCE +> Task :testClasses + +> Task :test + +TestJMemoryBuddy > testCreateHeapDump() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-46-20.hprof + +TestJMemoryBuddy > testCreateHeapDump() PASSED + +TestJMemoryBuddy > simpleTest() PASSED + +TestJMemoryBuddy > simpleTest2() PASSED + +TestJMemoryBuddy > simpleTest3() PASSED + +TestJMemoryBuddy > negativeTest2() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-46-21.hprof + JMemoryBuddy: no tracked references found in the dump; cannot compute a path to the GC root. + The following references should not be collected: [[someText]] + +TestJMemoryBuddy > negativeTest2() PASSED + +TestJMemoryBuddy > testErrorMessages1() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-46-22.hprof + JMemoryBuddy: no tracked references found in the dump; cannot compute a path to the GC root. + +TestJMemoryBuddy > testErrorMessages1() PASSED + +TestJMemoryBuddy > testErrorMessages2() STANDARD_OUT + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-46-23.hprof + + JMemoryBuddy — path to GC root for leak: Object @58aa4a348: + GC root [Java frame] Object + + +TestJMemoryBuddy > testErrorMessages2() PASSED + +TestJMemoryBuddy > clearReferenceOnFinalization() STANDARD_OUT + Finalized! + +TestJMemoryBuddy > clearReferenceOnFinalization() PASSED + +TestJMemoryBuddy > negativeTest() STANDARD_OUT + Warning test seems to be unstable. time used: 100% + Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-46-24.hprof + + JMemoryBuddy — path to GC root for leak: String @58ab06948: + GC root [Java frame] String + + The following references should be collected: [someText] + +TestJMemoryBuddy > negativeTest() PASSED + +TestJMemoryBuddy > simpleTestRepeated() PASSED + +Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0. + +You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins. + +For more on this, please refer to https://docs.gradle.org/8.8/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation. + +BUILD SUCCESSFUL in 13s +3 actionable tasks: 3 executed diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c57ab2..4da61c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ### 0.5.7 (TBD) +* On a failed leak check, JMemoryBuddy now reads back the heap dump it creates and prints the strong-reference path from a GC root to each not-collected object — no need to open VisualVM/MAT for the common case. Dependency-free; disable with `-Djmemorybuddy.printPath=false`. ### 0.5.6 (October 29, 2024) diff --git a/README.md b/README.md index 7dbd395..d82a9e2 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,17 @@ You can also use the method `assertCollectable` and `assertNotCollectable` to ch #### Analyzing the heap dump: -JMemoryBuddy makes it easy to analyze the heap dump because all problematic instances are wrapped inside a class with the name `AssertCollectable`. Just search your heap dump with your prefered tool for this class name: +On a failed leak check JMemoryBuddy reads back the heap dump and prints the strong-reference path from a GC root to each not-collected object directly in the test output — usually enough to find the cause without opening any tool: + +``` +JMemoryBuddy — path to GC root for leak: PageDoc @58a903060: + GC root [Java frame] DataRepository + ↓ DataRepository.listeners + ... + PageDoc ← leaked +``` + +Disable with `-Djmemorybuddy.printPath=false`. For a deeper look, open the dump in your preferred tool — all watched instances are referenced by a `TrackedWeakReference`, so you can search for that class name: ![visualvm](/screenshot-visualvm.png) @@ -74,6 +84,8 @@ You can configure VisualVM with SystemProperties: | Tables | Effect | Default | | ------------- |:-------------:| -----:| | -Djmemorybuddy.createHeapDump | Should a heap dump created on failure? | true | +| -Djmemorybuddy.printPath | Print the path from a GC root to each not-collected object on failure. | true | +| -Djmemorybuddy.maxObjects | Skip the path analysis when the dump has more objects than this (avoids heavy in-process analysis). | 8000000 | | -Djmemorybuddy.output | The folder where the heap dump gets saved. | if target exists, then "target" otherwise "build" | The following values usually shouldn't be changed but might be useful to make tests more stable or reduce the time required. diff --git a/build/classes/java/main/module-info.class b/build/classes/java/main/module-info.class new file mode 100644 index 0000000..18d89e1 Binary files /dev/null and b/build/classes/java/main/module-info.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/CleanupDetector$WeakReferenceWithRunnable.class b/build/classes/java/main/one/jpro/jmemorybuddy/CleanupDetector$WeakReferenceWithRunnable.class new file mode 100644 index 0000000..f0b5e96 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/CleanupDetector$WeakReferenceWithRunnable.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/CleanupDetector.class b/build/classes/java/main/one/jpro/jmemorybuddy/CleanupDetector.class new file mode 100644 index 0000000..1ad50e3 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/CleanupDetector.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/HeapPath$ClassInfo.class b/build/classes/java/main/one/jpro/jmemorybuddy/HeapPath$ClassInfo.class new file mode 100644 index 0000000..cffb3b6 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/HeapPath$ClassInfo.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/HeapPath$Counter.class b/build/classes/java/main/one/jpro/jmemorybuddy/HeapPath$Counter.class new file mode 100644 index 0000000..3c3c522 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/HeapPath$Counter.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/HeapPath.class b/build/classes/java/main/one/jpro/jmemorybuddy/HeapPath.class new file mode 100644 index 0000000..fb8eec0 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/HeapPath.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$1.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$1.class new file mode 100644 index 0000000..6738fb1 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$1.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$AssertCollectable.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$AssertCollectable.class new file mode 100644 index 0000000..4fe498d Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$AssertCollectable.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$AssertNotCollectable.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$AssertNotCollectable.class new file mode 100644 index 0000000..03a1f50 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$AssertNotCollectable.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$MemoryTestAPI.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$MemoryTestAPI.class new file mode 100644 index 0000000..cb10481 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$MemoryTestAPI.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$SetAsReferenced.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$SetAsReferenced.class new file mode 100644 index 0000000..a95b47d Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy$SetAsReferenced.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy.class new file mode 100644 index 0000000..bb103e2 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddy.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive$AssertCollectableLive.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive$AssertCollectableLive.class new file mode 100644 index 0000000..fd49238 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive$AssertCollectableLive.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive$CollectableEntry.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive$CollectableEntry.class new file mode 100644 index 0000000..112f9f4 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive$CollectableEntry.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive$Report.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive$Report.class new file mode 100644 index 0000000..24fbc7b Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive$Report.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive.class b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive.class new file mode 100644 index 0000000..c8b1863 Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/JMemoryBuddyLive.class differ diff --git a/build/classes/java/main/one/jpro/jmemorybuddy/TrackedWeakReference.class b/build/classes/java/main/one/jpro/jmemorybuddy/TrackedWeakReference.class new file mode 100644 index 0000000..8505e8c Binary files /dev/null and b/build/classes/java/main/one/jpro/jmemorybuddy/TrackedWeakReference.class differ diff --git a/build/classes/java/test/one/jpro/jmemorybuddy/TestCleanupDetector.class b/build/classes/java/test/one/jpro/jmemorybuddy/TestCleanupDetector.class new file mode 100644 index 0000000..78a8af4 Binary files /dev/null and b/build/classes/java/test/one/jpro/jmemorybuddy/TestCleanupDetector.class differ diff --git a/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$1.class b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$1.class new file mode 100644 index 0000000..102bc59 Binary files /dev/null and b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$1.class differ diff --git a/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$2.class b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$2.class new file mode 100644 index 0000000..3982e45 Binary files /dev/null and b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$2.class differ diff --git a/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$A.class b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$A.class new file mode 100644 index 0000000..a4f57b5 Binary files /dev/null and b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$A.class differ diff --git a/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$Holder.class b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$Holder.class new file mode 100644 index 0000000..57612ba Binary files /dev/null and b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy$Holder.class differ diff --git a/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy.class b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy.class new file mode 100644 index 0000000..df9766d Binary files /dev/null and b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddy.class differ diff --git a/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddyLive.class b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddyLive.class new file mode 100644 index 0000000..f058274 Binary files /dev/null and b/build/classes/java/test/one/jpro/jmemorybuddy/TestJMemoryBuddyLive.class differ diff --git a/build/heapdump_jmemb_2026-06-14_15-46-20.hprof b/build/heapdump_jmemb_2026-06-14_15-46-20.hprof new file mode 100644 index 0000000..815ffb8 Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-46-20.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-46-21.hprof b/build/heapdump_jmemb_2026-06-14_15-46-21.hprof new file mode 100644 index 0000000..ee1b1e4 Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-46-21.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-46-22.hprof b/build/heapdump_jmemb_2026-06-14_15-46-22.hprof new file mode 100644 index 0000000..d05a5f5 Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-46-22.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-46-23.hprof b/build/heapdump_jmemb_2026-06-14_15-46-23.hprof new file mode 100644 index 0000000..6917190 Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-46-23.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-46-24.hprof b/build/heapdump_jmemb_2026-06-14_15-46-24.hprof new file mode 100644 index 0000000..69f5286 Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-46-24.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-47-07.hprof b/build/heapdump_jmemb_2026-06-14_15-47-07.hprof new file mode 100644 index 0000000..dbf9965 Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-47-07.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-47-23.hprof b/build/heapdump_jmemb_2026-06-14_15-47-23.hprof new file mode 100644 index 0000000..0d2c1b9 Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-47-23.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-47-25.hprof b/build/heapdump_jmemb_2026-06-14_15-47-25.hprof new file mode 100644 index 0000000..bec9746 Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-47-25.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-47-26.hprof b/build/heapdump_jmemb_2026-06-14_15-47-26.hprof new file mode 100644 index 0000000..2c0271a Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-47-26.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-47-27.hprof b/build/heapdump_jmemb_2026-06-14_15-47-27.hprof new file mode 100644 index 0000000..8fc95a4 Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-47-27.hprof differ diff --git a/build/heapdump_jmemb_2026-06-14_15-47-28.hprof b/build/heapdump_jmemb_2026-06-14_15-47-28.hprof new file mode 100644 index 0000000..b3916fa Binary files /dev/null and b/build/heapdump_jmemb_2026-06-14_15-47-28.hprof differ diff --git a/build/reports/tests/test/classes/one.jpro.jmemorybuddy.TestCleanupDetector.html b/build/reports/tests/test/classes/one.jpro.jmemorybuddy.TestCleanupDetector.html new file mode 100644 index 0000000..9d1bb3d --- /dev/null +++ b/build/reports/tests/test/classes/one.jpro.jmemorybuddy.TestCleanupDetector.html @@ -0,0 +1,101 @@ + + + + + +Test results - TestCleanupDetector + + + + + +
+

TestCleanupDetector

+ +
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
1.138s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + + + + + + +
TestDurationResult
isPhantomRefCollectable()1.121spassed
isRunnableCalled()0.017spassed
+
+
+ +
+ + diff --git a/build/reports/tests/test/classes/one.jpro.jmemorybuddy.TestJMemoryBuddy.html b/build/reports/tests/test/classes/one.jpro.jmemorybuddy.TestJMemoryBuddy.html new file mode 100644 index 0000000..1094cec --- /dev/null +++ b/build/reports/tests/test/classes/one.jpro.jmemorybuddy.TestJMemoryBuddy.html @@ -0,0 +1,296 @@ + + + + + +Test results - TestJMemoryBuddy + + + + + +
+

TestJMemoryBuddy

+ +
+ + + + + +
+
+ + + + + + + +
+
+
11
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
5.555s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TestDurationResult
clearReferenceOnFinalization()0.118spassed
multiHopPath()1.302spassed
negativeTest()1.273spassed
negativeTest2()0.017spassed
simpleTest()0.006spassed
simpleTest2()0.016spassed
simpleTest3()0.764spassed
simpleTestRepeated()0.524spassed
testCreateHeapDump()0.087spassed
testErrorMessages1()0.164spassed
testErrorMessages2()1.284spassed
+
+
+

Standard output

+ +
Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-23.hprof
+Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-25.hprof
+
+JMemoryBuddy — path to GC root for leak: Object @7e016b900:
+  GC root [Java frame] TestJMemoryBuddy$Holder
+    ↓ TestJMemoryBuddy$Holder.field
+  Object   ← leaked
+
+multiHopPath message: The following references should be collected: [java.lang.Object@67d18ed7]
+Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-25.hprof
+The following references should not be collected: [[someText]]
+Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-26.hprof
+JMemoryBuddy: no tracked references found in the dump; cannot compute a path to the GC root.
+Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-27.hprof
+
+JMemoryBuddy — path to GC root for leak: Object @7e0175e48:
+  GC root [Java frame] Object
+
+Finalized!
+Warning test seems to be unstable. time used: 100%
+Creating Heapdump at: /Users/floriankirmaier/projects/claude/JMemoryBuddy/build/heapdump_jmemb_2026-06-14_15-47-28.hprof
+
+JMemoryBuddy — path to GC root for leak: String @7e016bb00:
+  GC root [Java frame] String
+
+The following references should be collected: [someText]
+
+
+
+
+

Standard error

+ +
java.io.IOException: File exists
+	at jdk.management/com.sun.management.internal.HotSpotDiagnostic.dumpHeap0(Native Method)
+	at jdk.management/com.sun.management.internal.HotSpotDiagnostic.dumpHeap(HotSpotDiagnostic.java:70)
+	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+	at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:64)
+	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+	at java.base/sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:97)
+	at java.management/com.sun.jmx.mbeanserver.ConvertingMethod.invokeWithOpenReturn(ConvertingMethod.java:193)
+	at java.management/com.sun.jmx.mbeanserver.ConvertingMethod.invokeWithOpenReturn(ConvertingMethod.java:175)
+	at java.management/com.sun.jmx.mbeanserver.MXBeanIntrospector.invokeM2(MXBeanIntrospector.java:115)
+	at java.management/com.sun.jmx.mbeanserver.MXBeanIntrospector.invokeM2(MXBeanIntrospector.java:52)
+	at java.management/com.sun.jmx.mbeanserver.MBeanIntrospector.invokeM(MBeanIntrospector.java:236)
+	at java.management/com.sun.jmx.mbeanserver.PerInterface.invoke(PerInterface.java:138)
+	at java.management/com.sun.jmx.mbeanserver.MBeanSupport.invoke(MBeanSupport.java:252)
+	at java.management/javax.management.StandardMBean.invoke(StandardMBean.java:405)
+	at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:803)
+	at java.management/com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:802)
+	at java.management/com.sun.jmx.mbeanserver.MXBeanProxy$InvokeHandler.invoke(MXBeanProxy.java:150)
+	at java.management/com.sun.jmx.mbeanserver.MXBeanProxy.invoke(MXBeanProxy.java:167)
+	at java.management/javax.management.MBeanServerInvocationHandler.invoke(MBeanServerInvocationHandler.java:258)
+	at jdk.proxy2/jdk.proxy2.$Proxy12.dumpHeap(Unknown Source)
+	at one.jpro.jmemorybuddy.JMemoryBuddy.createHeapDump(JMemoryBuddy.java:212)
+	at one.jpro.jmemorybuddy.JMemoryBuddy.memoryTest(JMemoryBuddy.java:193)
+	at one.jpro.jmemorybuddy.TestJMemoryBuddy.negativeTest2(TestJMemoryBuddy.java:90)
+	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:767)
+	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
+	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
+	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
+	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
+	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
+	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
+	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
+	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$8(TestMethodTestDescriptor.java:217)
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:156)
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
+	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
+	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
+	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160)
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
+	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
+	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
+	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160)
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146)
+	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144)
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143)
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
+	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
+	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
+	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
+	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
+	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
+	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
+	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
+	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
+	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
+	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
+	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
+	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:119)
+	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:94)
+	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:89)
+	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62)
+	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
+	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
+	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
+	at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source)
+	at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
+	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
+	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
+	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
+	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
+	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:119)
+	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:66)
+	at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
+	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
+
+
+
+
+ +
+ + diff --git a/build/reports/tests/test/classes/one.jpro.jmemorybuddy.TestJMemoryBuddyLive.html b/build/reports/tests/test/classes/one.jpro.jmemorybuddy.TestJMemoryBuddyLive.html new file mode 100644 index 0000000..7f823fd --- /dev/null +++ b/build/reports/tests/test/classes/one.jpro.jmemorybuddy.TestJMemoryBuddyLive.html @@ -0,0 +1,96 @@ + + + + + +Test results - TestJMemoryBuddyLive + + + + + +
+

TestJMemoryBuddyLive

+ +
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.060s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + +
TestDurationResult
testMarkKeepsGCBehaviour()0.060spassed
+
+
+ +
+ + diff --git a/build/reports/tests/test/css/base-style.css b/build/reports/tests/test/css/base-style.css new file mode 100644 index 0000000..4afa73e --- /dev/null +++ b/build/reports/tests/test/css/base-style.css @@ -0,0 +1,179 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding-left: 50px; + padding-right: 50px; + padding-top: 30px; + padding-bottom: 30px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +ul.tabLinks { + padding-left: 0; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; + min-width: 800px; + width: auto !important; + width: 800px; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + -moz-border-radius: 7px; + border-radius: 7px; + margin-right: 25px; + border: solid 1px #d4d4d4; + background-color: #f0f0f0; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #c5f0f5; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto !important; + width: 350px; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid #d0d0d0 1px; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0em; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto !important; + width: 700px; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} \ No newline at end of file diff --git a/build/reports/tests/test/css/style.css b/build/reports/tests/test/css/style.css new file mode 100644 index 0000000..3dc4913 --- /dev/null +++ b/build/reports/tests/test/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/build/reports/tests/test/index.html b/build/reports/tests/test/index.html new file mode 100644 index 0000000..8b5cc58 --- /dev/null +++ b/build/reports/tests/test/index.html @@ -0,0 +1,153 @@ + + + + + +Test results - Test Summary + + + + + +
+

Test Summary

+
+ + + + + +
+
+ + + + + + + +
+
+
14
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
6.753s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Packages

+ + + + + + + + + + + + + + + + + + + + + +
PackageTestsFailuresIgnoredDurationSuccess rate
+one.jpro.jmemorybuddy +14006.753s100%
+
+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+one.jpro.jmemorybuddy.TestCleanupDetector +2001.138s100%
+one.jpro.jmemorybuddy.TestJMemoryBuddy +11005.555s100%
+one.jpro.jmemorybuddy.TestJMemoryBuddyLive +1000.060s100%
+
+
+ +
+ + diff --git a/build/reports/tests/test/js/report.js b/build/reports/tests/test/js/report.js new file mode 100644 index 0000000..83bab4a --- /dev/null +++ b/build/reports/tests/test/js/report.js @@ -0,0 +1,194 @@ +(function (window, document) { + "use strict"; + + var tabs = {}; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function initTabs() { + var container = document.getElementById("tabs"); + + tabs.tabs = findTabs(container); + tabs.titles = findTitles(tabs.tabs); + tabs.headers = findHeaders(container); + tabs.select = select; + tabs.deselectAll = deselectAll; + tabs.select(0); + + return true; + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + var spans = document.getElementById("tabs").getElementsByTagName("span"); + var codeBlocks = []; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + var codeBlocks = findCodeBlocks(); + + for (var i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + var checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + var checkBox = getCheckBox(); + var label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + function switchTab() { + var id = this.id.substr(1); + + for (var i = 0; i < tabs.tabs.length; i++) { + if (tabs.tabs[i].id === id) { + tabs.select(i); + break; + } + } + + return false; + } + + function select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var h2 = document.createElement("H2"); + + h2.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(h2); + } + + function deselectAll() { + for (var i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var a = document.createElement("A"); + + a.setAttribute("id", "ltab" + i); + a.setAttribute("href", "#tab" + i); + a.onclick = switchTab; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + var owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + var titles = []; + + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + var header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + var elements = []; + var children = container.childNodes; + + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); \ No newline at end of file diff --git a/build/reports/tests/test/packages/one.jpro.jmemorybuddy.html b/build/reports/tests/test/packages/one.jpro.jmemorybuddy.html new file mode 100644 index 0000000..82e55ae --- /dev/null +++ b/build/reports/tests/test/packages/one.jpro.jmemorybuddy.html @@ -0,0 +1,123 @@ + + + + + +Test results - Package one.jpro.jmemorybuddy + + + + + +
+

Package one.jpro.jmemorybuddy

+ +
+ + + + + +
+
+ + + + + + + +
+
+
14
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
6.753s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+TestCleanupDetector +2001.138s100%
+TestJMemoryBuddy +11005.555s100%
+TestJMemoryBuddyLive +1000.060s100%
+
+
+ +
+ + diff --git a/build/test-results/test/TEST-one.jpro.jmemorybuddy.TestCleanupDetector.xml b/build/test-results/test/TEST-one.jpro.jmemorybuddy.TestCleanupDetector.xml new file mode 100644 index 0000000..b15eb92 --- /dev/null +++ b/build/test-results/test/TEST-one.jpro.jmemorybuddy.TestCleanupDetector.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/test-results/test/TEST-one.jpro.jmemorybuddy.TestJMemoryBuddy.xml b/build/test-results/test/TEST-one.jpro.jmemorybuddy.TestJMemoryBuddy.xml new file mode 100644 index 0000000..8a4c1fe --- /dev/null +++ b/build/test-results/test/TEST-one.jpro.jmemorybuddy.TestJMemoryBuddy.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + diff --git a/build/test-results/test/TEST-one.jpro.jmemorybuddy.TestJMemoryBuddyLive.xml b/build/test-results/test/TEST-one.jpro.jmemorybuddy.TestJMemoryBuddyLive.xml new file mode 100644 index 0000000..69e2eed --- /dev/null +++ b/build/test-results/test/TEST-one.jpro.jmemorybuddy.TestJMemoryBuddyLive.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/build/test-results/test/binary/output.bin b/build/test-results/test/binary/output.bin new file mode 100644 index 0000000..76ec4c9 Binary files /dev/null and b/build/test-results/test/binary/output.bin differ diff --git a/build/test-results/test/binary/output.bin.idx b/build/test-results/test/binary/output.bin.idx new file mode 100644 index 0000000..ecafef4 Binary files /dev/null and b/build/test-results/test/binary/output.bin.idx differ diff --git a/build/test-results/test/binary/results.bin b/build/test-results/test/binary/results.bin new file mode 100644 index 0000000..095688f Binary files /dev/null and b/build/test-results/test/binary/results.bin differ diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000..733502b Binary files /dev/null and b/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy$1.class.uniqueId2 b/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy$1.class.uniqueId2 new file mode 100644 index 0000000..102bc59 Binary files /dev/null and b/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy$1.class.uniqueId2 differ diff --git a/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy$2.class.uniqueId1 b/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy$2.class.uniqueId1 new file mode 100644 index 0000000..3982e45 Binary files /dev/null and b/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy$2.class.uniqueId1 differ diff --git a/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy$A.class.uniqueId0 b/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy$A.class.uniqueId0 new file mode 100644 index 0000000..a4f57b5 Binary files /dev/null and b/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy$A.class.uniqueId0 differ diff --git a/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy.class.uniqueId3 b/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy.class.uniqueId3 new file mode 100644 index 0000000..10ec5d6 Binary files /dev/null and b/build/tmp/compileTestJava/compileTransaction/stash-dir/TestJMemoryBuddy.class.uniqueId3 differ diff --git a/build/tmp/compileTestJava/previous-compilation-data.bin b/build/tmp/compileTestJava/previous-compilation-data.bin new file mode 100644 index 0000000..675dea3 Binary files /dev/null and b/build/tmp/compileTestJava/previous-compilation-data.bin differ diff --git a/src/main/java/one/jpro/jmemorybuddy/HeapPath.java b/src/main/java/one/jpro/jmemorybuddy/HeapPath.java new file mode 100644 index 0000000..0d61e57 --- /dev/null +++ b/src/main/java/one/jpro/jmemorybuddy/HeapPath.java @@ -0,0 +1,369 @@ +package one.jpro.jmemorybuddy; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A small, dependency-free HPROF reader that prints a strong-reference path from a + * GC root to each not-collected object. The objects are located in the dump via + * {@link TrackedWeakReference} instances (their {@code referent} field), so no strong + * reference to the leaked object is needed and weak references are excluded from the path. + * + *

Best-effort: on any parse problem or when the heap exceeds {@code maxObjects} the + * analysis is skipped silently — the heap dump itself is always still written. + */ +final class HeapPath { + + // HPROF top-level record tags + private static final int TAG_STRING = 0x01; + private static final int TAG_LOAD_CLASS = 0x02; + private static final int TAG_HEAP_DUMP = 0x0C; + private static final int TAG_HEAP_DUMP_SEGMENT = 0x1C; + + // HPROF heap-dump sub-record tags + private static final int ROOT_JNI_GLOBAL = 0x01; + private static final int ROOT_JNI_LOCAL = 0x02; + private static final int ROOT_JAVA_FRAME = 0x03; + private static final int ROOT_NATIVE_STACK = 0x04; + private static final int ROOT_STICKY_CLASS = 0x05; + private static final int ROOT_THREAD_BLOCK = 0x06; + private static final int ROOT_MONITOR_USED = 0x07; + private static final int ROOT_THREAD_OBJECT = 0x08; + private static final int ROOT_UNKNOWN = 0xFF; + private static final int CLASS_DUMP = 0x20; + private static final int INSTANCE_DUMP = 0x21; + private static final int OBJECT_ARRAY_DUMP = 0x22; + private static final int PRIMITIVE_ARRAY_DUMP = 0x23; + + private static final String TRACKED_REF = TrackedWeakReference.class.getName(); + + private final String path; + private final int maxObjects; + private int idSize; + + private final Map strings = new HashMap<>(); // stringId -> text + private final Map classNameStringId = new HashMap<>(); // classObjId -> stringId + private final Map classes = new HashMap<>(); // classObjId -> info + + // graph + metadata, built in pass 2 + private final Map edges = new HashMap<>(); // objId -> [fieldNameId, targetId, ...] (fieldNameId -1 = array element) + private final Map objClass = new HashMap<>(); // objId -> classObjId + private final Map roots = new HashMap<>(); // rootId -> root kind + private final Set targets = new HashSet<>(); // referents of TrackedWeakReference + + private HeapPath(String path, int maxObjects) { + this.path = path; + this.maxObjects = maxObjects; + } + + /** Reads the dump and prints a path to a GC root for each tracked, not-collected object. */ + static void printPaths(String hprofPath, int maxObjects) { + try { + new HeapPath(hprofPath, maxObjects).run(); + } catch (Throwable t) { + System.out.println("JMemoryBuddy: could not analyse the heap dump (" + t + "). The dump is at " + hprofPath); + } + } + + private void run() throws IOException { + parse(true); // classes + strings + parse(false); // graph + roots + targets + if (targets.isEmpty()) { + System.out.println("JMemoryBuddy: no tracked references found in the dump; cannot compute a path to the GC root."); + return; + } + for (Long target : targets) { + printPath(target); + } + } + + // ---- HPROF parsing ------------------------------------------------------ + + private void parse(boolean firstPass) throws IOException { + try (Counter in = new Counter(new DataInputStream(new BufferedInputStream(new FileInputStream(path), 1 << 16)))) { + readHeader(in); + while (true) { + int tag; + try { + tag = in.u1(); + } catch (EOFException eof) { + break; + } + in.u4(); // timestamp delta + long length = in.u4() & 0xFFFFFFFFL; + if (tag == TAG_STRING && firstPass) { + long id = in.id(); + strings.put(id, new String(in.bytes((int) (length - idSize)), java.nio.charset.StandardCharsets.UTF_8)); + } else if (tag == TAG_LOAD_CLASS && firstPass) { + in.u4(); // class serial + long classObjId = in.id(); + in.u4(); // stack serial + long nameId = in.id(); + classNameStringId.put(classObjId, nameId); + } else if (tag == TAG_HEAP_DUMP || tag == TAG_HEAP_DUMP_SEGMENT) { + parseHeapDump(in, length, firstPass); + } else { + in.skip(length); + } + } + } + } + + private void readHeader(Counter in) throws IOException { + // null-terminated format string, e.g. "JAVA PROFILE 1.0.2" + while (in.u1() != 0) { /* consume */ } + idSize = in.u4(); + in.u4(); // timestamp high + in.u4(); // timestamp low + } + + private void parseHeapDump(Counter in, long length, boolean firstPass) throws IOException { + long end = in.pos + length; + while (in.pos < end) { + int sub = in.u1(); + switch (sub) { + case ROOT_JNI_GLOBAL: addRoot(in.id(), firstPass, "JNI global"); in.id(); break; + case ROOT_JNI_LOCAL: addRoot(in.id(), firstPass, "JNI local"); in.u4(); in.u4(); break; + case ROOT_JAVA_FRAME: addRoot(in.id(), firstPass, "Java frame"); in.u4(); in.u4(); break; + case ROOT_NATIVE_STACK: addRoot(in.id(), firstPass, "native stack"); in.u4(); break; + case ROOT_STICKY_CLASS: addRoot(in.id(), firstPass, "sticky class"); break; + case ROOT_THREAD_BLOCK: addRoot(in.id(), firstPass, "thread block"); in.u4(); break; + case ROOT_MONITOR_USED: addRoot(in.id(), firstPass, "monitor"); break; + case ROOT_THREAD_OBJECT: addRoot(in.id(), firstPass, "thread object"); in.u4(); in.u4(); break; + case ROOT_UNKNOWN: addRoot(in.id(), firstPass, "unknown root"); break; + case CLASS_DUMP: parseClassDump(in, firstPass); break; + case INSTANCE_DUMP: parseInstanceDump(in, firstPass); break; + case OBJECT_ARRAY_DUMP: parseObjectArray(in, firstPass); break; + case PRIMITIVE_ARRAY_DUMP: parsePrimitiveArray(in); break; + default: throw new IOException("Unknown heap sub-record 0x" + Integer.toHexString(sub) + " at " + in.pos); + } + } + } + + private void addRoot(long id, boolean firstPass, String kind) { + if (!firstPass && id != 0) roots.putIfAbsent(id, kind); + } + + private void parseClassDump(Counter in, boolean firstPass) throws IOException { + long classObjId = in.id(); + in.u4(); // stack serial + long superId = in.id(); + in.id(); in.id(); in.id(); in.id(); in.id(); // loader, signers, protection domain, reserved x2 + in.u4(); // instance size + int cpCount = in.u2(); + for (int i = 0; i < cpCount; i++) { in.u2(); int t = in.u1(); in.skip(typeSizeWithId(t)); } + int staticCount = in.u2(); + for (int i = 0; i < staticCount; i++) { in.id(); int t = in.u1(); in.skip(typeSizeWithId(t)); } + int fieldCount = in.u2(); + long[] fields = new long[fieldCount * 2]; // [nameId, type, ...] + for (int i = 0; i < fieldCount; i++) { + long nameId = in.id(); + int type = in.u1(); + fields[i * 2] = nameId; + fields[i * 2 + 1] = type; + } + if (firstPass) classes.put(classObjId, new ClassInfo(superId, fields)); + } + + private void parseInstanceDump(Counter in, boolean firstPass) throws IOException { + long objId = in.id(); + in.u4(); // stack serial + long classObjId = in.id(); + long nBytes = in.u4() & 0xFFFFFFFFL; + if (firstPass) { in.skip(nBytes); return; } + + byte[] data = in.bytes((int) nBytes); + objClass.put(objId, classObjId); + boolean tracked = TRACKED_REF.equals(className(classObjId)); + List refs = new ArrayList<>(); + int off = 0; + long cls = classObjId; + while (cls != 0) { + ClassInfo ci = classes.get(cls); + if (ci == null) break; + String clsName = className(cls); + for (int i = 0; i < ci.fields.length; i += 2) { + long nameId = ci.fields[i]; + int type = (int) ci.fields[i + 1]; + if (type == 2) { // object + long ref = readId(data, off); + boolean isReferent = "java.lang.ref.Reference".equals(clsName) && "referent".equals(strings.get(nameId)); + if (isReferent) { + if (tracked && ref != 0) targets.add(ref); // weak: a target, never a strong edge + } else if (ref != 0) { + refs.add(new long[]{nameId, ref}); + } + off += idSize; + } else { + off += typeSize(type); + } + } + cls = ci.superId; + } + storeEdges(objId, refs); + } + + private void parseObjectArray(Counter in, boolean firstPass) throws IOException { + long objId = in.id(); + in.u4(); // stack serial + int n = in.u4(); + long arrayClass = in.id(); + if (firstPass) { in.skip((long) n * idSize); return; } + objClass.put(objId, arrayClass); + List refs = new ArrayList<>(); + for (int i = 0; i < n; i++) { + long ref = in.id(); + if (ref != 0) refs.add(new long[]{-1, ref}); + } + storeEdges(objId, refs); + } + + private void parsePrimitiveArray(Counter in) throws IOException { + in.id(); // array id + in.u4(); // stack serial + int n = in.u4(); + int t = in.u1(); + in.skip((long) n * typeSize(t)); + } + + private void storeEdges(long objId, List refs) { + if (objClass.size() > maxObjects) throw new RuntimeException("heap too large (> " + maxObjects + " objects) for in-process analysis"); + if (refs.isEmpty()) { edges.put(objId, EMPTY); return; } + long[] flat = new long[refs.size() * 2]; + for (int i = 0; i < refs.size(); i++) { flat[i * 2] = refs.get(i)[0]; flat[i * 2 + 1] = refs.get(i)[1]; } + edges.put(objId, flat); + } + + private static final long[] EMPTY = new long[0]; + + // ---- BFS + printing ----------------------------------------------------- + + private void printPath(long target) { + // BFS from all roots; record parent edges until we reach the target. + Map parent = new HashMap<>(); // childId -> [parentId, fieldNameId] + Deque queue = new ArrayDeque<>(); + Set visited = new HashSet<>(roots.keySet()); + queue.addAll(roots.keySet()); + boolean found = roots.containsKey(target); + while (!queue.isEmpty() && !found) { + long cur = queue.poll(); + long[] e = edges.get(cur); + if (e == null) continue; + for (int i = 0; i < e.length; i += 2) { + long child = e[i + 1]; + if (visited.add(child)) { + parent.put(child, new long[]{cur, e[i]}); + if (child == target) { found = true; break; } + queue.add(child); + } + } + } + + String leak = "leak: " + simpleName(className(objClass.get(target))) + " @" + Long.toHexString(target); + if (!found) { + System.out.println("JMemoryBuddy: " + leak + " is retained, but no strong path to a GC root was found " + + "(it may be held only by a thread stack the dump doesn't expose)."); + return; + } + + // reconstruct root -> target + List chain = new ArrayList<>(); + long node = target; + while (true) { + chain.add(node); + long[] p = parent.get(node); + if (p == null) break; + node = p[0]; + } + java.util.Collections.reverse(chain); + + StringBuilder sb = new StringBuilder(); + sb.append("\nJMemoryBuddy — path to GC root for ").append(leak).append(":\n"); + long root = chain.get(0); + sb.append(" GC root [").append(roots.getOrDefault(root, "root")).append("] ") + .append(simpleName(className(objClass.get(root)))).append('\n'); + for (int i = 1; i < chain.size(); i++) { + long child = chain.get(i); + long fieldNameId = parent.get(child)[1]; + String edge = fieldNameId == -1 ? "[]" : "." + strings.getOrDefault(fieldNameId, "?"); + sb.append(" ↓ ").append(simpleName(className(objClass.get(chain.get(i - 1))))).append(edge).append('\n'); + sb.append(" ").append(simpleName(className(objClass.get(child)))) + .append(i == chain.size() - 1 ? " ← leaked" : "").append('\n'); + } + System.out.println(sb); + } + + // ---- helpers ------------------------------------------------------------ + + private String className(Long classObjId) { + if (classObjId == null) return "?"; + Long nameId = classNameStringId.get(classObjId); + String n = nameId == null ? null : strings.get(nameId); + return n == null ? "?" : n.replace('/', '.'); + } + + private static String simpleName(String n) { + if (n == null) return "?"; + int i = n.lastIndexOf('.'); + return i < 0 ? n : n.substring(i + 1); + } + + private long readId(byte[] b, int off) { + long v = 0; + for (int i = 0; i < idSize; i++) v = (v << 8) | (b[off + i] & 0xFFL); + return v; + } + + private static int typeSize(int type) { + switch (type) { + case 2: return -1; // object -> idSize (patched by caller via typeSize w/ idSize); handled below + case 4: case 8: return 1; // boolean, byte + case 5: case 9: return 2; // char, short + case 6: case 10: return 4; // float, int + case 7: case 11: return 8; // double, long + default: throw new IllegalArgumentException("bad type " + type); + } + } + + private int typeSizeWithId(int type) { return type == 2 ? idSize : typeSize(type); } + + private static final class ClassInfo { + final long superId; + final long[] fields; // [nameStringId, type, ...] + ClassInfo(long superId, long[] fields) { this.superId = superId; this.fields = fields; } + } + + /** DataInputStream wrapper that tracks the byte position and resolves id-sized reads. */ + private final class Counter implements AutoCloseable { + final DataInputStream in; + long pos; + Counter(DataInputStream in) { this.in = in; } + int u1() throws IOException { int v = in.readUnsignedByte(); pos += 1; return v; } + int u2() throws IOException { int v = in.readUnsignedShort(); pos += 2; return v; } + int u4() throws IOException { int v = in.readInt(); pos += 4; return v; } + long id() throws IOException { + long v = 0; + for (int i = 0; i < idSize; i++) v = (v << 8) | (in.readUnsignedByte() & 0xFFL); + pos += idSize; + return v; + } + byte[] bytes(int n) throws IOException { byte[] b = new byte[n]; in.readFully(b); pos += n; return b; } + void skip(long n) throws IOException { + long left = n; + while (left > 0) { long s = in.skip(left); if (s <= 0) { if (in.read() < 0) throw new EOFException(); s = 1; } left -= s; } + pos += n; + } + public void close() throws IOException { in.close(); } + } +} diff --git a/src/main/java/one/jpro/jmemorybuddy/JMemoryBuddy.java b/src/main/java/one/jpro/jmemorybuddy/JMemoryBuddy.java index af94ab0..067e811 100644 --- a/src/main/java/one/jpro/jmemorybuddy/JMemoryBuddy.java +++ b/src/main/java/one/jpro/jmemorybuddy/JMemoryBuddy.java @@ -24,6 +24,8 @@ public class JMemoryBuddy { private static final int testDuration; private static final int sleepDuration; private static final boolean createHeapdump; + private static final boolean printPath; + private static final int maxObjects; private static final int garbageAmount; private static String mxBeanProxyName = "com.sun.management:type=HotSpotDiagnostic"; private static String outputFolderString = "."; @@ -33,6 +35,8 @@ public class JMemoryBuddy { testDuration = Integer.parseInt(System.getProperty("jmemorybuddy.testDuration","1000")); steps = Integer.parseInt(System.getProperty("jmemorybuddy.steps", "10")); createHeapdump = Boolean.parseBoolean(System.getProperty("jmemorybuddy.createHeapdump", "true")); + printPath = Boolean.parseBoolean(System.getProperty("jmemorybuddy.printPath", "true")); + maxObjects = Integer.parseInt(System.getProperty("jmemorybuddy.maxObjects", "8000000")); garbageAmount = Integer.parseInt(System.getProperty("jmemorybuddy.garbageAmount", "99999")); sleepDuration = testDuration / steps; @@ -62,8 +66,10 @@ static void createGarbage() { */ public static void assertCollectable(WeakReference weakReference) { if (!checkCollectable(weakReference)) { - AssertCollectable assertCollectable = new AssertCollectable(weakReference); - createHeapDump(); + // A marker so the object is locatable in the dump — held weakly, so it does not pollute the path. + TrackedWeakReference marker = new TrackedWeakReference(weakReference.get()); + analyzePaths(createHeapDump()); + java.lang.ref.Reference.reachabilityFence(marker); throw new AssertionError("Content of WeakReference was not collected. content: " + weakReference.get()); } } @@ -143,7 +149,7 @@ public static void memoryTest(Consumer f) { f.accept(new MemoryTestAPI() { public void assertCollectable(Object ref) { Objects.requireNonNull(ref); - toBeCollected.add(new WeakReference<>(ref)); + toBeCollected.add(new TrackedWeakReference(ref)); } public void assertNotCollectable(Object ref) { Objects.requireNonNull(ref); @@ -184,7 +190,7 @@ public void setAsReferenced(Object ref) { toBeNotCollectedMarked.add(wRef); } } - createHeapDump(); + analyzePaths(createHeapDump()); if (toBeNotCollectedMarked.isEmpty()) { throw new AssertionError("The following references should be collected: " + toBeCollectedMarked); } else if(toBeCollectedMarked.isEmpty()) { @@ -195,7 +201,7 @@ public void setAsReferenced(Object ref) { } } - static void createHeapDump() { + static String createHeapDump() { if (createHeapdump) { try { String dateString = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new Date()); @@ -204,12 +210,21 @@ static void createHeapDump() { String heapdumpFile = new java.io.File(outputFolder, fileName).getAbsolutePath(); System.out.println("Creating Heapdump at: " + heapdumpFile); getHotspotMBean().dumpHeap(heapdumpFile, true); + return heapdumpFile; } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("No Heapdump was created. You might want to change the configuration to get a HeapDump."); } + return null; + } + + /** Reads the dump back and prints a strong-reference path from a GC root to each not-collected object. */ + private static void analyzePaths(String heapDumpFile) { + if (printPath && heapDumpFile != null) { + HeapPath.printPaths(heapDumpFile, maxObjects); + } } private static void setMxBeanProxyName(String mxBeanName) { diff --git a/src/main/java/one/jpro/jmemorybuddy/TrackedWeakReference.java b/src/main/java/one/jpro/jmemorybuddy/TrackedWeakReference.java new file mode 100644 index 0000000..aa1add1 --- /dev/null +++ b/src/main/java/one/jpro/jmemorybuddy/TrackedWeakReference.java @@ -0,0 +1,14 @@ +package one.jpro.jmemorybuddy; + +import java.lang.ref.WeakReference; + +/** + * A marker {@link WeakReference} used so the not-collected object can be located inside a + * heap dump (by this class name) and its {@code referent} followed — without ever holding a + * strong reference to it. See {@link HeapPath}. + */ +final class TrackedWeakReference extends WeakReference { + TrackedWeakReference(Object referent) { + super(referent); + } +} diff --git a/src/test/java/one/jpro/jmemorybuddy/TestJMemoryBuddy.java b/src/test/java/one/jpro/jmemorybuddy/TestJMemoryBuddy.java index 7dc7541..12d3c95 100644 --- a/src/test/java/one/jpro/jmemorybuddy/TestJMemoryBuddy.java +++ b/src/test/java/one/jpro/jmemorybuddy/TestJMemoryBuddy.java @@ -180,4 +180,19 @@ protected void finalize() { public void testCreateHeapDump() { JMemoryBuddy.createHeapDump(); // shouldn't throw an exception } + + static class Holder { Object field; } + + @Test + public void multiHopPath() { + Holder holder = new Holder(); + Throwable e = Assertions.assertThrows(AssertionError.class, () -> { + JMemoryBuddy.memoryTest(checker -> { + Object leaked = new Object(); + holder.field = leaked; // leaked is reachable via holder.field + checker.assertCollectable(leaked); + }); + }); + System.out.println("multiHopPath message: " + e.getMessage()); + } }