Java Memory Management

 

We all know that Java itself manages the memory and needs no explicit intervention of the programmer. Garbage collector itself ensures that the unused space gets cleaned and memory can be freed when not needed. So what’s the role of programmer and why a programmer needs to learn about the Java Memory Management ? Being a programmer, you don’t need to bother with problems like destroying objects, all credits to the garbage collector. However the automatic garbage collection doesn’t guarantee everything. If we don’t know how the memory management works, often we will end up amidst things that are not managed by JVM (Java Virtual Machine). There are some objects that aren’t eligible for the automatic garbage collection.

Hence knowing the memory management is essential as it will benefit the programmer to write high performance based programs that will not crash, or if does so, the programmer will know how to debug or overcome the crashes.

Introduction:

In every programming language, the memory is a vital resource and is also scarce in nature. Hence it’s essential that the memory is managed thoroughly without any leaks. Allocation and deallocation of memory is a critical task and requires a lot of care and consideration. However in Java, unlike other programming language, the JVM and to be specific Garbage Collector has the role of managing memory allocation so that the programmer needs not to. Whereas in other programming languages such as C the programmer has direct access to the memory who allocates memory in his code, thereby creating a lot of scope for leaks.

The major concepts in Java Memory Management :

·        JVM Memory Structure

·        Working of Garbage Collector

 

1. Java Memory Model Structure

 

Whenever we execute a Java program, a separate memory area is reserved for storing various parts of our application code which we roughly call JVM memory. Though not necessary, having some knowledge about the structuring of this memory area is quite beneficial.

Knowing Java memory model becomes more important when we start working on complex tasks like performance tuning. Without having a good understanding of how JVM actually consumes the memory and how garbage collector uses different parts of this volatile memory, we may miss some important considerations for better memory management, thus better performance.



Memory Structure

 

1.1 STACK

Introduction

The stack is a very efficient linear data structure. The allocation happens on a contiguous block of memory. 

In this operation, data are added by pushing it to the top of the memory. Each time when a new item is added, the existing data is pushed to the bottom while the latest stays on top. Consequently, the latest is initially accessible. Operations such as pulling or popping of data are only possible from the top (First In Last Out). Besides, the allocation and deallocation are automatically carried out.

Applications

Considering the efficiency of Stack, the java virtual machine (JVM) has been developed to manage memory via Stack. A stack is a tightly managed data structure and java can maintain very tight scoping rules with the stack. In java, all local variables are created on the stack and they are automatically popped from the stack when you reach the close of the block that created the variable. Also, each thread has its stack. Therefore, data on a stack can only be seen by the thread that owns it. 

Primitive variables are local variables and local variables like ‘int’ and ‘double’  are stored in a stack.

Pros and Cons

The cost factor is minimal as the access time is faster. However, a shortage of memory can be an issue as it has a fixed size. Likewise, its implementation can be complicated, too.

1.2 Heap Memory

Heap memory is a part of memory allocated to JVM, which is shared by all executing threads in the application. It is the part of JVM in which all class instances and are allocated. It is created on the Start-up process of JVM. It does not need to be contiguous, and its size can be static or dynamic. Space allocated to the memory is reclaimed through an automatic memory management process called garbage collection. Heap memory is a shared area that is utilized during the runtime of Java applications. It is created during the instantiation of Java Virtual Machine (JVM).

 

This memory is shared by instances of all the classes created during the runtime of an application. As per system configuration, the size of heap memory may be fixed or variable. In order to reclaim the space of heap memory, an automatic memory management process called garbage collection is triggered by JVM. JVM provides control to developers to vary the size of heap memory according to requirement.

The functioning of Heap Memory

Java Heap memory is basically divided into two parts:

1. Young Generation

