• preface

The JVM, short for Java Virtual Machine (Java Virtual Machine), is also an imaginary computer that emulates various computer functions on an actual computer. One of the most important features of the Java language, automatic garbage collection mechanism, is also based on JVM implementation, and in our daily development often encounter memory overflow, memory leakage, program lag and other problems, to solve these problems and performance tuning. We need to understand the JVM’s Garbage Collection (GC) mechanism, so how is the Garbage Collection mechanism implemented? Let’s find out.

Run time data area

Before we understand the JVM’s garbage collection mechanism, we need to understand how the JVM’s runtime data areas are divided to know where garbage is generated and collected.

During the execution of Java programs, the Java VIRTUAL machine divides the memory it manages into different data areas. Each of these regions has its own purpose and time of creation and destruction. Some regions exist with the start of a virtual machine process, while others are created and destroyed depending on the start and end of a user thread. According to the Java Virtual Machine Specification (Java SE Version 7), the memory managed by the Java Virtual Machine will contain the following run-time data areas, as shown in figure 7.

1. Program counter

The Program Counter Register is a small memory space that can be thought of as a line number indicator of the bytecode being executed by the current thread. In the concept of virtual machine model (just the conceptual model, all kinds of virtual machine may be through some of the more efficient way to achieve), bytecode interpreter to work is by changing the counter value to select a need to be performed under the bytecode instruction, branches, loops, jumps, exception handling, thread to restore the basic function such as all need to rely on the counter. Because multithreading in the Java VIRTUAL machine is implemented by the way threads alternate and allocate processor execution time, at any given moment, one processor (or kernel for multi-core processors) will execute instructions in only one thread. Therefore, in order to recover to the correct execution position after thread switching, each thread needs to have an independent program counter, which is not affected by each other and stored independently. We call this kind of memory area “thread private” memory. If the thread is executing a Java method, this counter records the address of the virtual machine bytecode instruction being executed. If the Native method is being executed, this counter value is null (Undefined). This memory region is the only one where the Java Virtual Machine specification does not specify any OutOfMemoryError cases.

2. Java VM stack

Like program counters, the Java Virtual Machine Stack is thread-private and has the same lifetime as a thread. The virtual machine Stack describes the memory model of Java method execution: each method execution creates a Stack Frame to store information about local variables, operand stacks, dynamic links, method exits, and so on. The process of each method from invocation to completion corresponds to the process of a stack frame being pushed into and out of the virtual machine stack. The local variable table stores basic data types known at compile time (Boolean, byte, char, short, int, float, long, double), object references (reference type, which is not equivalent to the object itself, may be a reference pointer to the object’s starting address. They can also point to a handle representing an object or other location associated with that object) and the returnAddress type (which points to the address of a bytecode instruction). 64-bit long and double data occupy two local variable slots, and the rest occupy only one. The memory space required for the local variable table is allocated at compile time. When entering a method, how much local variable space the method needs to allocate in the frame is completely determined, and the size of the local variable table does not change during the method run. In the Java Virtual Machine specification, two exceptions are specified for this area: a StackOverflowError is thrown if the stack depth of a thread request is greater than the depth allowed by the virtual machine; If the virtual stack can be dynamically extended (as most Java virtual machines currently do, although the Java Virtual Machine specification also allows fixed-length virtual stacks), an OutOfMemoryError will be thrown if sufficient memory cannot be allocated during the extension.

3. Local method stack

The Native Method Stack is very similar to the virtual machine Stack. The difference is that the virtual machine Stack performs Java methods (that is, bytecode) services for the virtual machine, while the Native Method Stack serves the Native methods used by the virtual machine. The virtual machine specification does not mandate the language, usage, or data structure of methods in the local method stack, so specific virtual machines are free to implement it. There are even virtual machines (such as the Sun HotSpot VIRTUAL machine) that simply merge the local method stack with the virtual machine stack. Like the virtual stack, the local method stack area throws StackOverflowError and OutOfMemoryError exceptions.

4. The Java heap

For most applications, the Java Heap is the largest chunk of memory managed by the Java virtual machine. The Java heap is an area of memory that is shared by all threads and is created when the virtual machine is started. The sole purpose of this memory area is to hold object instances, and almost all object instances are allocated memory here. This is described in the Java Virtual Machine specification as follows: All object instances and arrays are allocated on the heap, but as JIT compilers and escape analysis techniques mature, allocation on the stack and scalar replacement optimization techniques can lead to subtle changes that make it less “absolute” to allocate all objects on the heap. The Java Heap is the primary area managed by the Garbage collector and is often referred to as the “Garbage Collected Heap”. From the point of view of memory collection, the Java heap can be subdivided into: new generation and old generation; More detailed are Eden space, From Survivor space, To Survivor space, etc. From the perspective of memory Allocation, the Java heap shared by threads may have multiple Thread private Allocation buffers (TLabs). However, no matter how to partition, it has nothing to do with the storage content, no matter which area, the storage is still the object instance, the purpose of further partition is to better reclaim memory, or faster allocation of memory. According to the Java Virtual Machine specification, the Java heap can be in a physically discontinuous memory space, as long as it is logically contiguous, like our disk space. When implemented, it can be either fixed size or extensible, but most current virtual machines are implemented as extensible (controlled by -xmx and -xMS). OutOfMemoryError is thrown if there is no memory in the heap to complete the instance allocation and the heap can no longer be extended.

