This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/main by this push:
     new 19591a949ae CAUSEWAY-2297: internal _MemoryUsage supporting heap size 
measurement
19591a949ae is described below

commit 19591a949aef2e245e437e83d02e29243ce2a713
Author: andi-huber <[email protected]>
AuthorDate: Mon Dec 15 16:47:20 2025 +0100

    CAUSEWAY-2297: internal _MemoryUsage supporting heap size measurement
---
 .../commons/internal/debug/_MemoryUsage.java       | 65 ++++++++++++++++------
 .../WrapperFactoryMetaspaceMemoryLeakTest.java     | 13 +++--
 2 files changed, 56 insertions(+), 22 deletions(-)

diff --git 
a/commons/src/main/java/org/apache/causeway/commons/internal/debug/_MemoryUsage.java
 
b/commons/src/main/java/org/apache/causeway/commons/internal/debug/_MemoryUsage.java
index 255a8794e9c..3b2d0234434 100644
--- 
a/commons/src/main/java/org/apache/causeway/commons/internal/debug/_MemoryUsage.java
+++ 
b/commons/src/main/java/org/apache/causeway/commons/internal/debug/_MemoryUsage.java
@@ -20,32 +20,62 @@
 
 import java.lang.management.ManagementFactory;
 import java.lang.management.MemoryPoolMXBean;
+import java.util.concurrent.Callable;
+import java.util.function.Consumer;
 
 import org.apache.causeway.commons.functional.ThrowingRunnable;
 
+import lombok.SneakyThrows;
+
 /**
  * <h1>- internal use only -</h1>
- * <p>
- * Memory Usage Utility
- * <p>
- * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this 
package! <br/>
+ *
+ * <p>Memory Usage Utility
+ *
+ * <p><b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this 
package! <br/>
  * These may be changed or removed without notice!
- * </p>
+ *
+ * @implNote heap-space measurement relies on GC actually running before 
taking the measurements;
+ *  {@link System#gc()} is the only hint we can provide to the GC, but have no 
control over what it
+ *  is doing; However, I was comparing measurements of a large 200MB+ object 
graph using
+ *  {@code GraphLayout.parseInstance(obj).totalSize()} from <pre>{@code
+<dependency>
+    <groupId>org.openjdk.jol</groupId>
+    <artifactId>jol-core</artifactId>
+    <version>0.17</version>
+</dependency>
+ *  }</pre>
+ * and the error with {@link _MemoryUsage} was surprisingly low {@code <1%}.
+ *
  * @since 3.4.0
  */
-public record _MemoryUsage(long usedInKibiBytes) {
+public record _MemoryUsage(
+               long metaspaceUsed,
+               long heapUsed
+               ) {
 
     // -- UTILITIES
 
-    static int indent = 0;
+       @SneakyThrows
+       public static <T> T measure(final Callable<T> callable, final 
Consumer<_MemoryUsage> callback) {
+               System.gc();
+        var before = metaspace();
+        T result = callable.call();
+       System.gc();
+        var after = metaspace();
+        callback.accept(after.minus(before));
+        return result;
+       }
 
-    public static _MemoryUsage measureMetaspace(final ThrowingRunnable 
runnable) {
+    public static _MemoryUsage measure(final ThrowingRunnable runnable) {
+       System.gc();
         var before = metaspace();
         var after = (_MemoryUsage) null;
         try {
             runnable.run();
         } catch (Throwable e) {
         } finally {
+               System.gc();
             after = metaspace();
         }
         return after.minus(before);
@@ -55,28 +85,31 @@ public static _MemoryUsage measureMetaspace(final 
ThrowingRunnable runnable) {
 
     private static _MemoryUsage metaspace() {
         for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) 
{
-            if (pool.getName().contains("Metaspace")) {
-                return new _MemoryUsage(pool.getUsage());
-            }
+            if (pool.getName().contains("Metaspace"))
+                               return new _MemoryUsage(pool.getUsage());
         }
         throw new RuntimeException("Metaspace Usage not found");
     }
 
     // -- NON CANONICAL CONSTRUCTOR
 
-    private _MemoryUsage(java.lang.management.MemoryUsage usage) {
-        this(usage.getUsed() / 1024);
+    private _MemoryUsage(final java.lang.management.MemoryUsage usage) {
+        this(usage.getUsed(),
+                       Runtime.getRuntime().totalMemory() - 
Runtime.getRuntime().freeMemory());
     }
 
     @Override
     public String toString() {
-        return String.format("%,d KiB", usedInKibiBytes);
+        return "metaspace: %.3f MB, heap: %.3f MB".formatted(
+                       0.000_001 * metaspaceUsed,
+                       0.000_001 * heapUsed
+                       );
     }
 
     // -- HELPER
 
-    private _MemoryUsage minus(_MemoryUsage before) {
-        return new _MemoryUsage(this.usedInKibiBytes - before.usedInKibiBytes);
+    private _MemoryUsage minus(final _MemoryUsage other) {
+        return new _MemoryUsage(this.metaspaceUsed - other.metaspaceUsed, 
this.heapUsed - other.heapUsed);
     }
 
 }
diff --git 
a/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/wrapper/WrapperFactoryMetaspaceMemoryLeakTest.java
 
b/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/wrapper/WrapperFactoryMetaspaceMemoryLeakTest.java
index df9f62d7817..d1ba082665a 100644
--- 
a/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/wrapper/WrapperFactoryMetaspaceMemoryLeakTest.java
+++ 
b/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/wrapper/WrapperFactoryMetaspaceMemoryLeakTest.java
@@ -27,6 +27,7 @@
 import org.junit.jupiter.params.provider.EnumSource;
 
 import org.springframework.boot.test.context.SpringBootTest;
+
 import org.apache.causeway.applib.services.wrapper.WrapperFactory;
 import org.apache.causeway.commons.internal.base._Blackhole;
 import org.apache.causeway.commons.internal.debug._MemoryUsage;
@@ -83,20 +84,20 @@ void uninstallFixture() {
     //  exercise(20000, 0);    //.115,729 KB
     @RequiredArgsConstructor
     enum Scenario {
-        TWO_K_TIMES_ONE(2000, 0, 4000),
-        TWO_K_TIMES_TEN(2000, 10, 4000),;
+        TWO_K_TIMES_ONE(2000, 0, 4*1000),
+        TWO_K_TIMES_TEN(2000, 10, 4*1000);
         final int instances;
         final int loops;
-        final int thresholdKibi;
+        final int thresholdKB;
     }
 
     @ParameterizedTest
     @EnumSource(Scenario.class)
     void testWrapper_waitingOnDomainEventScenario(final Scenario scenario) 
throws InterruptedException {
-        var usage = _MemoryUsage.measureMetaspace(()->
+        var usage = _MemoryUsage.measure(()->
             exercise(scenario.instances, scenario.loops));
-        Assertions.assertTrue(usage.usedInKibiBytes() < scenario.thresholdKibi,
-            ()->"%s exceeds expected %dKB threshold".formatted(usage, 
scenario.thresholdKibi));
+        Assertions.assertTrue(usage.metaspaceUsed() < 1000 * 
scenario.thresholdKB,
+            ()->"%s exceeds expected %dKB threshold".formatted(usage, 
scenario.thresholdKB));
         System.out.printf("scenario %s usage %s%n", scenario.name(), usage);
     }
 

Reply via email to