This is a part of java heap memory in which newly created objects are allocated and fully reserved for allocating objects. When space allocated to the young generation gets filled, a process called Minor GC (Garbage collection) is triggered by JVM to clean up space by removing unreferenced objects. The young generation is further divided into the following parts:

  • Eden Space: All newly created objects are first allocated to the Eden space of a young generation. It is a larger area of the young generation, and after this Eden space gets filled, Minor GC is triggered, which removes unreferenced objects and moves referenced objects that are survivor objects into another part of the young generation called survivor space.
  • Survivor Space: Collection of objects which survived after Minor GC is moved to this area. Survivor space is further subdivided into two parts called survivor to (s0) and survivor from (s1). At the time of Minor GC, objects from one survivor space are moved to other survivor space that is one of the survivor space is always empty. After many cycles of minor GC, survived objects from the young generation are moved to the old generation. Generally, objects are moved to the old generation after crossing the threshold set corresponding to the young generation’s age.

2. Old Generation

Long living objects are moved to the old generation. Objects survived after many cycles of minor GC are considered old enough to be accommodated into this old space. When space in the old generation gets filled, Major GC (Garbage collection) is triggered by JVM to clean up old space resources. Usually, the process of major garbage collection takes more time than minor garbage collection. 

If garbage collection is not able to clean space to accommodate new objects, an OutOfMemory error is thrown by JVM. To overcome this error, heap size needs to be increased, or proper memory management is required, which can be done through proper understanding of objects created during the application and which objects are taking more space.

 

Importance

The importance of heap memory can be summarized using the following points:

  • With the help of heap memory, we can find the smallest and largest number.
  • Garbage collection runs of heap memory to free up space.
  • Heaps memory allows global access to variables.
  • There is no limit on memory size.
  • The priority queue is implemented using heap memory.

Pros and Cons

Memory fragmentation is the main issue. Cost factors can become an issue, too. The access time is slower.  However, resizing is possible and the memory is sufficient. Its implementation is easier.

1.3. Method area and runtime constant pool

Method area stores per-class structures such as the runtime constant pool; field and method data; the code for methods and constructors, including the special methods used in class, instance, and interface initialization.

The method area is created on the virtual machine startup. Although it is logically a part of the heap it can or cannot be garbage collected, whereas we already read that garbage collection in heap is not optional; it’s mandatory. The method area may be of a fixed size or may be expanded as required by the computation and may be contacted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.

1.4. Native method stacks

Native method stacks are called C stacks; they support native methods (methods are written in a language other than the Java programming language), typically allocated per each thread when each thread is created. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks.

The size of native method stacks can be either fixed or dynamic.

1.5. PC registers

Each of the JVM threads has its own program counter (pc) register. At any point, each of the JVM threads is executing the code of a single method, namely the current method for that executing thread.

As the Java applications can contain some native code (for example, using native libraries), we have two different ways for native and non-native methods. If the method is not native (that is, a Java code), the PC register contains the address of the JVM instruction currently being executed. If the method is native, the value of the JVM’s PC register is undefined.

The Java Virtual Machine’s pc register is wide enough to hold a return address or a native pointer on the specific platform.

1.6 Reference Type

There are four types of references: StrongWeakSoft, and Phantom reference. The difference among the types of references is that the objects on the heap they refer to are eligible for garbage collecting under the different criteria.

Strong reference: It is very simple as we use it in our daily programming. Any object which has Strong reference attached to it is not eligible for garbage collection. We can create a strong reference by using the following statement:

StringBuilder sb= new StringBuilder();  

Weak Reference: It does not survive after the next garbage collection process. If we are not sure when the data will be requested again. In this condition, we can create a weak reference to it. In case, if the garbage collector processes, it destroys the object. When we again try to retrieve that object, we get a null value. It is defined in java.lang.ref.WeakReference class. We can create a weak reference by using the following statement:

WeakReference<StringBuilder> reference = new WeakReference<>(new StringBuilder());  

Soft Reference: It is collected when the application is running low on memory. The garbage collector does not collect the softly reachable objects. All soft referenced object s are collected before it throws an OutOfMemoryError. We can create a soft reference by using the following statement:

SoftReference<StringBuilder> reference = new SoftReference<>(new StringBuilder()); 

