This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

The memory space occupied by Java objects is allocated in the heap space, and memory management of the heap space is very important because of the large number of objects.

How objects are created

Let’s look at the bytecode of a piece of code:

By looking at bytecode, we know that the JVM executes Object obj = new Object(); There are three steps:

  1. The new keyword creates an object and returns a reference to it
  2. Call the init method of an object by reference
  3. Assign a reference to a local variable (static variable, class variable)

When we are easy to think of the new object, it is necessary to create a memory on the heap space operations, and we can through the object reference to perform the object’s methods or attributes of the object access, with the passage of time, we sometimes need to create a new object, and then a new object reference assigned to the variable, as a result, the old object reference disappears, However, the old objects are still occupying the memory space, and the purpose of Java garbage collection is to reclaim the memory space occupied by these objects.

How can I tell if an object is garbage?

Since it is garbage collection, the heap space is our garbage dump, and objects that are no longer referenced are garbage, so what algorithm is used to determine whether objects are garbage is very important. There are usually two ways:

  • Reference counting method
  • Accessibility analysis

Reference counting method

This approach is straightforward: since unreferenced objects are garbage, add a reference-count attribute to each object, add +1 if no reference is assigned, and add -1 every time no reference is used. =0 indicates that the object needs to be reclaimed. This method is easy to understand, simple and crude.

Let’s do a simple design that doesn’t think about how to recycle garbage, just how to get unreferenced objects.

UML class diagrams

Execution process:

  1. Change the code at new Object() toCom.sanjin. JVM. Determines that the object is garbage. ReferenceManager#newReferenceInternally, objects are created through Supply and references are added to the internally maintained list.
  2. Called where objects are neededCom.sanjin. JVM. Determines that the object is garbage. Reference#getThe reference value +1 is called after useCom.sanjin. JVM. Determines that the object is garbage. Reference#releaseThe reference value -1 is used.
  3. When insufficient memory triggers garbage collection, passCom.sanjin. JVM. Determines that the object is garbage. ReferenceManager#getUnRefReferenceUnreferenced objects can be retrieved, i.eThe garbage.

This seemingly simple solution presents a number of challenges:

  1. Concurrency issues during executionCom.sanjin. JVM. Determines that the object is garbage. ReferenceManager#getUnRefReferenceYou also need to disable the callCom.sanjin. JVM. Determines that the object is garbage. Reference#getNo will result in naming the object that get gets, but that object is =null. Of course release is ok.
  2. Circular reference problem, there are two objects A, B reference each other, reference count value is always not 0. This problem needs to be solved with graphs, with each object as a node. A’s reference to B is the edge A points to B, and B’s reference to A is the edge B->A. Instead of counting, you’re adding and subtracting edges. When an isolated ring is detected, all nodes in the ring are unreferenced objects.

Accessibility analysis

Reachability analysis is similar to solving the circular reference problem, when garbage collection, starting from the GCRoot object is traversed, generating a mesh graph. The variable procedure cannot perform the assignment of the object reference, otherwise it will cause that the object has been assigned, but when the object is called at last, the object =null, which means that the code cannot execute, so it is usually called STW (Stop the word). After traversal, unreached objects are garbage objects and need to be reclaimed.

conclusion

In fact, reachability analysis can be regarded as reference counting as a new algorithm to solve the problem of circular reference. All JVMS use accessibility analysis to make judgments.

How to recycle garbage?

When the JVM gets a bunch of garbage objects, how does it recycle them? Call C directly to free object memory? This is an option, but the downside is memory fragmentation. Excessive memory fragmentation can result in a large amount of memory that cannot be allocated to objects (because it is not contiguous). Fragments of memory in order to solve the problem, need to be in a memory after a garbage objects (because garbage objects discontinuous so, need a a release), note at this time can not stop the STW immediately, and then record live object reference replacement behind (for reference), and live objects (starting from the low address space object moves forward, The purpose is to reduce the memory space between the living objects), and finally overwrite the new reference of the living object on the old reference, and then stop the STW. This garbage collection algorithm is called the tag collation algorithm.

Of course, decluttering is a non-time consuming and CPU-consuming operation, since it requires freeing object memory one by one, moving living objects one by one. So is there a way to just clear the object and move the living object? Of course there is. The tag clearing algorithm is worth having. When the memory is allocated, only the memory in space A is allocated. When the memory in space A is insufficient, the surviving objects in space A are copied to space B. The whole copy can be directly copied, because the free memory in space B is continuous, and then the space A can be cleared directly. The trade-off is a reduction in memory space by half.

How to optimize heap space for object characteristics?

Garbage collection developers do a test, just out of the object, when the memory is full for garbage collection, 90% of the object is garbage. Only a few objects survive. Based on this characteristic, the reactor space can be divided into new generation and old age. The new generation stores objects that are newly created. Old age stores objects that have not been marked as garbage after multiple garbage collections. For older generations, there is probably not a lot of memory freed per GC, and if it is not worth reallocating a chunk of free memory for performance, the tag copying algorithm is not suitable, so in older generations, the tag descrambling algorithm is generally used. However, for the Cenozoic generation, the objects die quickly, and there are few surviving objects, so the use of the marker copy algorithm can obtain higher income. However, it is not necessary to divide the Cenozoic according to the ratio of 1:1, because only 10% of the surviving objects need to have a place to store, so a section of Cenozoic can be allocated as S1, S2 region, S1, S2 for marking and sorting. Cenozoic objects only exist in Cenozoic and S1. When garbage is collected, surviving objects are copied to S2. Other space can be directly released.

