Why won’t my JVM start with this heap size
JohnPape 0600007J6A Comments (2) Visits (10745)
Many times, while working with Java™-related programs, we run into situations where the Java Virtual Machine (JVM) will not start because of some memory related issues. In this article, we will cover one of these types of these memory-related problems. We will accomplish this through the use of a technique and a freely downloadable tool.
To get the most out of this article, the reader should posses the following skills and knowledge:
Windows 32-bit memory model
The Windows 32-bit memory model, as all 32-bit memory models, only allows for the addressing of 4GB of memory per process. However, all of this memory is not available to the application running in the process. In fact, by default Windows only allows the application to address 2GB of memory. This is called user space. The remaining 2GB of memory is reserved for the administrative overhead associated with running the process and is called kernel space. Memory is divided into regions called segments. Each segment is exactly 256MB in size, thus there are sixteen memory segments in total. The memory addresses for the segments range from 0x00000000 to 0xFFFFFFFF. The diagram below illustrates this:
In the diagram we can also see that the shared libraries (DLL’s) load near the 2GB mark (address 0x80000000 or the 8th shared memory segment). We also notice that, on the Windows platform, the user addressable memory starts in segment 0x00000000, which is right at the beginning of the memory range. Other operating systems reserve memory before as well as after the addressable user space.
When writing the numerical values of the memory segments the trailing 0’s can be omitted for brevity. For example, the segment 0x00000000 can be written as 0x0.
The Windows operating system can be configured to allow applications to address 3GB of user space in the process. This is accomplished by setting a boot flag that signals the kernel to load into the top 1GB of the process space. This results in a slightly different picture of the memory layout:
Note that the shared libraries are still loaded in the same location as the previous diagram.
The memory resources that Windows makes available to applications such as the JVM are not backed solely by physical RAM. Windows, like other operating systems, uses a concept of swap space or paging space. This is an area of disk in which memory pages are written to when physical RAM is needed for another application or service. A page of memory is generally 4Kb in size. The combination of swap space and physical RAM is commonly termed “virtual memory”. It is this combined memory pool that backs memory allocation requests by the applications. With the use of virtual memory, operating systems allow applications to use memory even if the amount of physical RAM is over committed.
It is important to note that, when dealing with the Java runtime environment, not all of the user space is available to the application. Let’s first understand how the JVM works with memory.
How the JVM works with memory
The JVM is firstly a process like any other and requires a certain amount of memory for itself. In the context of the JVM there are two pools of memory: the Java heap and the native heap. The Java heap must be a contiguous chunk of memory. The JVM cannot split the heap into to multiple sections just as it cannot split Java objects into pieces. This means that it is possible to have enough free memory for a JVM process to start but the JVM may fail if a DLL or some other process has pinned a chunk of memory in such a manner that the JVM cannot reserve the contiguous amount of memory requested of it at the command line. The Java heap is the portion of memory in which all Java object allocation occurs. The Java heap must be sized appropriately for the application running in the JVM. A heap too large or too small can result in performance problems and crashes.
The amount of memory that remains in the user space after the Java heap is reserved is called the native heap. This memory is used for runtime data for the JIT compiler, debug data like trace buffers, Java Native Interface (JNI) objects, and resources that underpin Java objects like classes, classloaders, threads, direct byte buffers, and sockets. The amount of native memory required for a JVM will vary heavily based on the type of application being run inside it. A multi-threaded application or a server application that opens and listens on TCP/IP sockets could consume a significant amount of memory and failure to have enough native memory available to the process can result in OutOfMemory errors and crashes. It is possible to have enough memory available to the application in the Java heap yet exhaust the space remaining for native memory.
In the next few sections we’ll deal with a couple of common problems that may prevent you from starting a JVM with the desired amount of Java heap space.
While trying to start a Java application with a maximum heap size of 1GB, you receive the following error:
"JVMJ9VM015W Initialization error for library j9gc24(2): Failed to instantiate heap; 1G requested
Could not create the Java virtual machine.”
But when you look at the available memory the system reports 1.2GB of free memory. You are able to start a JVM with the maximum heap size configured to 800MB. Why won’t the JVM start with 1GB of heap space?
What to do
In this case, it is apparent that the amount of free memory should be sufficient to contain the Java heap and the native heap. In fact, free memory is generally not a concern when running Java applications. Memory allocation by the JVM is not always backed by physical RAM. For best performance, however, it is recommended to keep the JVM in physical RAM since paging memory to and from disk can cause severe drops in performance.
Since the JVM claims that it cannot create the JVM with the requested 1GB of Java heap we can conclude that, while the memory is available, it is not contiguous in nature. This implies that something like a DLL is mapped to a memory address that resides somewhere in the 0-1024M range of the shared memory segments. This range corresponds to shared memory segments 0x00000000, 0x10000000, 0x20000000, and 0x30000000. In order to find the obstructing library we need a tool that can tell us what processes are running and what libraries each process is using. Additionally, we really need to know where the libraries are loaded (in terms of memory address space) and how large they are. To accomplish this, we can obtain a tool called ListDLLs. It can be downloaded ⇒here. This tool is freeware.
Once we have the tool and have run it on our system will we have output similar to this:
ListDLLs v2.25 - DLL lister for Win9x/NT
Copyright (C) 1997-2004 Mark Russinovich
Sysinternals - ⇒www
System pid: 4
smss.exe pid: 632
Command line: Syst
Base Size Vers
0x7c900000 0xb0000 5.01.2600.2180 C:WI
In the example output above, we can see the process ID of the applications running currently on the system as well as the full command line of the process. For the process ID 623 listed above we can see that the smss.exe executable is loaded at memory address 0x48580000 and is 0xf000 bytes long. If we convert these hexadecimal values into decimal values we see that the executable is loaded at 1,213,726,720 (approximately 1.2GB) and it is 64,440 bytes long (approximately 64.4kb). Using this conversion method we can attempt to find potential obstructions to our JVM.
Since we requested 1GB or 1024MB of space for the heap, we know that we are looking at the range of 0MB – 1024MB. Furthermore, we know that we can allocate a JVM with a maximum heap space of 800MB. Therefore, we should be looking for libraries and executables occupying memory in the 0x3 segment. By filtering the results of our ListDLLs output we can determine those items that are loaded into the range we care about. For example, take this filtered list as shown in Figure 3:
The list shows all of the executables and libraries loaded in the 0x3 segment, spanning a range of memory from the 768MB to the 1024MB. Now that we can see the potential culprits for the problem; we just need to correlate the libraries to the processes that own them. By reviewing the original ListDLLs output and searching for the libraries we can add process info to our table as shown in Figure 4:
For simplicity, the converted decimal values for the memory address and size columns have been added.
Now it is apparent to us that the DLL’s in question belong to one of two processes: Windows Explorer and the JVM itself. The first library, msohev.dll appears to be related to Microsoft Office in some way whereas the remaining three libraries are related to our JVM process (in this case, WebSphere Integration Developer which is based on the Rational Software Development Platform v7.0). To resolve the problem with our JVM not starting with a 1GB heap size we can either seek to remove the loaded libraries from the environment by terminating the associated process or if that is not possible, we can seek to rebase the libraries so that they are loaded into higher parts of the shared memory space and thus are more unlikely to be encountered obstructing the contiguity of the memory space used by the JVM.
Rebasing DLL’s is not a trivial task and doing so incorrectly or without full knowledge of what you are doing can cause problems with other applications and possibly the operating system itself. For this reason, once you can determine that you will need to rebase your libraries, it is advisable to make the changes to development or test machines (or something like a virtual machine, such as ⇒VMWare, ⇒VirtualBox, and ⇒VirtualPC) in case things need to be rolled back. Once you are able to remove or move the offending DLL(s) you should be able to create your JVM with the desired 1GB of heap space.
Please leave a comment below if you have questions or connect with me on Twitter ⇒(@jpapejr)