This public repository is read-only and no longer maintained.
A Java agent (as in -javaagent
) that automates the generation of heap dumps when thresholds in terms of memory usage of the entire heap
or single memory pools (e.g., eden
, old gen
, etc.) are met.
Unlike other Java agents, the Java Memory Assistant does not do bytecode manipulation via the Instrumentation API. It is shipped as a Java agent only for convenience, so that its functionality can be added to any Java application without having to modify the latter's content or logic.
The Java Memory Assistant jar file must be passed to the startup parameters of the JVM using the -javaagent
option, e.g.:
java -javaagent:jma.jar -jar yourApp.jar
By default, the Java Memory Assistant does nothing. To activate it, one needs to pass at least two System properties as options:
jma.check_interval
: every how often will the agent poll the JVM runtime to check the usage state of the memory poolsjma.thresholds.[memory pool or 'heap']
: a usage threshold for the specified memory pool or for the entire heap
Thresholds of multiple memory pools are supported. The heap dump is then triggered when a check is executed and the usage of at least one memory pool is higher or equal to the threshold specified. For example:
java -javaagent:jma.jar -Djma.check_interval=5000ms -Djma.thresholds.heap=60 -jar yourApp.jar
These startup parameters would make the Java Memory Assistant check every 5 seconds how much of the heap is in use and, if 60% or more of the heap is allocated, it will trigger a heap dump using the HotSpotDiagnosticMXBean#dumpHeap(String, boolean) facility.
System Property | Supported values | Description | Default value |
jma.enabled | true , false |
Whether the agent has to activate itself or not, irrespective from any thresholds or other settings specified | true |
jma.check_interval | (0, 2147483647]ms|s|m|h |
How often will the agent check for the memory usage conditions. The numeric value must be and int between 1 and 2147483647 (extremes included). The time unit is one of ms (milliseconds), s (seconds), m (minutes) or h (hours). |
-1 (disabled) |
jma.max_frequency | (1, 2147483647]/(1, 2147483647]ms|s|m|h |
How often can agent create heap dumps in a given time-span. Both numeric values must be int between 1 and 2147483647 (extremes included). The time unit is one of ms (milliseconds), s (seconds), m (minutes) or h (hours). |
no maximum frequency specified |
jma.log_level | DEBUG , WARN , INFO , ERROR , OFF |
The minimum severity of the logs to be printed; all logs from severity INFO and lower are printed to System.out ; ERROR is printed to System.err ; if OFF is specified, no logs are written anywhere |
ERROR |
jma.heap_dump_folder | Any valid local of absolute filesystem path in which the JVM process can create files | Filesystem location where heap dumps will be created | System.getProperty("user.dir") |
jma.heap_dump_name | The pattern to use to generate file names for heap dumps, e.g., heapdump_%host_name%_%ts%.hprof |
Name of the file under the jma.heap_dump_folder where the heap dump will be stored |
heapdump_%host_name%_%ts:yyyyMMddHHmmss%.hprof |
jma.thresholds.heap | The thresholds can be specified as one of the following:
|
The usage threshold of the overall heap that, when reached or surpassed, triggers a heap dump | null (disabled) |
jma.thresholds.[memory_pool_name] | Either a usage percentage threshold, i.e., any number between 0 and 99.99 followed by the '%' sign (precise to the second decimal digit, e.g., 42.42), or a percentage-based increase-over-time-frame specification, e.g., +5%/4s (5% increase over 4 seconds); time unit is one of ms (milliseconds), s (seconds), m (minutes) or h (hours). Supported memory pools depend on the JVM |
The usage threshold of the particular memory pool that, when reached or surpassed, triggers a heap dump | null (disabled) |
jma.command.interpreter | Any string | A OS-specific interpreter (e.g., shell, cmd) that will be executed via the JDK java.lang.ProcessBuilder API. If set to "" , the command interpreter is ignored. |
cmd on Windows, sh otherwise |
jma.execute.before | Any string | A script that will be executed by the command interpreter before a heap dump is created; it receives as first (and only) input the filename of the heap dump that will be created API | null (disabled) |
jma.execute.after | Any string | A script that will be executed by the command interpreter after a heap dump has been created; it receives as first (and only) input the filename of the heap dump that has been created API | null (disabled) |
jma.execute.on_shutdown | Any string | A script that will be executed by the command interpreter as a shutdown hook (as in java.lang.Runtime.addShutdownHook(java.lang.Thread) ) upon a soft shutdown of the JVM, e.g., when java.lang.System.exit(int) is invoked; the command invoked receives no input. |
null (disabled) |
File names for heap dumps can be generated using a combination of fixed characters and tokens that can replaced with values.
Tokens are delimited by the %
character.
For example, my_heapdump_%host_name%_%uuid%.hprof
is a pattern with two tokens: %host_name%
and %uuid%
.
When generating a heap dump, the name of the file where it will be stored is calculated by replacing %host_name%
with the hostname of the machine running the Java process (e.g., localhost
) and a random UUID.
That is, the heap dump name may look something like my_heapdump_localhost_19866c22-ce15-41de-807b-4805d0387d76.hprof
.
The Java Memory Assistant supports the following tokens:
Token | Supports configuration? | Description |
%host_name% | forbidden | The network name of the host of the Java Virtual Machine, calculated as: java.net.InetAddress.getLocalHost().getHostName() . |
%uuid% | forbidden | A random UUID of 128 bits, calculated as: java.util.UUID.randomUUID().toString() . |
%ts% or %ts:[date format]% | optionally: a valid java.text.SimpleDateFormat pattern |
The number of millis since the Unix epoch, calculated as java.lang.System.currentTimeMillis() , or its value formatted with the pattern provided as configuration; the formatter uses the java.util.TimeZone.getDefault() time zone. |
%env:['['i, j']']% | required: the name of an environment property | The name of an environment property, the value of which replaces the token.
The value is looked up via the java.lang.System.getenv([property name]) call.
If the environment property is not present, the token will generate no characters.
The value of the environment property can be truncated using the `[a, b]` notation appended to the environment variable name to select a substring of the value. |
The timestamp %ts%
token supports configuration that specifies the pattern to use to format the timestamp.
Configuration is passed after the :
character that follows the token name, e.g., %ts:[config]%
.
Configuration is optional.
If no configuration is passed to the %ts%
token, the number of millis since the epoch is printed out.
For example, assume we were to generate a heap dump exactly at Unix epoch, we would have the following file names:
heap_dump_%ts%.hprof
generatesheap_dump_0.hprof
heap_dump_%ts:yyyyMMddmmssSS.hprof
generatesheap_dump_19700101000000.hprof
The environment %env:...%
token allows you to insert in the heap dump names the value of an environment property.
The environment property is evaluated once at agent's startup.
If the environment property is not set, the token does not generate any character in the heap dump name.
The %
character also serves as escape character for itself.
Escaping needs to be performed both in token as well as in the fixed
part of a pattern:
heap_%%_dump.hprof
generatesheap_%_dump.hprof
heap_dump_%ts:yyyyMMdd'%%'mmssSS.hprof
generatesheap_dump_19700101%000000.hprof
End-to-end integration tests are run automatically in the JVM running Gradle.
Additionally, the same tests are repeated automatically for each of the supported JVMs, provided that the binaries of the JVM are available in the right folder (more on this later).
This repository does not contain any JVM binaries, as that would count as distribution and violate most EULAs out there. (Plus, polluting GIT with tons of binaries is bad form.)
JVM for the integration tests have to be placed in folders under the test-e2e/src/test/resources/jdks
directory.
The java
executable must be under the test-e2e/src/test/resources/jdks/[jvm_directory]/bin/java
path.
The name of the [jvm_directory]
will given the name to the Gradle itest
task that will run the integration tests with that JVM.
All JVMs support thresholds for the entire heap (specified via the jma.thresholds.heap
system property). The specific memory pools, however, depend on the particular JVM.
The Java Memory Assistant currently supports the following JVMs and settings thresholds for the specific memory areas.
Trying to run the Java Memory Assistant on an unsupported JVM will lead to the agent disabling itself, but won't impact the rest of the JVM or the application running inside it.
Supported memory pools:
eden
survivor
old_gen
metaspace
code_cache
compressed_class
Supported memory pools:
eden
survivor
old_gen
metaspace
code_cache
code_heap.non_nmethods
code_heap.non_profiled_nmethods
code_heap.profiled_nmethods
compressed_class
Supported memory pools:
eden
survivor
old_gen
perm_gen
code_cache
Supported memory pools:
eden
survivor
old_gen
metaspace
code_cache
compressed_class
Supported memory pools:
eden
survivor
old_gen
metaspace
code_cache
compressed_class
Supported memory pools:
eden
survivor
old_gen
perm_gen
code_cache
Supported memory pools:
eden
survivor
old_gen
metaspace
code_cache
compressed_class
Supported memory pools:
eden
survivor
old_gen
metaspace
code_cache
code_heap.non_nmethods
code_heap.non_profiled_nmethods
code_heap.profiled_nmethods
compressed_class
Supported memory pools:
eden
survivor
tenured_gen
metaspace
code_cache
compressed_class
Supported memory pools:
eden
survivor
old_gen
metaspace
code_cache
compressed_class
Supported memory pools:
eden
survivor
old_gen
metaspace
code_cache
compressed_class
Supported memory pools:
eden
survivor
old_gen
metaspace
code_cache
code_heap.non_nmethods
code_heap.non_profiled_nmethods
code_heap.profiled_nmethods
compressed_class
This project should be built with a Java 1.7 JDK.
Building it with a Java 1.8 JDK will also work, but there will be warnings reporting that the bootstrap classpath [is] not set in conjunction with -source 1.7
.
To trigger a build, run the following command from the root of this repository:
./gradlew clean build
For building behind a proxy, consider setting the proxy-variables as follows:
./gradlew -Dhttps.proxyHost=[proxy_hostname] -Dhttps.proxyPort=[proxy_port] -Dhttp.proxyHost=[proxy_hostname] -Dhttp.proxyPort=[proxy_port] clean build
where, for example [proxy_hostname]
is proxy.wdf.sap.corp
and [proxy_port]
is 8080
.
When adding new code files to the project the build might fail with Missing header in: [...]
. Run the following command to add a license header to all new files:
./gradlew LicenseFormat
Due to the restrictions introduced by the Java module system with Java 9, the following additional argument must be passed to the java
command:
java ... --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED ...
This will allow the Java Memory Assistant to access the com.sun.management.internal.HotSpotDiagnostic
MBean it uses to trigger the creation of the heap dumps.
This project is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file.