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);
}