Let us first understand the commond sources of native memory allocations in JVM
In order to maintain some metadata about the loaded classes, The JVM uses a dedicated non-heap area called Metaspace
Before Java 8, the equivalent was called PermGen
or Permanent Generation.
Metaspace
or PermGen
contains the metadata about the loaded classes rather than the instances of them, which are kept inside the heap
.
The heap sizing configurations won't affect the Metaspace size since the Metaspace is an off-heap
data area
-XX:MetaspaceSize
and-XX:MaxMetaspaceSize
to set the minimum and maximum Metaspace size- Before Java 8,
-XX:PermSize
and-XX:MaxPermSize
to set the minimum and maximum PermGen size
One of the most memory-consuming data areas in the JVM is the stack
, created at the same time as each thread.
The stack
stores local variables and partial results, playing an important role in method invocations.
In contrast with other data areas,
the total memory allocated to stacks is practically unbounded when there is no limitation on the number of threads
When the JVM compiles bytecode to assembly instructions,
it stores those instructions in a special non-heap data area called Code Cache
The code cache can be managed just like other data areas in the JVM.
The -XX:InitialCodeCacheSize
and -XX:ReservedCodeCacheSize
tuning flags determine the initial and maximum possible size for the code cache
The JVM is shipped with a handful of GC algorithms, each suitable for different use cases.
All those GC algorithms share one common trait: they need to use some off-heap data structures to perform their tasks.
These internal data structures consume more native memory.
String
objects usually occupy a large portion of the Heap.
If a large number of those strings contain the same content, then a significant part of the heap will be wasted
In order to save some heap space, we can store one version of each String and make others refer to the stored version.
This process is called String Interning. Since the JVM can only intern Compile Time String Constants,
we can manually call the intern() method on strings we intend to intern.
JVM stores interned strings in a special native fixed-sized hashtable called the String Table, also known as the String Pool
.
We can configure the table size (i.e. the number of buckets) via the -XX:StringTableSize
tuning flag.
In addition to the string table, there's another native data area called the Runtime Constant Pool
.
JVM uses this pool to store constants like compile-time numeric literals or method and field references that must be resolved at runtime.
The JVM is the usual suspect for a significant number of native allocations,
but sometimes developers can directly allocate native memory, too.
Most common approaches are the malloc call by JNI and NIO's direct ByteBuffers
.
java -XX:+PrintFlagsFinal -version | grep Metaspace . . . uintx MetaspaceSize = 21807104 {pd product} uintx MaxMetaspaceSize = 4294901760 {product} . . .
Now that we know the common sources of native memory allocations in the JVM,
let us find out how to monitor them
Enable the native memory tracking
using yet another JVM tuning flag: -XX:NativeMemoryTracking
=off|sumary|detail
java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseG1GC -jar app.jar
Here, we're enabling the NMT while allocating 300 MB of heap space, with G1 as our GC algorithm.
We can use jcmd
commandto get details on native memory usage when NMT is turned on
jcmd <pid> VM.native_memory
Run any hello world java program, make sure we use Thread.sleep(30000) for 30 seconds,
so that should be enough for us to get the process-id i.e. PID and run jcmd
command
For ex:
/**
* Class under Native Memory Tracking
*
* @author manoranjan.kumar
*/
public class App {
public boolean isPalindrome(String input) {
if (input == null) {
throw new IllegalArgumentException("input shouldn't be null");
}
if (input.equals(reverse(input))) {
return true;
}
return false;
}
/**
* just for the illustration purpose
*
* just a plain simple function, not a sophisticated one,
*
* @param input
* @return
*/
private String reverse(String input) {
String reverse = "";
for (int i = input.length() - 1; i >= 0; i--) {
reverse += input.charAt(i);
}
return reverse;
}
public static void main(String[] args) {
System.out.println("Hello World!");
App app = new App();
System.out.println(app.isPalindrome("malayalam"));
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Run the above program:
java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseG1GC App.java
Open up command prompt and run this jps -l
to get the process-id
C:\Users\manoranjan.kumar>jps -l
21864 Eclipse
32840 sun.tools.jps.Jps
11580 com.thecodecache.code_coverage.App
Output:
C:\Users\manoranjan.kumar>jcmd 11580 VM.native_memory
11580:
Native Memory Tracking:
Total: reserved=1712794KB, committed=413186KB
- Java Heap (reserved=307200KB, committed=307200KB)
(mmap: reserved=307200KB, committed=307200KB)
- Class (reserved=1056893KB, committed=4989KB)
(classes #413)
(malloc=125KB #167)
(mmap: reserved=1056768KB, committed=4864KB)
- Thread (reserved=33926KB, committed=33926KB)
(thread #34)
(stack: reserved=33792KB, committed=33792KB)
(malloc=95KB #178)
(arena=39KB #66)
- Code (reserved=249638KB, committed=2574KB)
(malloc=38KB #313)
(mmap: reserved=249600KB, committed=2536KB)
- GC (reserved=61731KB, committed=61731KB)
(malloc=17443KB #2019)
(mmap: reserved=44288KB, committed=44288KB)
- Compiler (reserved=133KB, committed=133KB)
(malloc=2KB #25)
(arena=131KB #3)
- Internal (reserved=1010KB, committed=1010KB)
(malloc=946KB #1827)
(mmap: reserved=64KB, committed=64KB)
- Symbol (reserved=1366KB, committed=1366KB)
(malloc=911KB #99)
(arena=456KB #1)
- Native Memory Tracking (reserved=80KB, committed=80KB)
(malloc=5KB #58)
(tracking overhead=75KB)
- Arena Chunk (reserved=176KB, committed=176KB)
(malloc=176KB)
- Unknown (reserved=640KB, committed=0KB)
(mmap: reserved=640KB, committed=0KB)
Let's analyze the NMT output section by section.
NMT reports the total reserved and committed memory as follows:
Total: reserved=1712794KB, committed=413186KB
Despite allocating 300 MB of heap, the total reserved memory for our app is almost 1.7 GB,
much more than that. Similarly, the committed memory is around 440 MB, which is, again, much more than that 300 MB.
Reserved memory
represents the total amount of memory our app can potentially use.
Conversely, the committed memory
is equal to the amount of memory our app is using right now.
NMT reports our heap allocations as we expected:
Java Heap (reserved=307200KB, committed=307200KB)
(mmap: reserved=307200KB, committed=307200KB)
300 MB of both reserved and committed memory, which matches our heap size settings.
This is what NMT displays about the class metadata for loaded classes:
Class (reserved=1091407KB, committed=45815KB)
(classes #6566)
(malloc=10063KB #8519)
(mmap: reserved=1081344KB, committed=35752KB)
Almost 1 GB reserved and 45 MB committed to loading 6566 classes.
NMT report on thread allocations:
Thread (reserved=33926KB, committed=33926KB)
(thread #34)
(stack: reserved=33792KB, committed=33792KB)
(malloc=95KB #178)
(arena=39KB #66)
In total, 32 MB of memory is allocated to stacks for 34 threads – almost 1 MB per stack.
JVM allocates the memory to threads at the time of creation, so the reserved and committed allocations are equal.
NMT displays about the generated and cached assembly instructions by JIT:
Code (reserved=249638KB, committed=2574KB)
(malloc=38KB #313)
(mmap: reserved=249600KB, committed=2536KB)
Currently, almost 2.5 MB of code is being cached, and this amount can potentially go up to approximately 245 MB.
NMT report about G1 GC's memory usage:
GC (reserved=61731KB, committed=61731KB)
(malloc=17443KB #2019)
(mmap: reserved=44288KB, committed=44288KB)
As we can see, almost 60 MB is reserved and committed to helping G1
.
Let's see how the memory usage looks like for a much simpler GC, say Serial GC:
java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseSerialGC App.java
GC (reserved=1095KB, committed=1059KB)
(malloc=7KB #79)
(mmap: reserved=1088KB, committed=1052KB)
NMT report about the symbol allocations, such as the string table
and constant pool
:
Symbol (reserved=1366KB, committed=1366KB)
(malloc=911KB #99)
(arena=456KB #1)
Almost 1 MB is allocated to symbols.
The NMT allows us to track how memory allocations change over time
First, we should mark the current state of our application as a baseline:
jcmd <pid> VM.native_memory baseline
Then, after a while, we can compare the current memory usage with that baseline:
jcmd <pid> VM.native_memory summary.diff
NMT, using + and – signs, would tell us how the memory usage changed over that period
Total: reserved=1771487KB +3373KB, committed=491491KB +6873KB
Class (reserved=1084300KB +2103KB, committed=39356KB +2871KB)
The total reserved and committed memory increased by 3 MB and 6 MB, respectively.
Other fluctuations in memory allocations can be spotted as easily.
NMT can provide very detailed information about a map of the entire memory space.
To enable this detailed report, we should use the -XX:NativeMemoryTracking
=detail tuning flag.
Virtual memory map:
[0x8f1c1000 - 0x8f467000] reserved 2712KB for Thread Stack
from [Thread::record_stack_base_and_size()+0xca]
[0x8f1c1000 - 0x8f467000] committed 2712KB from [Thread::record_stack_base_and_size()+0xca]
[0x8f585000 - 0x8f729000] reserved 1680KB for Thread Stack
from [Thread::record_stack_base_and_size()+0xca]
[0x8f585000 - 0x8f729000] committed 1680KB from [Thread::record_stack_base_and_size()+0xca]
[0x8f930000 - 0x90100000] reserved 8000KB for GC
from [ReservedSpace::initialize(unsigned int, unsigned int, bool, char*, unsigned int, bool)+0x555]
[0x8f930000 - 0x90100000] committed 8000KB from [PSVirtualSpace::expand_by(unsigned int)+0x95]
[0x902dd000 - 0x9127d000] reserved 16000KB for GC
from [ReservedSpace::initialize(unsigned int, unsigned int, bool, char*, unsigned int, bool)+0x555]
[0x902dd000 - 0x9127d000] committed 16000KB from [os::pd_commit_memory(char*, unsigned int, unsigned int, bool)+0x36]
[0x9127d000 - 0x91400000] reserved 1548KB for Thread Stack
from [Thread::record_stack_base_and_size()+0xca]
[0x9127d000 - 0x91400000] committed 1548KB from [Thread::record_stack_base_and_size()+0xca]
[0x91400000 - 0xb0c00000] reserved 516096KB for Java Heap <--- reserved memory range
from [ReservedSpace::initialize(unsigned int, unsigned int, bool, char*, unsigned int, bool)+0x190] <--- callsite that reserves the memory
[0x91400000 - 0x93400000] committed 32768KB from [VirtualSpace::initialize(ReservedSpace, unsigned int)+0x3e8] <--- committed memory range and its callsite
[0xa6400000 - 0xb0c00000] committed 172032KB from [PSVirtualSpace::expand_by(unsigned int)+0x95] <--- committed memory range and its callsite
[0xb0c61000 - 0xb0ce2000] reserved 516KB for Thread Stack
from [Thread::record_stack_base_and_size()+0xca]
[0xb0c61000 - 0xb0ce2000] committed 516KB from [Thread::record_stack_base_and_size()+0xca]
[0xb0ce2000 - 0xb0e83000] reserved 1668KB for GC
from [ReservedSpace::initialize(unsigned int, unsigned int, bool, char*, unsigned int, bool)+0x555]
[0xb0ce2000 - 0xb0cf0000] committed 56KB from [PSVirtualSpace::expand_by(unsigned int)+0x95]
[0xb0d88000 - 0xb0d96000] committed 56KB from [CardTableModRefBS::resize_covered_region(MemRegion)+0xebf]
[0xb0e2e000 - 0xb0e83000] committed 340KB from [CardTableModRefBS::resize_covered_region(MemRegion)+0xebf]
[0xb0e83000 - 0xb7003000] reserved 99840KB for Code
from [ReservedSpace::initialize(unsigned int, unsigned int, bool, char*, unsigned int, bool)+0x555]
[0xb0e83000 - 0xb0e92000] committed 60KB from [VirtualSpace::initialize(ReservedSpace, unsigned int)+0x3e8]
[0xb1003000 - 0xb139b000] committed 3680KB from [VirtualSpace::initialize(ReservedSpace, unsigned int)+0x37a]
[0xb7003000 - 0xb7603000] reserved 6144KB for Class
from [ReservedSpace::initialize(unsigned int, unsigned int, bool, char*, unsigned int, bool)+0x555]
[0xb7003000 - 0xb73a4000] committed 3716KB from [VirtualSpace::initialize(ReservedSpace, unsigned int)+0x37a]
[0xb7603000 - 0xb760b000] reserved 32KB for Internal
from [PerfMemory::create_memory_region(unsigned int)+0x8ba]
[0xb770b000 - 0xb775c000] reserved 324KB for Thread Stack
from [Thread::record_stack_base_and_size()+0xca]
[0xb770b000 - 0xb775c000] committed 324KB from [Thread::record_stack_base_and_size()+0xca]
Reference: