An overview,

Java Garbage Collection (GC) is one of the main differences between Java and C++/C. As Java developers, they generally do not need to write special memory Collection and Garbage cleaning code, and they do not need to be afraid of memory leakage and overflow problems like C programmers. Over time, the Java GC mechanism has matured to do most things for us almost automatically.

While Java does not require the allocation and reclamation of memory that developers display, this does make programming a lot easier for developers, but it can also have some side effects:

The JVM spends too much time on memory reclamation. Memory leaksCopy the code

Therefore, as a Java programmer, it is necessary to learn the JVM memory management and reclamation mechanism, which can help us in our daily work to detect various memory overflow or leak problems, solve performance bottlenecks, achieve higher concurrency, and write more efficient programs.

Second, JVM memory space management

According to the JVM specification, the JVM divides memory into the following regions:

Method area 2. Heap area 3. Local method stack 4. Program counterCopy the code

The method area and heap are shared by all threads.

2.1 method area

The method area stores information about the class to be loaded (such as the class name, modifiers), static variables in the class, constants defined by final, fields in the class, and method information. When developers call methods such as getName and isInterface in the class object to obtain information, these data come from the method area. The method area is shared globally, and under certain conditions it is also GC. An OutOfMemory: PermGen Space exception is thrown when the method area uses more memory than it allows.

In the Hotspot VIRTUAL machine, this area corresponds to Permanent Generation(persistent Generation). In general, there is very little garbage collection performed on the method area, which is one of the reasons why the method area is called persistent Generation. This does not mean that there is no garbage collection on the method area. The garbage collection is mainly for the constant pool memory reclamation and the unloading of loaded classes. Garbage collection on method areas is demanding and quite difficult, but more on that later.

The Runtime Constant Pool is the part of the method area used to store literal constants, symbolic references, and translated direct references generated at compile time. Translation will be done in the class link phase); The runtime constant pool can store compile-time constants as well as run-time constants. For example, the String intern() method maintains a constant pool. If the character ABC is already in the constant pool, the String returns the address of the String. And returns the address.

–XX:PermSize; Maximum value –XX:MaxPermSize.

2.2 the heap area

The heap area is the most important area to understand the JavaGC mechanism. The heap is the largest area of memory managed by the JVM, and the main area of memory managed by the JavaGC mechanism. The heap is shared by all threads and created at virtual machine startup. The heap is used to store object instances and array values, and you can think of all objects created by New in Java as allocated here.

The heap size can be controlled with the parameters -xms and -xmx. -xms is the latest heap memory applied at JVM startup, which defaults to 1/64 of physical memory but less than 1GB. -xmx is the maximum Heap size that a JVM can apply for. The default Heap size is 1/4 of the physical memory but less than 1GB. By default, when the remaining Heap space is less than 40%, the JVM increases the Heap size to -xmx. When free Heap memory is greater than 70%, the JVM reduces the Heap size to the size specified by -xms, which can be specified by -xx :MaxHeapFreeRatio. For systems, to avoid frequent Heap size adjustments during runtime, we usually set -xms and -xmx to be the same.

In order to make memory collection more efficient (more on this later), the heap has been managed by generation since Sun JDK 1.2, as shown in the following figure:

Young Generation

When objects are created, memory is first allocated in the young generation (note that large objects can be allocated directly in the old generation). A Minor GC(also known as the Young GC) is triggered when the Young generation needs to reclaim.

The young generation consists of Eden Space and two Survivor Spaces of the same size (also known as S0 and S1). The size of the new generation can be adjusted by -xMN, or the size of Eden Space and Survivor Space can be adjusted by -xx :SurvivorRadio. Different GC methods classify Eden Space and Survivor Space by this value in different ways, and some GC methods dynamically size Eden, S0, and S1 based on health.

The Eden region memory of the young generation is continuous, so its allocation will be very fast. Similarly, the recovery in Eden area is also very fast (because in most cases, the survival time of objects in Eden area is very short, and the copy recovery algorithm adopted in Eden Area is very efficient when the proportion of viable objects is very small, which will be described in detail later).

OutOfMemoryError:Java Heap Space exception is thrown if, after garbage collection, there is still not enough memory allocated and no further expansion is possible.