Phantom Reference: It is available in java.lang.ref package. It is defined in java.lang.ref.PhantomReference class. The object which has only phantom reference pointing them can be collected whenever garbage collector wants to collect. We can create a phantom reference by using the following statement:

PhantomReference<StringBuilder> reference = new PhantomReference<>(new StringBuilder());  

2. Garbage Collection Process

As discussed earlier, depending on the type of reference that a variable from the stack holds to an object from the heap, at a certain point in time, that object becomes eligible for the garbage collector.



 

Garbage-eligible objects

For example, all objects that are in red are eligible to be collected by the garbage collector. You might notice that there is an object on the heap, which has strong references to other objects that are also on the heap (e.g. could be a list that has references to its items, or an object that has two referenced type fields). However, since the reference from the stack is lost, it cannot be accessed anymore, so it is garbage as well.

To go a bit deeper into the details, let’s mention a few things first:

This process is triggered automatically by Java, and it is up to Java when and whether or not to start this process.

It is actually an expensive process. When the garbage collector runs, all threads in your application are paused (depending on the GC type, which will be discussed later).

This is actually a more complicated process than just garbage collecting and freeing up memory.

Even though Java decides when to run the garbage collector, you may explicitly call System.gc() and expect that the garbage collector will run when executing this line of code, right?

This is a wrong assumption.

You only kind of ask Java to run the garbage collector, but it’s, again, up to it whether or not to do that. Anyway, explicitly calling System.gc() is not advised.

Since this is a quite complex process, and it might affect you performance, it is implemented in a smart way. A so-called “Mark and Sweep” process is used for that. Java analyzes the variables from the stack and “marks” all the objects that need to be kept alive. Then, all the unused objects are cleaned up.

So actually, Java does not collect any garbage. In fact, the more garbage there is, and the fewer that objects are marked alive, the faster the process is. To make this even more optimized, heap memory actually consists of multiple parts. We can visualize the memory usage and other useful things with JVisualVM, a tool that comes with the Java JDK. The only thing you have to do is install a plugin named Visual GC, which allows you to see how the memory is actually structured. Let’s zoom in a bit and break down the big picture:



Heap memory generations 

When an object is created, it is allocated on the Eden(1) space. Because the Eden space is not that big, it gets full quite fast. The garbage collector runs on the Eden space and marks objects as alive.

Once an object survives a garbage collecting process, it gets moved into a so-called survivor space S0(2). The second time the garbage collector runs on the Eden space, it moves all surviving objects into the S1(3) space. Also, everything that is currently on S0(2) is moved into the S1(3) space.

If an object survives for X rounds of garbage collection (X depends on the JVM implementation, in my case it’s 8), it is most likely that it will survive forever, and it gets moved into the Old(4) space.

Taking everything said so far, if you look at the garbage collector graph(6), each time it has run, you can see that the objects switch to the survivor space and that the Eden space gained space. And so on and so forth. The old generation can be also garbage collected, but since it is a bigger part of the memory compared to Eden space, it does not happen that often. The Metaspace(5) is used to store the metadata about your loaded classes in the JVM.

The presented picture is actually a Java 8 application. Prior to Java 8, the structure of the memory was a bit different. The metaspace is called actually the PermGen. space. For example, in Java 6, this space also stored the memory for the string pool. Therefore, if you have too many strings in your Java 6 application, it might crash.

Garbage Collector Types

The JVM actually provides four different garbage collectors. Each garbage collector will vary in Application throughput and Application pause. Application throughput denotes the speed at which a Java application runs and Application pause means the time taken by the garbage collector to clean the unused memory spaces.

1.     Serial Garbage Collector: This is the simplest GC implementation, as it basically works with a single thread. If we select Serial garbage collector as our default garbage collector then whenever we will call garbage collector to clean unused memory then serial garbage collector holds all the running threads of application i.e. It works by freezing all the application threads and It will create a single thread to perform garbage collection. It freezes all other running threads of application until garbage collection operations have concluded. If we use Serial Garbage Collector as our default garbage collector then the application throughput will decrease and application pause time will increase. As a result, this GC implementation freezes all application threads when it runs. Hence, it is not a good idea to use it in multi-threaded applications like server environments.