5. Methods area

The Method Area, like the Java heap, is an Area of memory shared by threads that stores information about classes loaded by the virtual machine, constants, static variables, code compiled by the just-in-time compiler, and so on. Although the Java Virtual Machine specification describes the method area as a logical part of the Heap, it has an alias called non-heap, which is supposed to distinguish it from the Java Heap. The Java Virtual Machine specification is very relaxed about method areas, which, like the Java heap, do not require continuous memory and can be either fixed size or extensible, but optionally do not implement garbage collection. Garbage collection is relatively rare in this area, but it is not as “permanent” as the name of the permanent generation that data enters the method area. The target of memory reclamation in this area is mainly for constant pool reclamation and type unloading. Generally speaking, the “performance” of the reclamation in this area is not satisfactory, especially for type unloading, but the reclamation in this part of the area is indeed necessary. According to the Java Virtual Machine specification, OutOfMemoryError is thrown when the method area cannot meet memory allocation requirements.

6. Runtime constant pool

The Runtime Constant Pool is part of the method area. The Constant Pool Table is used to store various literals and symbolic references generated at compile time. This part of the Constant Table is stored in the runtime Constant Pool after the Class is loaded into the method area. The Java Virtual Machine has strict rules on the format of each part of a Class file (including the constant pool, of course). Each byte must be used to store what data is accepted, loaded, and executed by the VIRTUAL machine. However, the Java Virtual Machine specification does not specify any details about runtime constant pools. Virtual machines implemented by different vendors can implement this memory area according to their needs. However, in general, in addition to storing symbolic references described in Class files, translated direct references are also stored in the runtime constant pool. Runtime constant pool relative to the Class file another important feature of the constant pool is dynamic, the Java language does not require constant must only compile time to produce, is not preset constant pool into the Class file content can enter method area runtime constant pool, runtime might also put new constants in a pool, One feature that developers use most often is the Intern () method of the String class. Since the runtime constant pool is part of the method area and is naturally limited by the method area memory, OutOfMemoryError is thrown when the constant pool can no longer claim memory.

7. Direct memory

Direct Memory is not part of the run-time data region of the virtual machine, nor is it defined in the Java Virtual Machine specification. But this part of memory is also frequently used and can cause OutofMemoryErrors, so we’ll cover it here. The NIO (New Input/Output) class was introduced in JDK 1.4, introducing a Channel and Buffer based I/O method that can allocate off-heap memory directly using Native libraries. It then operates through a DirectByteBuffer object stored in the Java heap as a reference to this memory. This can significantly improve performance in some scenarios because it avoids copying data back and forth between the Java heap and Native heap. Obviously, the allocation of native direct memory is not limited by the Size of the Java heap, but since it is memory, it is certainly limited by the size of the total native memory (including RAM and SWAP or paging files) and the addressing space of the processor. When configuring VM parameters, the server administrator sets parameters such as -xmx based on the actual memory. However, the direct memory is often ignored. As a result, the sum of memory regions exceeds the physical memory limit (including the physical and OS limits), and an OutOfMemoryError occurs during dynamic expansion.

Ii. How to find the “garbage”

Said earlier, in the heap stored inside the Java world almost all the object instance, the garbage collector before recycling was carried out on the pile, the first thing is to determine which of these objects was still “alive”, which is “dead”, the garbage objects can be simply interpreted as may no longer be any way to use the object, How to distinguish these dead and alive objects requires garbage judgment algorithms, mainly reference counting method and reachability analysis method.

1. Reference counting

Add a reference counter to the object, incrementing it every time a reference is made to it; When a reference is invalid, the counter value is reduced by 1; An object whose counter is 0 at any point in time cannot be used again. Objects with a zero reference count are garbage and can be collected. Microsoft’s Component Object Model (COM) technology, FlashPlayer using ActionScript 3, Python language and Squirrel, widely used in the field of game scripting, all use reference counting algorithm for memory management.

  • Advantages: simple implementation, high judgment efficiency.
  • Disadvantages: Requires extra space to store counters, making it difficult to detect circular references between objects.

