Cross Column

Showing posts with label OutOfMemoryError. Show all posts
Showing posts with label OutOfMemoryError. Show all posts

Thursday, August 13, 2020

8 Flavors of OutOfMemoryError

There are total eight different flavors of java.lang.OutOfMemoryError ordered by roughly the frequency of occurrences in my own experience:
  1. Java heap space
  2. GC Overhead limit exceeded
  3. Permgen space
  4. Metaspace
  5. Kill process or sacrifice child
  6. Requested array size exceeds VM limit
  7. Unable to create new native thread
  8. reason stack_trace_with_native_method
New Added (05/17/2021)
In the past, I have authored multiple articles on how to debug and resolve such OutOfMemory (OOM) issues.  Here are a list of them:
  1. Understanding Garbage Collection (HotSpot)
  2. Diagnosing Java.lang.OutOfMemoryError (Memory Analyzer)
  3. Understanding Garbage Collector Output of Hotspot VM (Hotspot)
  4. A Case Study of java.lang.OutOfMemoryError: GC overhead limit exceeded (HotSpot)
  5. How to See a Long Command Line of a Running Process on HotSpot VM (HotSpot)
    • -XX:+HeapDumpOnOutOfMemoryError
  6. Which JVM?
    • Ways to configure JVM options in JDeveloper / WebLogic Server / Eclipse
  7. HotSpot VM Performance Tuning Tips (HotSpot)
  8. HotSpot—java.lang.OutOfMemoryError: PermGen space (HotSpot)
  9. Analyzing the Performance Issue Caused by WebLogic Session Size Too Big (HotSpot)
  10. How to Debug Native OutOfMemory in JRockit (JRockit)
  11. Default Values of JRockit's VM Options (JRockit)
  12. Diagnosing Heap Stress in HotSpot (HotSpot)
  13. JRockit: Parallel vs Concurrent Collectors (JRockit)
  14. Eclipse MAT: Querying Heap Objects Using OQL (Memory Analyzer)
  15. Eclipse MAT: Understand Incoming and Outgoing References (Memory Analyzer)
  16. HotSpot: Monitoring and Tuning Metaspace in JDK 8 (HotSpot)
  17. JDK 8: Thread Stack Size Tuning (HotSpot)
  18. How to Setup a Standalone Memory Analyzer for Windows 10 (Memory Analyzer)

Wednesday, May 29, 2013

How to Debug Native OutOfMemory in JRockit

This is the first time that I have seen the following messages:

Caused By: java.lang.OutOfMemoryError: CG #210992 (2) weblogic/management/configuration/DomainMBeanImpl$Helper.getChildren()Ljava/util/Iterator; in generate_code (compilerfrontend.c:537).
Attempting to allocate 6G bytes
There is insufficient native memory for the Java Runtime Environment to continue.

In this article, we will discuss what native memory is and how to debug running out of native memory in JRockit.

Native Memory vs. Heap Memory


There are two types of memory used by JVM and its applications, all of which are allocated from system memory:
  • Java Heap
    • Java heap is the area of memory used by the JVM to do dynamic memory allocation.
    • The amount of memory used for the heap can be controlled by the following command options:
      • –Xms2g
      • –Xmx2g
    • Heap memory can be garbage collected[4].
  • Native Memory
    • Internal JVM memory management is, to a large extent, kept off the Java heap and allocated natively in the operating system, through system calls like malloc.   This non-heap system memory allocated by the JVM is referred to as native memory. 
    • For JRockit, increasing the amount of available native memory is done implicitly by lowering the maximum Java heap size using –Xmx.
If the heap is too large, it may well be the case that not enough native memory is left for JVM internal usage—bookkeeping, code optimizations, and so on. In that case, the JVM may have no other choice than to throw an OutOfMemoryError from native code (for example, from line 537 of compilerfrontend.c in the previous example).

One example is when several parallel threads perform code optimizations in the JVM. Code optimization typically is one of the JVM operations that consumes the largest amounts of native memory, though only when the optimizing JIT is running and only on a per-method basis.

There are also mechanisms that allow the Java program, and not just the JVM, to allocate native memory, for example through JNI calls. If a JNI call executes a native malloc to reserve a large amount of memory, this memory will be unavailable to the JVM until it is freed.

Code Buffers


JRockit is unique in that it has no bytecode interpreter[1].  The native code is emitted into a code buffer and executed whenever the function it represents is called.  There are two main problems associated with this compile-only strategy:
  • Larger compile-code size
    • This problem is mitigated by garbage collecting code buffers with methods no longer in use.
  • Long compilation time for large methods
    • This problem is solved by having a sloppy mode for the JIT.
    • Sometimes JRockit will use a lot of time generating a relatively large method, the typical example being a JSP.
      • However, once finished, the response time for accessing that JSP will be better than that of an interpreted version.
The problem of running out of memory for metadata in JRockit is not that different from the one in HotSpot, except for that it is native memory instead of heap memory. There are, however, two differences:
  • Cleaning up stale metadata is always enabled by default in JRockit
    • UseCodeGC = true (default)
      • Allow GC of discarded compiled code
    • FreeEmptyCodeBlocks = true (default)
      • Free unused code memory
  • There is no fixed size limit, be default, for the space used to store metadata

JRCMD[2]

When JRockit runs out of native memory and throws an OOM exception, JRCMD can be used for debugging.  JRCMD is a small command-line tool that can be used to interact with a running JRockit instance.  And it can be used to track native memory usage.

There is no need to pre-configure the JVM or the application to be able to later attach the tool. Also, the tool add virtually no overhead, making it suitable for use in live production environments.

The tools.jar in the JDK contains an API for attaching to a running JVM—the Java Attach API. This framework is utilized by JRCMD to invoke diagnostic commands.

For debugging OOM, you can invoke jrcmd with print_memusage command with displayMap argument:

$ ./jrcmd 411 print_memusage displayMap
411:
Total mapped                  3641460KB           (reserved=178564KB)
-              Java heap      2097152KB           (reserved=0KB)
-              GC tables        70156KB
-          Thread stacks        45876KB           (#threads=132)
-          Compiled code        65536KB           (used=45010KB)
-               Internal         1672KB
-                     OS       394836KB
-                  Other       544088KB
-            Classblocks        27392KB           (malloced=26718KB #62502)
-        Java class data       393728KB           (malloced=388547KB #294025 in 62502 classes)
- Native memory tracking         1024KB           (malloced=168KB #10)


+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    OS                          *java    r x 0x0000000000400000.(     76KB)
    OS                          *java    rw  0x0000000000612000.(      4KB)
    OS                        *[heap]    rw  0x000000001e8a0000.( 284976KB)
THREAD                      Stack 457    rwx 0x000000004007c000 (      8KB)
THREAD                      Stack 457        0x000000004007e000 (     12KB)

In the header section, the first column contains the name of a memory space (i.e., "Java Heap") and the second column shows how much memory is mapped for that space. The third column contains details.

In the map section, the first column shows the category of memory chunks:
  • THREAD: Thread related, for example thread stacks.
  • INT: Internal use, for example pointer pages.
  • HEAP: Chunk used by JRockit for the Java heap.
  • OS: Mapped directly from the operating system, such as third party DLLs or shared objects.
  • MSP: Memory space. A memory space is a native heap with a specific purpose, for example native memory allocation inside the JVM.
  • GC: Garbage collection related, for example live bits.
  • CODE: compiled code
When tracking native memory leaks, it is useful to look at how much the memory usage changes over time.  You can do by establishing a baseline first:

$jrcmd 411 print_memusage scale=M baseline

The argument baseline is used to establish a point from which to start measuring.  The scale argument modifies the unit of the amounts of memory in the printout (default is KB).  Once print_memusage is executed with the baseline argument, subsequent calls will include differentials against the baseline.  This can facilitate the monitoring of memory usage changes over time.

References

  1. Oracle JRockit - The Definitive Guide by Marcus Hirt and Marcus Lagergren
  2. Diagnostic Commands (JRCMD)
  3. JNI calls (Wikipedia)
  4. Understanding Garbage Collection (XML and More)
  5. Where did all of these ConstPoolWrapper objects come from?!

Saturday, March 31, 2012

A Case Study of java.lang.OutOfMemoryError: GC overhead limit exceeded

When you see the following exception:
  • java.lang.OutOfMemoryError: GC overhead limit exceeded
it means the garbage collector is taking an excessive amount of time and recovers very little memory in each run.

In this article, we will showcase a real example using the trace records from the garbage collector's output. To learn more on garbage collector's output, read [1] first.

Correlating Timestamps between server.log and gc.log

Two log files generated from Hotspot VM and WebLogic Server are used in this study:
  • server.log
  • gc.log
Note that the names or locations of the log files could be different (which are configurable) in your environment. By default, they are generated in the log directory of the WLS domain.

server.log

In the server log file, we have spotted OutOfMemoryError at 5:21:40 AM as shown below:
  <Mar 30, 2012 5:21:40 AM PDT> <Error> 
  <Kernel> <BEA-000802> <ExecuteRequest failed

  java.lang.OutOfMemoryError: GC overhead limit exceeded.

We have also noted the first timestamp in the server log file is  4:13:56 AM:

  <Mar 30, 2012 4:13:56 AM PDT> <Info> <Security> <BEA-090905> <Disabling CryptoJ

Because gc log file uses elapsed time (in seconds) since server started, we know this out-of-memory incident happened 4064 seconds after server started:
  • 5:21:40 - 4:13:56 = 4064 secs.
gc.log

Searching for the Full GC events, we have found that something went bad at 3167 seconds after server started.

3167.301: [Full GC3168.441:
  [SoftReference, 786 refs, 0.0001560 secs]3168.442:
  [WeakReference, 25548 refs, 0.0041550 secs]3168.446:
  [FinalReference, 7977 refs, 0.0024050 secs]3168.448:
  [PhantomReference, 40 refs, 0.0000070 secs]3168.448:
  [JNI Weak Reference, 0.0000080 secs]
  [PSYoungGen: 100268K->23067K(349568K)]
  [ParOldGen: 2713398K->2752509K(2752512K)]
  2813666K->2775576K(3102080K)
  [PSPermGen: 210744K->210744K(393216K)], 2.8599650 secs]
  [Times: user=18.52 sys=0.03, real=2.85 secs]

3170.809: [Full GC3171.973:
  [SoftReference, 991 refs, 0.0002140 secs]3171.974:
  [WeakReference, 26280 refs, 0.0041760 secs]3171.978:
  [FinalReference, 12136 refs, 0.0142580 secs]3171.992:
  [PhantomReference, 34 refs, 0.0000100 secs]3171.992:
  [JNI Weak Reference, 0.0000080 secs]
  [PSYoungGen: 197915K->32131K(349568K)]
  [ParOldGen: 2752509K->2752511K(2752512K)]
  2950424K->2784643K(3102080K)
  [PSPermGen: 210744K->210744K(393216K)], 2.7171030 secs]
  [Times: user=17.79 sys=0.04, real=2.72 secs]

We have noticed that a series of Full GC's happened after 3167.301 seconds.  For example, the above shows the first two such GC events.  Only after 3.5 seconds, the second Full GC was triggered.  Usually, in between Full GC's, it should be interleaved with multiple Minor GC's.  But, it was not the case.

From examining the GC output, we have concluded that:
  • Full GC's happened too frequent (in about 3 to 4 seconds)
  • Old generation space was full and remained full after garbage collection.
However,  at this time, the exception
  • java.lang.OutOfMemoryError: GC overhead limit exceeded
was not thrown yet.

Based on our calculation, we know that exception was thrown after 4064 seconds after server started.  Here are the garbage collector's output around that time:

4064.456: [Full GC4065.667:
  [SoftReference, 2380 refs, 0.0003730 secs]4065.667:
  [WeakReference, 47627 refs, 0.0091360 secs]4065.677:
  [FinalReference, 3007 refs, 0.0007760 secs]4065.677:
  [PhantomReference, 42 refs, 0.0000070 secs]4065.677:
  [JNI Weak Reference, 0.0000090 secs]
  [PSYoungGen: 174848K->172682K(349568K)]
  [ParOldGen: 2752509K->2752508K(2752512K)]
  2927357K->2925191K(3102080K)
  [PSPermGen: 210598K->210598K(393216K)], 2.3630820 secs]
  [Times: user=16.01 sys=0.05, real=2.37 secs]

4066.837: [Full GC4068.051:
  [SoftReference, 2355 refs, 0.0003750 secs]4068.051:
  [WeakReference, 43590 refs, 0.0081260 secs]4068.060:
  [FinalReference, 6859 refs, 0.0012270 secs]4068.061:
  [PhantomReference, 42 refs, 0.0000220 secs]4068.061:
  [JNI Weak Reference, 0.0000090 secs]
  [PSYoungGen: 174848K->171709K(349568K)]
  [ParOldGen: 2752508K->2752510K(2752512K)]
  2927356K->2924219K(3102080K)
  [PSPermGen: 210598K->210598K(393216K)], 2.8005630 secs]
  [Times: user=18.47 sys=0.02, real=2.81 secs]

Heap Size Adjustment

When a Full GC happens, you need to determine whether it is the occupancy of the old generation space or the occupancy of the permanent generation space that triggers a full garbage collection.  In our case, the trigger was not from the occupancy of the permanent generation space:
  • [PSPermGen: 210598K->210598K(393216K)]
From the above, we know the total permanent generation space was 393216K.  After Full GC, its size remained stable (i.e., 210598K).

However, we know there was serious issue regarding old generation space:
  • [ParOldGen: 2752508K->2752510K(2752512K)]
It became full and remained full after space reclamation.

Conclusion

If you observe an OutOfMemoryError in the garbage collection logs, try increasing the Java heap size (including young generation and old generation) or permanent generation space.  If the issue is coming from old generation, try increasing  the Java heap size up to 80% of the physical memory you have available for the JVM taking into the consideration of the available memory on your system, the memory needed by the OS, the memory needed by other applications running concurrently, and so on.

Based on whether the old generation space or the permanent generation space is running out of memory, you adjust the sizes of heap spaces in this way:
  • For old generation space OutOfMemoryErrors
    • increase -Xms and -Xmx
  • For permanent generation OutOfMemoryErrors
    • increase -XX:PermSize and -XX:MaxPermSize

References

  1. Understanding Garbage Collector Output of Hotspot VM 
  2. Java Tuning White Paper 
  3. Java 2 Platform, Standard Edition 5.0 "Trouobingshooting and Diagnostic Guide"
  4. Which JVM?

© Travel for Life Guide. All Rights Reserved.

Analytical Insights on Health, Culture, and Security.