The Old Generation

Old age is used to store objects that have survived multiple garbage collections in the younger generation, which can be understood as older objects, such as cache objects; New object may also directly allocates memory on the old s, that there are two main situations: for a large object, can be set through the launch parameters – XX: PretenureSizeThreshold = 1024, said more than how would not be in the young generation distribution, but in old age distribution directly. This parameter is not valid when the younger generation is using the Parallel Explogc, as it decides on its own what objects allocate memory directly on the older generation based on the run; The other is a large array object with no references to external objects.

When the old decade becomes Full, a garbage collection is required for the old decade, which is called the Major GC (also known as the Full GC).

The memory used by the old age is the value of -xmx minus the value of -xmn.

2.3 Native Method Stack

The local method stack is used to support the execution of native methods and stores the state of each native method call. The native method stack and virtual machine method stack work the same way. The only difference is that the virtual machine stack executes Java methods while the native method stack executes native methods. In many virtual machines (such as the Default HotSpot VM in Sun’s JDK), the native method stack and virtual machine stack are used together.

2.4 Program Counter Register

A program counter is a small area of memory, either a CPU register or operating system memory, that is used to indicate the line number of the bytecode executed by the current thread. It can be understood as a line number indicator of the current thread. The bytecode interpreter works by changing the value of this counter to fetch the next statement instruction. Each program counter is used only to record the line number of one thread, so it is thread private (one program counter per thread).

If the program executes a Java method, the counter records the address of the virtual machine bytecode instruction being executed. If you are executing a native method, the counter value is Undefined. Since the program counter only records the current instruction address, there is no memory overflow. Therefore, The program counter is also the only area of any JVM memory region where OutOfMemoryError is not defined.

2.5 JVM Stack

The virtual stack occupies the memory of the operating system. Each thread has a virtual stack, which is thread private and allocated efficiently. When executing each method of a thread, a Statck Frame will be created, which stores local variable tables, operation stations, dynamic links, method exits, etc. When the method is called, the stack Frame will be pushed into the JVM stack, and when the method is executed, the stack Frame will be pushed out of the stack.

Local variables table stores local variables related to methods, including various basic data types, object references, return addresses, etc. In the local variable table, only the long and double types occupy two local variable Spaces (Slot, for 32-bit machines, a Slot is 32 bits), and all the others are one Slot. Note that the local variable table is determined at compile time. The amount of space allocated for a method to run is fully determined in the stack frame and does not change during the lifetime of the method.

Two exceptions are defined in the virtual machine stack. If the stack depth of the thread call is greater than the maximum depth allowed by the virtual machine, a StatckOverFlowError is raised. Most Java virtual machines, however, allow dynamic expansion of the virtual stack size (a small percentage is fixed), so threads can apply for stacks until they run out of memory, at which point outofMemoryErrors are thrown.

2.6 Java Object Access Mode

Typically, a Java reference access involves three areas of memory: the JVM stack, the heap, and the method area. Take the simplest local variable reference: Object objRef = new Object() :

  1. Object objRef represents a local reference stored in the JVM stack’s local variable table and represents a reference type of data.
  2. New Object() is stored in the heap as instance Object data;
  3. The address of the type data (interface, method, field, Object type, etc.) that can be queried is also recorded in the heap. The actual data is stored in the method area.

In the Java Virtual Machine specification, only a reference to an object is specified. The method of accessing a specific object through a reference type is not specified. However, there are two main implementation methods:

2.6.1 Access through Handles

In the handled access implementation, the JVM heap is divided into a separate memory region as a handle pool that stores Pointers to object instance data (in the heap) and object type data (in the method area). This implementation is very stable because the address is represented by a handle.

2.6.2 Access through direct Pointers

In the direct pointer access mode, reference stores the actual address of the object in the heap, and the object information stored in the heap contains the corresponding type of data in the method area. The biggest advantage of this approach is speed, which is used in the HotSpot VIRTUAL machine.

Third, JVM memory allocation

Most of the memory consumed by Java objects is on the heap, which is shared by threads, so memory allocation on the heap needs to be locked, which leads to the overhead of creating objects. When there is insufficient space on the heap, a GC is initiated, and if there is still insufficient space after GC, an OutOfMemory exception is thrown.

In order to improve memory Allocation efficiency, HotSpot VM in Eden region of young generation uses two techniques to speed up memory Allocation, namely bump-the-Pointer and TLAB (Thread-local Allocation Buffers). Since Eden area is continuous, the core of bump-the-Pointer technology is to track the last object created. When creating an object, you only need to check whether there is enough memory behind the last object, thus greatly speeding up the memory allocation speed. As for TLAB technology, it will allocate an independent Space in the Eden Space of the new generation for each newly created Thread. This Space is called TLAB (Thread Local Allocation Buffer), and its size is calculated by JVM according to the running situation. Through – XX: TLABWasteTargetPercent to Settings it can take up Eden Space percentage, the default is 1%. There is no lock required to allocate memory on TLAB. Typically, the JVM will allocate memory on TLAB first, and if the object is too large or TLAB space has run out, it will still be allocated on the heap. Therefore, when writing programs, multiple small objects can be allocated more efficiently than large objects. You can add -xx :+PrintTLAB to the startup parameter to view TLAB space usage.

Objects that survive long enough in the young generation to not be cleaned up (that is, after a few Minor GC’s) are copied to the old generation, which generally has more space than the young generation, can hold more objects, and has fewer GC’s than the young generation. When the older generation runs out of memory, the Major GC, also known as Full GC, is performed.

You can use the -xx :+UseAdaptiveSizePolicy switch to control whether a dynamic control policy is adopted, and if so, to dynamically adjust the size of the various regions in the Java heap and the age into the old age.

If the object is large (such as a long string or a large array) and there is not enough space for the young generation, the large object will be allocated directly to the old generation (large objects can trigger premature GC and should be used sparatively, and short-lived large objects should be avoided). Use – XX: PretenureSizeThreshold to control the size of the object directly to the old s, greater than the value of object allocation on the old s directly.

Four, the way of memory recovery

The JVM reclaims memory in the heap and method areas through GC, which is done automatically. When it comes to the Java GC mechanism, it does three things: determine what memory needs to be reclaimed; Determine when GC needs to be performed; How to perform GC. The JVM mainly implements GC in the form of collectors, including reference counting collectors and trace collectors.

4.1 Reference counting collector

Reference counters are managed in a decentralized manner. Counters record whether objects are referenced or not. When the counter is 0, this object is no longer in use and can be reclaimed, as shown in the figure:

In the figure above, ObjectB’s reference counter goes to 0 after ObjectA releases a reference to ObjectB, and the memory held by ObjectB is reclaimed.

The reference counter needs to be increased or decreased each time an object is assigned, which has some cost. In addition, reference counters have no way to recycle for cyclic reference scenarios. For example, if ObjectB and ObjectC refer to each other, then ObjectB and ObjectC cannot be reclaimed even if ObjectA releases the reference to ObjectB and ObjectC. Therefore, for Java languages that can form complex reference relationships, Reference counters are very inappropriate, and the SunJDK does not implement GC in this way.

4.2 Trace Collector

The trace collector is centrally managed and records the status of data references globally. Triggering based on certain conditions (such as timing, when out of space), execution requires scanning for references to objects from the root collection, which can cause the application to pause. There are mainly three realization algorithms in Copying, mark-sweep and mark-compact.

Copying

Replication takes place by scanning for live objects from the root collection and copying the found live objects into a new completely unused space, as shown in the figure below:

The copying collector method only needs to scan all living objects from the root collection. When there are fewer living objects in the space to be reclaimed, the copying algorithm will be more efficient (this algorithm is used in the Eden area of the young generation), and the cost is to increase an empty memory space and move objects.

Marking-Deleting

The method of mark-clearing is to scan from the root set and mark the surviving objects. After marking, the unmarked objects in the whole space will be scanned and cleared, as shown in the following figure:

In the figure above, the blue parts are living objects with references, and the brown parts are recyclable objects with no references. In marking stage, all objects will be scanned for mark objects, which is time-consuming.

The cleanup phase reclaims unreferenced objects, and the surviving objects are retained. The memory allocator holds a list of references to free space and queries the list for allocation when an allocation request is made.

The mark-clear action does not require object movement, and only objects that are not alive are processed. It is efficient when there are many living objects in the space, but it can cause memory fragmentation because mark-clearing directly reclaims memory occupied by non-living objects.

Mark-compact

Mark-compression, like mark-clear, marks live objects, but the processing is different after clearing. After clearing the memory occupied by the object, mark-compression will move all live objects to the left free space, and then update the pointer to the object, as shown in the following figure:

Obviously, mark-compression can move and tidy the surviving objects on the basis of mark-clear, thus solving the problem of memory fragmentation and obtaining more continuous memory space to improve the allocation efficiency. However, the cost is relatively high because the objects need to be moved.

5. GC process in virtual machine

5.1 Why generational recycling?

In the beginning, THE JVM’s GC was mark-clean-compress, which was not very efficient because as more objects were allocated, the list of objects became larger, and scanning and moving became more time-consuming, resulting in slower memory reclamation. However, according to the analysis of Java applications, it is found that the lifetime of most objects is very short, and only a small part of the data lifetime is relatively long. Please see the following statistics for the memory lifetime of Java objects:

As you can see from the chart, most objects are very short-lived, and as time goes on, fewer and fewer objects are allocated.

5.2 GC Process on VMS

Now that we know why JVMS need generational collection, let’s take a closer look at the entire collection process.

  1. In the initial phase, the newly created object is allocated to the Eden zone, and both survivor Spaces are empty.

  2. Minor garbage is triggered when Eden is full

  3. After scanning and marking, viable objects are copied to S0 and non-viable objects are reclaimed

  4. In the next Minor GC, the Eden zone is the same as above, with unreferenced objects being reclaimed and surviving objects copied to survivor zone. In the survivor zone, however, all data from S0 is copied to S1, and it should be noted that the age of the two objects moved to S0 during the last minor GC is increased by 1 after being copied to S1. At this time, Eden area S0 is cleared, and all surviving data is copied to S1 area, and there are objects of different ages in S1 area, as shown in the following figure:

  5. The next time MinorGC repeats the process, this time the two survivor zones are swapped, the surviving object is copied to S0, the surviving object’s age is increased by 1, and the Eden zone and another survivor zone are emptied.

  6. To illustrate the Promotion process, after a few more Minor GC’s, the live objects are promoted from the young generation to the old generation when their age reaches a threshold (configurable by parameter, default is 8).

  7. As MinorGC progresses over and over again, new objects are promoted to older eras.

  8. It basically covers all the recycling processes of the entire young generation. Eventually, MajorGC will occur in the old age, and the old age space will be cleared and compressed.

As you can see from the above procedure, the Eden area is a contiguous space, and one of Survivor is always empty. After a GC and replication, one Survivor holds the currently alive objects, while the contents of Eden and the other Survivor are no longer needed and can be emptied, and at the next GC, the roles of the two Survivor are switched again. Therefore, this method of garbage collection is very efficient in both allocating and cleaning up memory. This method of garbage collection is known as “stop-and-copy” cleaning (copying the Eden region and surviving objects from one Survivor into another Survivor). This does not mean that the stop-copy cleanup method is efficient. In fact, it is only efficient in this case (due to the fact that most objects have a short lifetime), and it would have been inappropriate to use stop-copy in the old days.

The old generation stores many more objects than the young generation, and there are many large objects. It is quite inefficient to use the stop-copy algorithm to clean up the memory of the old generation. In general, the old age substitution algorithm is mark-compression algorithm, that is: mark the surviving objects (there are references), move all the surviving objects to one end, to ensure the continuity of memory. When a Minor GC occurs, the virtual machine checks whether the size of each promotion to the old age is greater than the size of the remaining space in the old age. If so, it triggers a Full GC directly. Otherwise, it checks to see if -xx :+HandlePromotionFailure is set. MinorGC only occurs, in which case memory allocation failures are tolerated. If not, Full GC is still done (this means that if you set -xx :+Handle PromotionFailure, triggering MinorGC will trigger Full GC at the same time, even if there is a lot of memory left in the old days, so it is best not to do this).

There are two types of permanent generation collection: constant in constant pool, useless class information, constant collection is very simple, no reference can be recycled. To recycle useless classes, you must ensure three things:

All instances of the class have been recycled. 2. The ClassLoader that loaded the class has been recycled. The Class object of the Class object is not referenced (that is, there is no place where the Class is referenced by reflection)Copy the code

Recycling of the permanent generation is not required, and classes can be set to be recycled using parameters.

Garbage collector

We have already seen that the JVM’s memory collection process is performed by the garbage collector in the virtual machine. Therefore, it is necessary to choose the appropriate garbage collector in a practical application scenario. Let’s introduce the garbage collector.

6.1 Serial Collector

The serial collector is the default configuration used by client virtual machines in JavaSE5 and 6. It is the simplest collector and is suitable for systems with only one processor. In the serial collector, both the Minor and Major GC processes use a single thread for garbage collection.

Usage scenarios

First, serial GC is typically used in scenarios where application pauses are not very demanding and run in client mode, using only one CPU core for garbage collection. With today’s hardware, the serial GC can manage many small memory applications and can guarantee relatively small pauses (a few seconds in the case of a Full GC). Another common scenario where serial GC is used is when a machine is running multiple JVM VMS (the number of JVM VMS is greater than the number of CPU cores), in which only one processor is used for garbage collection by one JVM, with no significant impact on other JVMS. Finally, serial collectors are suitable for hardware devices with relatively small memory and CPU cores.

Related Parameter Commands

1 Enable the serial collector: -xx :+UseSerialGC

2 Command line Examples:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
Copy the code

6.2 Parallel Collector

The parallel collector adopts a multi-threaded approach to garbage collection, which can bring great CPU throughput. It has no impact on the running application when garbage collection is not performed, and multithreading is used to speed up the collection while the process is GC, so the parallel collector is well suited for batch processing. Of course, if your application requires a lot of program pauses, the concurrency collector described below is recommended. By default, on a machine with N cpus, the number of concurrent threads reclaimed is N. Of course, the amount of parallelism can be controlled with parameters: -xx :ParallelGCThreads=< Desired Number >. The parallel collector is the default collection on Server level machines (cpus > 2 and memory > 2 gb),

On a machine with a single-cpu CPU, the default collector is still used for actual collection, even if the parallel collector is configured. If there are only two cpus on a machine, the parallel collector and the default collector work equally well, and only when there are more than two cpus does the pause time of the young generation collector decrease.

Application scenarios

Parallel collector is suitable for multiple cpus with short pause time requirements. In general, some batch applications such as report printing and database queries can use parallel collectors.

Replace multithreading in the young and single threading in the old

1 Enable the -xx :+UseParallelGC command

2 Command line Examples:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
Copy the code

Both the younger generation and the older generation use multithreading

1 Enable the -xx :+UseParallelOldGC command

When the -xx :+UseParallelOldGC option is enabled, garbage collection in both young and old generations is multithreaded, as well as in the compression phase. Because the HotSpot VIRTUAL machine uses a stop-copy algorithm in the young generation, there is no compression process in the young generation, while the old generation uses a mark-clean-compression algorithm, there is only compact process in the old generation.

2 Command line Examples:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
Copy the code

6.3 Concurrent Mark Sweep (CMS) Collector

The CMS collector is primarily for persistent areas, which attempt to reduce pauses in garbage collection in the form of multithreaded concurrency. The CMS collector does not copy or move live objects.

Application scenarios

The CMS collector is mainly used in scenarios where the application has high requirements on pause time, such as desktop UI applications that need to timely respond to user operation events, servers that must be able to quickly respond to client requests, or databases that need to quickly respond to query requests, etc.

Related command parameters

1 Enable the CMS collector: -xx :+UseConcMarkSweepGC

-xx :ParallelCMSThreads=

3 Command Line Example:

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
Copy the code

6.4 G1 Collector

G1, Garbage First, is a new collector in Java 7 that aims to replace the existing CMS collector. G1 features parallelism, concurrency, incremental compression, and pause periods, which I won’t cover in detail here.

Related command parameters

1 Enable G1 collector: -xx :+UseG1GC

2 Command line Examples:

java -Xmx12m -Xms3m -XX:+UseG1GC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
Copy the code

Reprint please indicate the source: original link