No mainstream Java virtual machine uses reference counting, mostly because it is difficult to solve the problem of circular references between objects. The reference counter is 1, and the object should be recycled at the end of the cycle, but it cannot be recycled, causing a memory leak.

public class TestReferenceCounter {

    public static void main(String[] args) {
        TestObject obj1 = new TestObject();
        TestObject obj2 = new TestObject();
        obj1.instance = obj2;
        obj2.instance = obj1;
        obj1 = null;
        obj2 = null;
    }

    static class TestObject { Object instance; }}Copy the code

2. Reachability Analysis

In the mainstream implementations of major commercial programming languages (Java, C#, and even the aforementioned archaic Lisp), reachability analysis is used to determine whether an object is alive or not. The basic idea of this algorithm is:

  • Search down from these nodes through a series of objects called “GC Roots” as starting points;
  • The search path is called the Reference Chain.
  • When an object is not connected to GC Roots by any reference chain (in graph theory terms, it is unreachable from GC Roots to this object), the object is proved to be unavailable.

As shown in the figure below, object 5, Object 6, and Object 7 are related to each other, but they are not reachable to GC Roots, so they will be judged as recyclable objects.

In the Java language, objects that can be used as GC Roots include the following:

  • The object referenced in the virtual machine stack (the local variable table in the stack frame).
  • The object referenced by the class static property in the method area.
  • The object referenced by the constant in the method area.
  • Objects referenced by JNI (commonly referred to as Native methods) in the Native method stack.

Advantages and disadvantages of accessibility analysis:

  • Advantages: Can solve the problem of circular reference, do not need to take up extra space;
  • Disadvantages: the implementation is more complex, need to analyze a lot of data, consume a lot of time; The analysis process requires GC pauses (reference relationships cannot change), that is, pauses all Java threads of execution (called “Stop The World”, which is The focus of garbage collection);

3. Reference relationships

Whether the number of references is determined by reference counting algorithm or whether the reference chain of an object is reachable by reachability analysis algorithm, determining whether an object is alive or not is related to “reference”.

3.1. Java references before JDK 1.2:

If the value stored in a reference data type represents the starting address of another chunk of memory, the chunk is said to represent a reference. This definition is too narrow to describe much.

3.2. After JDK 1.2, Java extended the concept of reference into strong, soft, weak and virtual reference, and the four kinds of reference intensity gradually decreased.

  • Strong Reference

Strong references are common references to program code such as “Object obj = new Object ()”. As long as strong references exist, the garbage collector will never reclaim the referenced Object.

Test code:

        Object object = new Object();
        System.out.println("Strong Reference object = " + object);
        System.gc();
        Thread.sleep(500);
        System.out.println("Strong Reference gc object = " + object);
Copy the code

Running results:

Strong Reference object = java.lang.Object@39ba5a14
Strong Reference gc object = java.lang.Object@39ba5a14
Copy the code
  • Soft Reference

Soft references are used to describe objects that are useful but not necessary. Objects associated with soft references are listed in the collection scope for a second collection before the system is about to run out of memory. An out-of-memory exception is thrown if there is not enough memory for this collection. After JDK 1.2, a SoftReference class was provided to implement soft references.

Test code:

        Object object = new Object();
        SoftReference<Object> softReference = new SoftReference<>(object);
        object = null;
        System.out.println("Soft Reference object = " + softReference.get());
        System.gc();
        Thread.sleep(500);
        System.out.println("Soft Reference gc object = " + softReference.get());
Copy the code

Running results:

Soft Reference object = java.lang.Object@39ba5a14
Soft Reference gc object = java.lang.Object@39ba5a14
Copy the code
  • Weak Reference

Weak references are also used to describe non-essential objects, but they are weaker than soft references, and objects associated with weak references only survive until the next garbage collection occurs. When the garbage collector works, objects associated only with weak references are reclaimed regardless of whether there is currently enough memory. After JDK 1.2, WeakReference classes were provided to implement weak references.

Test code:

        WeakReference<Object> weakReference = new WeakReference<>(new Object());
        System.out.println("Weak Reference object = " + weakReference.get());
        System.gc();
        Thread.sleep(500);
        System.out.println("Weak Reference gc object = " + weakReference.get());
Copy the code

Running results:

Weak Reference object = java.lang.Object@511baa65
Weak Reference gc object = null
Copy the code
  • Phantom Reference

Virtual references, also known as ghost references or phantom references, are the weakest type of reference relationship. The existence of a virtual reference does not affect the lifetime of an object, nor can an object instance be obtained through a virtual reference.

Virtual references are mainly used to track the activity of objects being collected by the garbage collector. A virtual reference differs from a soft or weak reference in that:

A virtual reference must be used in conjunction with a ReferenceQueue. When the garbage collector is about to reclaim an object and finds that it has a virtual reference, it adds the virtual reference to the reference queue associated with it before reclaiming the object’s memory.

       ReferenceQueue<Object> phantomReferenceQueue = new ReferenceQueue<>();
       // Create a virtual reference that must be associated with a reference queue
        PhantomReference<Object> phantomReference = new PhantomReference<>(new Object(), phantomReferenceQueue);
Copy the code

A program can determine whether a referenced object is about to be garbage collected by determining whether a virtual reference has been added to the reference queue. If the program finds that a virtual reference has been added to the reference queue, it can take the necessary action before the memory of the referenced object is reclaimed.

4. To be or not to be

An object marked unreachable in the reachability analysis algorithm is not necessarily reclaimed, and it has a second chance to regenerate. Each object should be marked twice before being recycled. One is to mark the object with no associated reference chain, and the second is to judge whether the object overwrites the Finalize () method. If it does not, the “death penalty” is really set.

If the OBJECT is determined by the JVM to be necessary to finalize(), the object is put into an F-queue and later executed by a low-priority Finalizer thread automatically created by the virtual machine. By “execute,” I mean that the virtual machine fires the method, but it does not wait for it to finish. Virtual machine is optimized here, because if an object runs in Finalize method for a long time or has an infinite loop, other objects in f-queue may wait forever, and even the whole memory reclamation system may crash.

We can reincarnate this object in finalize() as follows:

public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive(a) {
        System.out.println("yes,i am still alive");
    }

    @Override
    protected void finalize(a) throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        // The object successfully saves itself for the first time
        SAVE_HOOK = null;
        System.gc();
        // Since the Finalize method has a low priority, we pause for 0.5 seconds to wait for it
        Thread.sleep(500);
        if(SAVE_HOOK ! =null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no,i am dead");
        }
        // The following code is exactly the same as the above, but the self-rescue fails
        SAVE_HOOK = null;
        System.gc();
        // Since the Finalize method has a low priority, we pause for 0.5 seconds to wait for it
        Thread.sleep(500);
        if(SAVE_HOOK ! =null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no,i am dead"); }}}Copy the code

Running results:

The finalize method executed! yes,i am still alive no,i am deadCopy the code

As can be seen from the running results, the first rescue succeeds, and the second rescue fails. Therefore, it should be noted that finalize() method will only be called once by the system, and only the first time will be called by GC for multiple times, so there is only one chance for regeneration.

5. Recycling method area

If a String “ABC” is already in the constant pool, but the system does not have any String objects that are “ABC”, the object should be reclaimed. The garbage collection of the method area (the persistent generation in the HotSpot VIRTUAL machine) mainly recycles two parts: discarded constants and useless classes. For example, the above “ABC” is a discarded constant, so which classes are useless?

  • All instances of the class have been reclaimed, that is, there are no instances of the class in the Java heap;
  • The ClassLoader that loaded the class has been reclaimed;
  • The java.lang.Class object corresponding to this Class is not referenced anywhere, and the methods of this Class cannot be accessed anywhere through reflection.

Garbage collection algorithm

From the introduction above, we already know what garbage is and how to determine if an object is garbage. So, let’s learn how to recycle garbage, which is what the garbage collection algorithm and garbage collector need to do.

1. Mark-sweep algorithm

“Tag is the most basic collection algorithms – clear” (Mark – Sweep) algorithm, is divided into “tag” and “remove” in two stages: first, to Mark the required collection of object, the Mark after the completion of the unified recycling all the marked drop objects, its Mark is the accessibility analysis method to determine the garbage in front of the marking process of objects.

The following figure shows the execution process of the “mark-clean” algorithm:

Advantages:

  • No object movement is required, and only non-viable objects are processed, which is extremely efficient in the case of a large number of viable objects.

Disadvantages:

  • Both the labeling and cleaning processes are inefficient;
  • Memory fragmentation is easy to occur. Too much fragmentation space may cause failure to store large objects.

2. Mark-compact algorithm

The marking process of mark-tidy algorithm is the same as the marking process of “mark-clean” algorithm, but the processing of garbage objects after marking is different. Instead of cleaning up the recyclable objects directly, it moves all objects to one end and then directly cleans up the memory beyond the end boundary.

Below is a schematic diagram of the “mark-tidy” algorithm:

Advantages:

  • After sorting, the allocation of new objects only needs to be completed by pointer collision, relatively simple; With this method, the location of the free area is always known and there is no fragmentation problem.

Disadvantages:

  • The duration of GC pauses increases because you need to copy all objects to a new location and update their reference addresses.

Copying algorithms

Replication algorithm is proposed to solve the problem of efficiency and garbage recovery of heap debris. It divides the available memory by capacity into two equally sized pieces, one of which is used at a time. When this area of memory is used up, the surviving objects are copied to the other area, and the used memory space is cleaned up again.

The following figure forcopySchematic diagram of the algorithm:

Advantages:

  • The marking and copying phases can be carried out simultaneously;
  • Only one piece of memory is reclaimed at a time, running efficiently;
  • Just move the pointer to the top of the stack and allocate the memory in order, which is simple to implement.
  • Memory reclamation does not consider the occurrence of memory fragmentation.

Disadvantages:

  • Available memory has shrunk by half.

The replication algorithm is more suitable for the new generation (objects with a short lifetime), while in the old age (objects with a long lifetime), the survival rate of objects is relatively high. If more replication operations are performed, the efficiency will be lower, so other algorithms, such as “mark-collation” algorithm, are generally used in the old age.

4. Generational Collector algorithms

The generation collection algorithm divides the heap into new generation, old generation and permanent generation. After JDK8, the term permanent generation is removed and replaced by meta-space. The new generation is further divided into Eden and Survivor zone, wherein Survivor consists of FromSpace (Survivor0) and ToSpace (Survivor1). Generally, the young generation uses the replication algorithm (low object survival rate), while the old generation uses the tag sorting algorithm (high object survival rate).

Young Generation:

Almost all newly generated objects are first placed in the young generation, which is divided into an Eden zone and two Survivor (Survivor0, Survivor1) zones in a 8:1:1 ratio. The life cycle of Cenozoic objects is as follows:

  1. Most objects are generated in the Eden zone.
  2. When a new object is created and Eden space application fails (due to lack of space, etc.), a Minor GC/Scavenge GC is initiated.
  3. To reclaim, copy Eden zone survivable objects to a Survivor0 zone and then empty Eden zone.
  4. When this Survivor0 is also full, copy Eden and survivable objects in Survivor0 to another Survivor1, then empty Eden and survivable objects in Survivor0, then Survivor0 is empty. It then swaps Survivor0 with Survivor1, leaving Survivor1 empty, and so on.
  5. When survivable objects of Eden and SURVIVable objects of SURVIVable 0 are insufficient in Survivor1, survivable objects are stored directly to the old age.
  6. When an object escapes a GC in a Survivor zone, its age is increased by 1.
  7. By default, if the object is 15 years old, it moves to the old age.
  8. If the old generation is also Full, a Full GC will be triggered, that is, the new generation and the old generation will be recycled.

The Old Generation:

Objects that survive N garbage collections in the new generation are put into the old generation. Therefore, you can think of the tenured generation as holding objects with long life cycles. The old age has the following characteristics:

  1. Memory is much larger than the New generation (approximately 1:2).
  2. As a guarantee for space allocation in Eden area, when MinorGC is in operation, if there are too many surviving objects that cannot be completely put into Survivor area, it will borrow memory from old age to store objects.
  3. Dynamic object age determination: If the total size of all objects of the same age in Survivor zone is more than half of the Survivor zone space, objects older than or equal to this age will be copied to the old age during MinorGC.
  4. When the memory of the old age is Full, Major GC is triggered, that is, Full GC. The frequency of Full GC is relatively low, and the old age object has a long survival time and a high survival rate.
  5. Major GC is usually accompanied by at least one Minor GC(not absolutely), which is typically 10 times slower than Minor GC.
  6. General large objects directly into the old age. Large objects are objects that require a large amount of contiguous storage space. The most common type of large object is a large array.
  7. Long-lived objects will enter the old age, with a default age of 15.

For the generation age threshold of promotion, we can control it by -xx: MaxTenuringThreshold parameter. As mentioned above, the default age threshold of promotion is 15 years old, why not 16 or 17?

This is where the memory layout of the object comes in. In the HotSpot virtual machine, the memory layout of the object can be divided into three areas: Header, Instance Data, and Padding.

In fact, part of the object header of the HotSpot VIRTUAL machine is used to store runtime data about the object itself, such as hash codes, GC generation ages, lock status flags, thread-held locks, biased thread ids, biased timestamps, etc. The length of this data is 32bit and 64bit respectively on 32-bit and 64-bit VMS (with compression pointer disabled). It is officially called the Mark Word.

In a 32-bit HotSpot virtual machine, if the object is not locked, the Mark Word 32bit space contains 25bits for the object hash code, 4bits for the object generation age, 2bits for the lock flag, 1bit is fixed to 0, as shown in the following table:

On 64-bit systems and 64-bit JVMS, when pointer compression is enabled, the size of the Class pointer in the header is still 4 bytes, and the Mark Word area is increased to 8 bytes, so the header is at least 12 bytes, as shown in the following table:

As you can see, the age of the object is 4 bits, from 0000 to 1111, and the maximum value is 15, so the age of the object cannot exceed 15.

Permanent Generation:

Used to store static files (class classes, methods), constants, etc. Persistent generation has no significant impact on garbage collection, but some applications may generate or call classes dynamically, such as Hibernate, etc. In such cases, a large persistent generation space should be set up to store the classes added during the run. The collection of persistent generations has two main parts: discarded constants and useless classes. Permanent generation in Java SE8 features have been removed, replaced by dimension (MetaSpace), so it won’t appear the Java. Lang. OutOfMemoryError: PermGen error mistake.

Card Table Improves GC efficiency

In some cases, objects of the old age may refer to objects of the new generation, so when marking a living object, you need to scan all objects of the old age. Since the object has a reference to a Cenozoic object, that reference would also be called GC Roots, so wouldn’t a full heap scan be done every time a Minor GC is made?

HotSpot’s solution is a technology called Card Table, which divides the heap into 512-byte cards and maintains a Card Table that stores an identity bit for each Card. This identifier bit indicates whether the corresponding card is likely to contain references to the new generation object. If there is, then we consider the card to be dirty. The diagram below:

Instead of scanning the entire age during Minor GC, we can look for dirty cards in the card table and add objects from dirty cards to Minor GC Roots. After scanning for all dirty cards, the Java VIRTUAL machine clears all dirty cards. Card tables can be used to reduce full-heap space scans of older generations, which can greatly improve GC efficiency.

Garbage collector

If the collection algorithm is the methodology of memory collection, then the garbage collector is the concrete implementation of memory collection. Java virtual machine specification for the garbage collector should be how to implement and there are no rules, so different vendors, different versions of the garbage collector is provided by the virtual machine may have very big difference, and generally provide parameters for users according to their application characteristics and requirements of using different s collector. The collector discussed here is based on the HotSpot virtual machine after JDK 1.7 Update 14 (in which the commercially available G1 collector was officially available while G1 was still experimental), and the virtual machine contains all collectors as shown in the figure.

The figure shows seven collectors acting on different generations, and if there is a line between the two collectors, they can be used together. The region in which the virtual machine is located indicates whether it belongs to the new generation collector or the old generation collector.

1. Serial collector

The Serial collector is the most basic and oldest collector. As the name suggests, this collector is a single-threaded collector. Serial collects in a single-threaded manner, and the system does not allow application threads to disturb the GC thread while it is working. At this point, the application enters the paused state, stop-the-world. The length of stop-the-world pause time is an important indicator of the performance of a collector.

  • Recovery area: Cenozoic;
  • Algorithm: copy algorithm;
  • Runtime environment: The default generation collector running in Client mode.

2. ParNew collector

The ParNew collector is essentially a multi-threaded version of the Serial collector. In addition to using multiple threads for garbage collection, the behavior of the ParNew collector includes all the control parameters available to the Serial collector (e.g. -xx: SurvivorRatio, -xx: SurvivorRatio). XX: PretenureSizeThreshold, – HandlePromotionFailure, collection algorithms, Stop The World, object allocation rules, collection strategies, and so on are all identical to The Serial collector, and The two collectors share a considerable amount of code in implementation. The working process of the ParNew collector is shown below:

Applicability. 3

The Parallel Exploiter is a next-generation garbage collector that uses a “replication” algorithm similar to the ParNew Insane, but with a greater emphasis on throughput. The Parallel Scanvenge collector, which evolved from ParNew, is known as the “throughput first” collector. Throughput is the ratio of CPU time spent running user code to total CPU consumption, i.e. Throughput = user code time run/(user code time run + garbage collection time). If the virtual machine runs for 100 minutes and garbage collection takes 1 minute, the throughput is 99%.

The Parallel Scanvenge collector provides a set of parameters based on ParNew to configure the desired collection time or throughput, and then target the collection accordingly. The approximate range of throughput can be controlled through VM options:

-xx: MaxGCPauseMills: indicates the upper limit of the expected collection time, which controls the impact of collection on application pauses. -xx: GCTimeRatio: Ratio of the expected GC time to the total time, used to control throughput. -xx: UseAdaptiveSizePolicy: indicates the automatic generation size adjustment policy.

Note, however, that pause times are incompatible with throughput goals, as reducing pause times also reduces throughput. Therefore, it is necessary to control the target within a reasonable range.

4. Serial Old collector

Serial Old is an older version of the Serial collector, a single-threaded collector that uses a mark-tidy algorithm. The main significance of this collector is also for the use of virtual machines in Client mode.

Parallel Old collector

Parallel Old is an older version of the Parallel Scanvenge collector, a multithreaded collector that uses the “mark-clean” algorithm. The collector was only available in JDK 1.6, after the recent generation of the Parallel Exploder was somewhat of an embarrassment. The reason is that if the new generation chooses the Parallel Avenge, the older generation will have no choice but to exploit the Serial Old (PS MarkSweep) collector. The Parallel collector may not maximize throughput on the whole application due to the performance of the Serial collector on the server application. The Parallel collector can not take advantage of the multi-CPU processing power of the server due to the single-threaded aging collection. In older environments with large and more advanced hardware, the throughput of this combination may not even be as “awesome” as ParNew plus CMS.

It was not until the Parallel Old collector was invented that the “through-first” collector became a viable combination of applications, the Parallel Scavenge avenge and the CPU exploiter. The Parallel Old collector works as shown below:

6. CMS collector

The CMS (Concurrent Mark Sweep) collector is a collector whose goal is to obtain the shortest collection pause time. At present, a large part of Java applications are concentrated on the server side of Internet sites or B/S systems. These applications pay special attention to the response speed of services and hope to have the shortest system pause time to bring users a better experience. The CMS collector is a good fit for such applications.

As the name (which includes “Mark Sweep”) suggests, the CMS collector is based on a “mark-sweep” algorithm, which is more complex than the previous collectors. The process is divided into four steps, including:

  • CMS Initial Mark
  • CMS Concurrent Mark
  • Re-marking (CMS Remark)
  • CMS Concurrent sweep

The initial marking and re-marking steps still need to “Stop The World”. Initial marking only marks objects that GC Roots can directly associate with, which is very fast. The stage of concurrent marking is the process of GC RootsTracing, while the stage of re-marking is to correct the mark records of those objects whose marks change due to the continued operation of the user program during concurrent marking. The pause time in this phase is generally slightly longer than in the initial tagging phase, but much shorter than in concurrent tagging.

Because the collector thread, which takes the longest concurrent markup and concurrent cleanup, can work with the user thread, the CMS collector’s memory reclamation process is, in general, executed concurrently with the user thread. The following figure clearly shows the concurrency and pauses required in the CMS collector’s operational steps.

Advantages:

  • Concurrent collection, low pause.

Disadvantages:

  • CPU resources are very sensitive;
  • Unable to handle floating garbage;
  • Is based on the “mark-sweep” algorithm, which has all the disadvantages.

The CMS collector can achieve concurrency, the fundamental reason lies in the adoption of “mark-clean” algorithm and a fine-grained decomposition of the algorithm process. As mentioned earlier, the “mark-clean” algorithm produces a large amount of memory fragmentation that is unacceptable to the new generation, so the new generation collector does not provide a CMS version.

7. G1 collector

The G1 (garbage-First) collector is one of the latest developments in collector technology. It is a Garbage collector for server-side applications that the HotSpot development team has assigned to replace the CMS collector released in JDK 1.5 (in the longer term). Compared to other GC collectors, G1 has the following features:

  1. Parallelism and concurrency: The G1 is able to take advantage of multi-CPU, multi-core environments by using multiple cpus to reduce stop-the-world pause times.
  2. Generational collection: As with other collectors, the concept of generational collection still exists in G1. Although G1 can manage the entire GC heap independently without the cooperation of other collectors, it can work differently with newly created objects and old objects that have been around for a while and have survived multiple GC’s for better collection results.
  3. Spatial integration: Different from CMS’s “mark-clean” algorithm, G1 is a collector based on “mark-clean” as a whole and “copy” as a local (between two regions) algorithm. Both algorithms mean that G1 does not generate memory space fragmentation during operation. The collection provides overall free memory.
  4. Predictable pauses: In addition to pursuing low pauses, G1 also models predictable pauses, allowing users to explicitly specify that no more than N milliseconds should be spent on garbage collection within a time segment of M milliseconds.

Across the entire heap memory

When using the G1 collector, the memory layout of the Java heap is very different from that of the other collectors. It divides the entire Java heap into independent regions of equal size. While the concept of new generation and old generation is retained, the new generation and old generation are no longer physically separated. They are all collections of parts of regions (which do not need to be continuous).

Build predictable time models

The G1 collector is able to model predictable pause times because it can systematically avoid region-wide garbage collection across the entire Java pair. G1 tracks the value of Garbage accumulation in each Region (the amount of space collected and the empirical value of the collection time), maintains a priority list in the background, and gives priority to the Region with the highest Garbage collection value (hence the name garbage-first) based on the allowed collection time. The operation process of G1 collection is as follows:

  1. Initial Marking: Just Mark the objects that GC Roots can be directly associated with, and change the value of TAMS (Next Top at Mark Start) so that new objects can be created in the correct Region available when the user program runs concurrently in the Next stage. This stage requires the thread to be paused, but it takes a short time.
  2. Concurrent Marking: An analysis of the reachability of objects in the heap, starting with GC Roots, to identify viable objects. This phase is time-consuming but can be performed concurrently with user programs.
  3. Final Marking: The virtual machine will record the changes in the thread Remembered Set Logs during concurrent marking. The virtual machine will record the changes in the thread Remembered Set Logs. The final marking phase requires the consolidation of data from the Remembered Set Logs into the Remembered Set. This phase requires the thread to be paused, but can be performed in parallel.
  4. Live Data Counting and Evacuation: Each Region is first ordered by recovery value and cost to develop a recovery plan based on the expected GC downtime of the user. This phase can also be executed concurrently with the user program, but because only part of the Region is reclaimed, the time is controlled by the user, and halting the user thread greatly improves collection efficiency.

The following figure clearly shows the concurrency and pauses in the G1 collector’s operational steps (at Safepoint) :

The GC mode of G1 can be divided into two types:

YoungGC: When allocating generic objects (non-giant objects), a YoungGC is triggered when all Eden regions have reached their maximum usage threshold and cannot allocate enough memory. Each time the Young GC reclaims all Eden and Survivor zones and copies the surviving objects to the Old zone and another portion of Survivor zones. Mixed GC: When more and more objects are promoted to the Old age, in order to avoid running out of heap memory, the virtual machine will trigger a Mixed garbage collector, namely Mixed GC. This algorithm is not an Old GC, but will collect part of the Old age in addition to the whole new generation. Note here: You can choose which Old areas to collect, and thus control the time it takes to collect garbage. G1 has no concept of Full GC and calls Serial Old GC for Full heap scan when Full GC is required.

Conclusion:

To check the default garbage collector used by the JVM, run the following command from a Mac terminal or Windows CMD:

java -XX:+PrintCommandLineFlags -version
Copy the code

Taking my computer as an example, the execution result is:

-XX:G1ConcRefinementThreads=10 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=267050880 -XX:MaxHeapSize=4272814080 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC -XX:-UseLargePagesIndividualAllocation
java version "12.0.1" 2019-04-16
Java(TM) SE Runtime Environment (build 12.01.+12)
Java HotSpot(TM) 64-Bit Server VM (build 12.01.+12, mixed mode, sharing)
Copy the code

Garbage collector parameters summary:

  • UseSerialGC: specifies the default value when the VM runs in Client mode. After this function is enabled, the Serial and Serial Old collectors are used to reclaim memory.
  • UseParNewGC: When the secondary switch is turned on, the collector combination of ParNew + Serial Old is used to reclaim memory.
  • UseConcMarkSweepGC: When the secondary switch is turned on, the collector combination of ParNew + CMS + Serial Old is used for memory reclamation. The Serial Old collector will be used as a backup collector in the event of Concurrent Mode Failure of the CMS collector.
  • UseParallelGC: Specifies the default value of the VM running in Server mode. After this switch is turned on, recycle memory using the Parallel Avenge + Serial Old (PS MarkSweep) collector.
  • UseParallelOldGC: Use the Parallel Avenge + ParallelOld collector when this switch is turned on.
  • SurvivorRatio: capacity ratio between Eden and Survivor zones in the new generation. The default value is 8, indicating Eden: Survivor = 8:1.
  • PretenureSizeThreshold: Directly promotes to the object size of the previous generation. After this parameter is set, objects larger than this parameter will be allocated in the previous generation.
  • MaxTenuringThreshold: Object age for promotion to the old age. After each Minor GC, the age of each object increases by 1, and when this parameter value is exceeded, the object becomes old.
  • UseAdaptiveSizePolicy: Dynamically adjusts the size of various regions in the Java heap and the age to which they are aged.
  • HandlePromotionFailure: Whether the allocation guarantee is allowed to fail, i.e. the extreme case where the remaining space of the old generation is insufficient for the entire Eden and Survivor zone of the new generation to survive.
  • ParallelGCThreads: Sets the number of threads that perform memory reclamation during parallel GC.
  • GCTimeRatio: Ratio of GC time to total time. The default value is 99, that is, 1% GC time is allowed. Only valid when using the Parallel Scavenge collector.
  • MaxGCPauseMillis: Sets the maximum pause time for GC. Only valid when using the Parallel Scavenge collector.
  • CMSInitiatingOccupancyFraction: set the CMS collector after the old s space is how much triggers garbage collection. The default value is 68% and only takes effect when the CMS collector is used.
  • UseCMSCompactAtFullCollection: set the CMS collector after complete garbage collection is to conduct a memory defragmentation. Only valid when using the CMS collector.
  • CMSFullGCsBeforeCompaction: set the CMS collector in start again after several times of garbage collection memory defragmentation. Only valid when using the CMS collector.

References:

An In-depth Understanding of the Java Virtual Machine by Zhiming Zhou

Summary of JVM garbage Collection mechanism (GC)

Java Virtual Machine (JVM) you only need to read this article!

JVM- In-depth analysis of the memory layout of objects

JVM – Card table, an important means of garbage collection

In-depth understanding of the JVM, seven garbage collectors