Garbage collection algorithm summary:

  1. Collection by generation: according to the characteristics of the objects in life and death by generation collection. The new generation uses marker copying and the old generation uses marker sorting
  2. The Eden and S1 / S2 regions were divided into Cenozoic. The ratio is about eight to two
  3. When the S-zone can’t hold an object, go straight to the old age

Garbage collection algorithm

Serial collector

The Serial collector features a single thread that performs a reachability analysis from the GC Root when garbage collection is triggered. The garbage collection algorithm is then implemented, with the new generation using copy and the old using collation. The entire process suspends the user thread as STW occurs.

ParNew collector

The ParNew collector is an upgraded version of Serial. In the new generation garbage collection process, multithreading is used for reachability analysis and duplicate collection algorithm. But the old days were still single-threaded. Multithreading garbage collection is not always good, more threads means more CPU consumption, resulting in lower throughput.

Parallel avenge

The new generation of multithreaded collectors, in contrast to ParNew, feature throughput control. Throughput = CPU execution user code time /CPU execution time (over a period of time)

The Parallel Insane provides two parameters to control throughput: the -xxmaxGCPausemillis parameter controls the maximum pause time, and the -xx :GCTimeRatio parameter controls the throughput size. The shorter the pause time, the faster the response time, and the pause time can be increased by reducing the Eden area size. However, more GCS are responded to, and more GCS can lead to throughput degradation.

The higher the throughput, the faster the user code computes.

CMS collector

The CMS collector targets the minimum pause time. The process includes:

  1. Initial flag: Suspend the user thread and scan for GC ROOT
  2. Concurrency flags: Perform reachability analysis against GC ROOT, since concurrency requires processing while nodes are scanned
  3. Relabelling: Fixes of oopMap changes during the concurrent phase, such as those that were dead and then came back alive, need to be changed otherwise the user program will run incorrectly.
  4. Concurrency clear: the tag removal algorithm knows the garbage object

Advantages: Low pause and high time consuming parts fully support concurrent execution. Disadvantages: 1. If the machine CPU is not much, CMS multithreading is a “chicken ribs”, set the thread will occupy CPU consumption, seriously affect the user program to increase the context switch. 2. Floating garbage will be generated. Concurrent marking and concurrent cleanup phases, where user programs execute new garbage objects, are called “floating garbage.” You have to wait until the next garbage collection is clear. And because the user thread does not stop, you need to leave enough space for allocating objects. When the reserved space is insufficient, a Concurrent Mode Failure occurs, at which point the Full GC is raised. The default is triggered when using 68% for older generations. If memory growth under the condition of not very block, can also through the parameter – XX: CMSInitiatingOccupancyFraction percentage value for improvement of the trigger to reduce frequency of garbage collection. However, it should not be too high, which risks triggering Concurrent Mode Failure. 3. In most cases use the tag removal algorithm to remove rubbish, so will produce the memory allocated to large objects in FULL GC, provide – XX: UseCMSCompactAtFullCollection switch parameters, when FULL GC will be memory consolidation, clean up the pieces. Of course every time waiting for Full GC is not very good, can pass – XX: CMSFullGCsBeforeCompaction parameter specifies the GC, after how many times a finishing.

G1 collector

The G1 collector pursues predictable pause times. The goal of garbage collection from G1 is not to collect more garbage at a time, but to achieve a dynamic balance between garbage collection speed and memory allocation speed.

G1 garbage collection process is similar to CMS:

  1. Initial flag, single thread, STW
  2. Concurrent markup, multithreading, convenient object graph
  3. Final tag, multithreading, STW
  4. Filter recycling, multithreading, STW

Although the steps are similar to the CMS, but g1 collector is different from before any garbage collector is the division of pile is not in Cenozoic, old age, but in block partition (marked rehion book, but I feel this is the operating system is used the free list of memory management way, to see don’t understand at the beginning of region). There are two types of block sizes: small blocks of 0-32M and large blocks for storing large objects. How to allocate the specific size remains to be studied. For the object initialization process, the JVM book only states that any region can be used as Eden, S region, and large regions as old region by default. So let me imagine how objects are allocated.

When we new an object.

  1. Check whether the object exceeds the threshold for large objects. If the threshold is exceeded, a large Region block is allocated for storage. If not, the thread finds the region it is using and determines whether it has enough space. If so, it stores the region.
  2. When the new object or the heap space is insufficient, obtain the list of all regions allocated, evaluate the collection time of the region, and the percentage of garbage collected. This is executed concurrently with the user thread.
  3. Blocks are selected for GC based on the pause time threshold set. For small pieces, the replication algorithm is used for collection. The key points are Eden, S1 and S2. Does each of these small blocks need to be equipped with 2 extra blocks or 1 extra block (8:2 divided into S-block) for the replication algorithm? At present, I am also looking for materials to study, and I can share the answers.

In my opinion, the main purpose of g1’s conversion into region is to pursue more controllable pause time. In the past, it was necessary to scan the entire pair space to estimate the collection time, while CMS can scan each region from global to local, which is more precise and controllable. This is the biggest feature of G1 in my opinion.