Understanding the Java memory management system is quite important to develop the memory sensitive applications. It also helps us to optimize the usage of memory in our programs. Java maintains a stack to store all object references whichever are being in use by program. Objects actually stores in memory heap and their references are maintained on memory stack.
Let us try to understand it with some example description. This is not what exactly JVM does, but an example only to simplify the definitions.
As soon as we create an object in program, its information is being saved to memory with some fixed byte pattern. Now we can use the start of memory location as reference id for this object. In program, there are various kind of scopes defined by Java. These are like code block scope, method level scope, class level scope, package scope and whole program level scope etc. These scopes generally has parent <> child <> sibling relations with each other. Now as soon as the program scope initialize, a corresponding scope data storage structure (data structure to save the scope variable and other data which will have the memory references of the actual objects in memory) can be made and pushed to memory stack. Similarly other scope structures can be made as soon as they come in existence and can be stored with their parent scope. These scope data structures remain on stack till the corresponding scope is active in program. As soon as, it gets deactivated; the corresponding data structure will be removed from stack. Take it like as program thread enters into a method, we create a scope data structure for method scope and push it to stack with its parent scope which should be class scope. Now as program thread leaves the method, we remove the corresponding scope data from the stack. Scope can interact with each other as per the language protocol because they have parent <> child relationship. This is a simplified version of memory stack with JVM. Now you can visualize scope data structures used to be created or destroyed with the activation or deactivation of corresponding program scope.
From above example, you can understand that if we need to collect all the data which is actually referred by any program at a point of time, we can simply traverse on the data maintained in the stack. It will give us the access to all live objects data. Memory may still have other objects which are actually not referenced by memory stack due to deactivation of their scope.
Now comes the Java Garbage Collector, whose responsibility is to sweep all the garbage objects remains in memory and leave only those objects which are still referenced on the memory stack. For this, JVM uses 'Mark-Sweep-Compact' three phases technique. Here JVM give three passes to memory objects to destroy the garbage objects. The process is
- In first pass, GC traverse on the Memory Stack and mark all the objects which are referenced from Memory Stack through any scope data structure.
- In second pass, GC claim the space of all unmarked objects i.e. release their memory. Before claiming the objects for collection, Garbage Collector thread call 'finalize' method of the object in order to give it a chance to do the resource cleanup. Here if any object resurrect it again, i.e. do some activity which put its memory reference back to Memory Stack, garbage collector won't collect its memory and leave this. However in this process, GC maintain a state for the objects for which 'finalize' method has already been called once. In next garbage collection cycle, it does not call the 'finalize' method on this object.
- In third pass, garbage collector compact the memory space which was released by second pass and hence made the released memory available for JVM.
This way, GC manage the memory. The process followed to manage the memory is not light in its processing, as three phases process takes a lot of time to execute and hence is a heavy process. Due to this, JVM does not call the GC very often, but it calls it only when it really feels that memory is low and program may need more memory soon. So even if you are calling System.gc(), it does not mean that JVM will call the GC. However it is just like a request to JVM to consider calling the GC. But JVM has its internal logic to decide when to call the GC.
'Finalize' method is used for resource cleaning process by most of the applications. You can use this method to release any resource like ObjectOutputStream, any handle to an OS file etc. But be very careful while relying on this method. Reason is that JVM does not guarantee to call the finalize method. Consider a condition, when program never face a memory pressure and hence JVM never finds the need to call the GC.