Implementation:
If you want to use serial garbage collector, then we have to explicitly mention while running jar like:

java -XX:+UseSerialGC -jar GFGApplicationJar.java

 

2.     Parallel Garbage Collector: Parallel Garbage Collector is the default garbage collector in Java 8. It is also known as Throughput collector. Parallel Garbage Collector is same as Serial Garbage Collector because Parallel Garbage Collector also freezes the running threads of the application while performing the garbage collection. But the difference is, Parallel Garbage Collector uses multiple threads to perform cleaning of unused heap area. The advantage of using Garbage Collector as default GC is we can mention few attributes for the garbage collector, like

 

How many threads, can garbage collector use to perform garbage collection.

 

Implementation:

java -XX:+UseParallelGC -XX:ParallelGCThreads=NumberOfThreads -jar GFGApplicationJar.java

 

Maximum pause can garbage collector take while performing garbage collection

 

Implementation:

java -XX:+UseParallelGC -XX:MaxGCPauseMillis=SecInMillisecond -jar GFGApplicationJar.java

The parallel garbage collector is far better than serial garbage collector but one problem with the parallel garbage collector is it pauses the application during minor operations also. It is best suited if applications that can handle such pauses. If we are using JDK 8 then parallel GC is the default garbage collector.

Implementation: If we are running on java 9 and want to use parallel garbage collector then we should use below command:

java -XX:+UseParallelGC -jar GFGApplicationJar.java

3.     CMS Garbage collector: CMS Garbage collector is known as concurrent mark-sweep garbage collector. This garbage collector uses multiple threads to scan the heap memory consistently to the mark objects that are unused and then sweep the marked objects. As we know, Serial garbage collector and Parallel garbage collector freeze the running threads of the application while performing the garbage collection. But CMS Garbage collector will perform freezing of running threads i.e. application pause in two cases only:

 

While performing the garbage collection, If there is a change in heap memory in parallel.

While marking the referenced objects in the old generation space.

If we compare CMS collector with Parallel garbage collector, CMS collector uses more CPU to ensure better application throughput. If we are developing an application where we can provide more CPU resources for better performance then CMS garbage collector is the blockquoteferred choice over the parallel collector. To enable CMS Garbage Collector, we can use the following argument:

java -XX:+UseParNewGC -jar GFGApplicationJar.java

4.     G1 Garbage Collector: Firstly G1 Garbage Collector is introduced in JDK 7. Initially, It was designed to provide better support for larger heap memory application. G1 Garbage Collector is the default garbage collection of Java 9. G1 collector replaced the CMS collector since it’s more performance efficient. How G1 Garbage Collector works is different from other collectors. Unlike other collectors, the G1 collector partitions the heap space into multiple equal-sized regions. Basically it is mainly designed for an application having heap size greater than 4GB. It divides the heap area into multiple regions vary from 1MB to 32MB. While performing the garbage collection, G1 Garbage Collector mark the heap region which has objects that are in use throughout the heap. By the help of this garbage collector has the information about the regions that contains most use less objects and garbage collector first perform the garbage collection on that region only. Thats why it is known as G first garbage collector. G1 also does compact the free heap space just after garbage collection that makes G1 Garbage Collector better than other garbage collectors. G1 Garbage Collector is the default garbage collector of Java 9.

 

Implementation: If we are using Java version less than 9 and we want to use G1 Garbage Collector then we have to mention explicitly while running jar file like:

 

java -XX:+UseG1GC -jar GFGApplicationJar.java

Conclusion

Memory management in Java is a very interesting topic but very broad topic too, Java itself will manage the memory without major interventions by the programmer, but knowing how memory work in will give you the advantage of writing good and optimized code in terms of memory usage, finding and fixing memory leaks.

 

Comments

  1. This comment has been removed by a blog administrator.

    ReplyDelete

Post a Comment