Introduction
The article discusses memory allocation and memory management in Java coding. The article describes what is running beyond Java coding, and what is the actual
impact of each coding word. After that, garbage collection details are discussed.
After reading the article, you would be familiar with memory management in Java applications, and will be able to expect what would be the positive and negative
impacts of your coding on memory usage.
Data types
The Java programming language has two different data types: primitive and non-primitive (reference) data types. Each type is stored and handled
in the memory in a specific way.
Primitive data types
In Java, there are 8 primitive data types (boolean, byte, short, int, long, float, double, char). The variables created according to these data types are stored
in the stack. They are handled like the old way of variables in C, C++. A variable is created and stored in the stack once it is declared. It is
destroyed and its location is released once the variable scope is finished (block, function…etc.).
Figure 1: Primitive variable declaration
After the code in figure 1 is executed, a stack location is created like presented in figure 2.
Figure 2: Stack view after primitive variable declaration
After the function myFunction
is finished, the location in the stack memory will be removed. This stack allocation is done for primitive variables which are
declared as local variables in a function or block. The primitive attributes that are part of an object will be handled like the object itself. The object
(non-primitive data types) handling is described in the next section.
Non-primitive (reference) data types
The big part of variables in Java (as a pure object oriented programming language) is the reference objects part. The reference object types are:
- Built-in wrapper classes (Boolean, Integer, Long…..)
- Other built-in classes (String, Object……)
- Any user defined classes
Reference objects are constructed from two parts: the reference object is stored in the stack, and the referenced
object (real location) is stored in the heap. The next figure describes the impact of object declaration and initialization.
Figure 3: Non-primitive object declaration
After the snippet code in figure 3 runs, the stack memory is affected as described in figure 4.
Figure 4: Stack view after non-primitive object declaration
If you try to use this object now, you will get a null pointer exception because the reference “X” points to no memory location till now.
Figure 5: Non-primitive object declaration and initialization
The snippet code in figure 5 allocates the memory location in the heap of this student object.
Figure 6: Memory view after non-primitive object initialization
Now the real object location is stored in heap memory, a reference holding this location address is stored in the stack. It is something like pointers in C and C++, but with some
differences like, in Java, you don’t have the control to move the pointer to a different memory location like in C, the management of these pointers is the responsibility of the JVM.
This rule is applied for all non-primitive objects in Java. The array is also considered as a non-primitive object. A reference to the array is stored in the stack, while the actual array
nodes are allocated in the heap. This rule explains why the developer should call “new
” during array initialization even if it is an array of primitive data
types like presented in figure 7.
Figure 7: Array object declaration and initialization
The String
class is a special case of classes in Java. You could define a string object by one of the ways presented in figure 8.
Figure 8: String object declaration and initialization
For simplicity, Java enables you to define a String
object without calling “new
” like in
the “y1
” object in figure 8. But both variables defined in figure 8 have a
reference object in the stack, and a heap location containing the actual string value.
While the String
class is immutable, any change in the object value would raise a new location reservation in the
heap and the old location would be kept with no reference. Figures 9 and 10 present sample code and its reflection on the memory locations.
Figure 9: String declaration and initialization example
Figure 10: Memory view of the string example
The object “John” is not needed now because no reference points to it. This point opens the “Garbage Collection” topic.
Garbage collection
Garbage collection is the functionality that Java provides to manage memory and handle all non-primitive objects in the heap memory. The garbage collector is
a low priority
thread that runs from time to time to check if there are any used memory locations and there
are no references to these locations. The garbage collector
returns these used locations to be heap free locations, and could be reused if needed. In the string example in figures 9 and 10, the memory location “John”
is eligible for garbage collection because there is no object referring to it, while “Ali” and “Adams” are not eligible for that, because they are used by objects “x1
” and “y1
”.
The “finalize ()
” function is called automatically on the object before garbage collection releases this object
to memory. Java provides this capability to give the developer the chance to do some destructor work (release resources….etc.).
The garbage collection execution runs in parallel with the main application execution, so the developer
can not force the garbage collector to work, he could only recommend garbage
execution by “System.gc()
”. Although the developer has many ways to manage his memory, one of the ways is: setting a start and maximum heap size as described in figure 11.
Figure 11: Heap memory parameters example
If you take a look at the heap memory details, we will find that the JVM divides the heap memory into three main sections as presented in figure 12.
Figure 12: Heap memory details
As presented in figure 12, the heap memory is constructed from “Young Generation”, “Old Generation”, and “Permanent Area”. The permanent area is not used for application objects,
so it is out of garbage collection work. The new objects are inserted in the young generation area in the first stage (Eden space). Every now and then, garbage
collection performs a minor collecting activity, if the object still has a reference after a subsequent of this minor collecting, the object would be
moved to supervisor 1 and then supervisor 2 if it is still used.
Meanwhile a Major collecting process is performed from time to time (it takes some time
of the main application performance time, because it is more complex than the minor
collecting activity). If the object is still needed, it would be moved to the “old generation” area. The ratio between these young and old areas could be
managed through JVM parameters as described in figure 13, which means the ratio young:old is 1:2.
Figure 13: Heap memory partitions setting parameters
References
History
- Version 1: 15 April 2012, Initial version.