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: Strong, Weak, Soft,
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.



This comment has been removed by a blog administrator.
ReplyDelete