preface

The JVM memory model and the JVM garbage collection mechanism have always been the topic of the Java industry practitioners (actual tuning, interview), Jack again with us to learn the JVM garbage collection mechanism. It may be difficult to skip the advanced to architect stage as a Java practitioner without mastering these things.


Some concepts of JVM tuning

The data type

There are two types of data types in Java virtual machines: basic and reference. A primitive variable holds the original value, that is, the value it represents is the value itself. Variables of reference type hold reference values. A “reference value” represents a reference to an object, not the object itself, which is stored at the address represented by the reference value.

Basic types include: byte, short, int, long, char, and float, double, Boolean, returnAddress

Reference types include: class type, interface type, and array.


Heap and stack

Heap and stack are key to program operation, and it is necessary to clarify their relationship.


The size of a Java object

The size of the base data type is fixed, which is not covered here. For Non-primitive Java objects, the size is debatable.

In Java, the size of an empty Object is 8 bytes, which is just the size of an Object that holds no attributes in the heap. Look at the following statement:

Object ob = new Object();

This completes the life of a Java object in the program, but it takes up 4byte+8byte space. Four bytes is the amount of space required to hold references in the Java stack described in the previous section. And those 8 bytes are the information about the objects in the Java heap. Because all Non-primitive Java objects need to inherit Object by default, any Java Object must have a size greater than 8 bytes.

Given the size of Object, we can calculate the size of other objects.

Class NewObject { int count; boolean flag; Object ob; } Duplicate codeCopy the code

The size is: empty Object size (8 bytes)+int size (4 bytes)+Boolean size (1byte)+ empty Object reference size (4 bytes)=17 bytes. However, since Java allocates object memory in multiples of 8, the nearest multiples of 8 greater than 17 bytes is 24, so the size of this object is 24 bytes.

Note here the size of the wrapper type for the base type. Because these wrapper types are already objects, they need to be treated as objects. The size of the wrapper type is at least 12 bytes (at least the space required to declare an empty Object), and 12 bytes contains no valid information. Also, because Java objects are integers multiple of 8, a primitive wrapper class is at least 16 bytes. This memory footprint is horrible, it is N times the size of the base type (N>2), and some of the memory footprint is even more dramatic (just think about it). Therefore, use wrapper classes sparingly if possible. After JDK5.0, the Java virtual machine is optimized for storage because automatic cast is added.

Reference types

Object reference types are divided into strong reference, soft reference, weak reference and virtual reference.


JVM tuning – Basic garbage collection algorithms

Garbage collection algorithms can be divided from different perspectives:

According to the basic recycling strategy


Reference Counting:

Older collection algorithms. The principle is that the object has a reference, that is, one count is increased, and one count is decreased by removing a reference. In garbage collection, only objects with a count of 0 are collected. The most deadly aspect of this algorithm is its inability to handle circular references.


Mark-sweep:

The algorithm is implemented in two stages. The first phase marks all referenced objects starting from the root node, and the second phase walks through the heap to clean up unmarked objects. This algorithm requires the suspension of the entire application and generates memory fragmentation.


Copying:

This algorithm divides the memory space into two equal regions and uses only one of them at a time. Garbage collection iterates through the current area of use and copies objects in use to another area. The second algorithm only deals with the objects that are in use each time, so the replication cost is relatively small. Meanwhile, after the replication, the corresponding memory can be arranged, and there will be no “fragmentation” problem. Of course, the disadvantage of this algorithm is also obvious, that is, it requires twice the memory space.


Mark-compact:

This algorithm combines the advantages of “mark-erase” and “copy” algorithms. The first stage marks all referenced objects from the root node. The second stage traverses the heap, removing unmarked objects and “compressing” the surviving objects into one of the blocks of the heap, in order. This algorithm avoids the fragmentation problem of “mark – clean” and the space problem of “copy” algorithm.


Divided according to the way of partition treatment

Incremental Collecting :** Real-time garbage collection algorithm, that is, garbage collection is carried out at the same time as the application. For some unknown reason collectors in JDK5.0 do not use this algorithm.

** Generational Collecting :** Garbage collection algorithms based on life cycle analysis of objects. Objects are divided into young generation, old generation, and persistent generation, and different algorithms (one of the above methods) are used to recycle objects with different life cycles. Today’s garbage collectors (starting with J2SE1.2) use this algorithm.


By system thread

Serial collection: Serial collection uses a single thread to handle all garbage collection because it is easy and efficient to implement without multithreaded interaction. However, its limitations are obvious, namely that it cannot take advantage of multiple processors, so this collection is suitable for single-processor machines. Of course, this collector can also be used on multiprocessor machines with small data volumes (around 100M).

** Parallel collection :** Parallel collection uses multiple threads to handle garbage collection, making it fast and efficient. And in theory, the more cpus there are, the better the parallel collector.

** Concurrent collection :** Compared to serial collection and parallel collection, the first two require the entire runtime environment to be suspended while garbage collection is being performed, while only garbage collection is running. Therefore, the system has significant pauses in garbage collection, and the pauses are longer because the heap is larger.


Three, the problems faced by garbage recycling

How to Distinguish garbage

The “reference counting” method mentioned above is determined by statistical control over the number of references to objects generated and deleted. The garbage collector collects objects with a count of 0. But this approach does not solve circular references. Therefore, the later implemented garbage detection algorithm starts from the root node where the program runs, traverses the entire object reference, and looks for the surviving object. So where does garbage collection start in this implementation? That is, where to start to find out which objects are being used by the current system. The difference between the heap and the stack analyzed above is that the stack is where the actual program execution takes place, so to get what objects are being used, you need to start with the Java stack. Also, a stack corresponds to a thread, so if there are multiple threads, all stacks corresponding to those threads must be checked.

At the same time, in addition to the stack, there are registers and so on when the system is running, but also store the program running data. In this case, the reference of to stack or register as a starting point, we can find the object in the heap, and from these objects to find other objects in the heap of reference, this reference to gradually expand, ultimately ends with a null reference or basic type, forming a reference of the objects in the Java stack as the object of the root node of a tree, if there are multiple references the stack, It will eventually form a tree of multiple objects. The objects in the object tree are required by the current system and cannot be garbage collected. The rest of the objects can be treated as unreferenced objects and can be recycled as garbage.

Therefore, the starting point for garbage collection is some root objects (Java stack, static variables, registers…). . The simplest Java stack is the main function executed by Java programs. This collection method is also the “mark-clean” collection method mentioned above


How to handle fragments

Since the lifetime of different Java objects is not constant, fragmented memory can occur if memory is not defragmented after a program has been running for some time. The most immediate problem with fragmentation is that large chunks of memory cannot be allocated and programs run less efficiently. Therefore, in the basic garbage collection algorithm mentioned above, the “copy” approach and the “mark-tidy” approach can solve the fragmentation problem.


How to solve the simultaneous object creation and object reclamation problem

Garbage collector threads recycle memory, while program running threads consume (or allocate) memory, one recycle memory, one allocate memory, the two are contradictory. Therefore, in the existing garbage collection method, before garbage collection, it is generally necessary to suspend the entire application (that is, to suspend the allocation of memory), and then garbage collection, and then continue the application after the collection is completed. This realization is the most direct, and the most effective way to solve the contradiction between the two.

However, there is an obvious disadvantage of this approach, which is that as the heap space continues to increase, the garbage collection time will also increase correspondingly, and the corresponding application pause time will also increase correspondingly. For applications with high time requirements, such as a maximum pause time of several hundred milliseconds, this limit is likely to be exceeded when the heap space is larger than a few gigabytes, in which case garbage collection becomes a bottleneck. To solve this contradiction, there is the concurrent garbage collection algorithm, in which the garbage collection thread runs at the same time as the program running thread. In this way, the pause problem is solved, but because of the need to generate objects and recycle objects at the same time, the complexity of the algorithm will be greatly increased, the processing capacity of the system will be correspondingly reduced, at the same time, the “fragmentation” problem will be more difficult to solve.


Iv. Details on Garbage Recycling by Generation (1)

Why do we divide generations

Generational garbage collection strategies are based on the fact that different objects have different lifecycles. Therefore, objects with different life cycles can be collected in different ways to improve collection efficiency.

In the running process of Java programs, a large number of objects will be generated, some of which are related to business information, such as Session objects, threads and Socket connections in Http requests. Such objects are directly linked to business, so their life cycle is relatively long. But there are some objects, mainly temporary variables generated in the process of running the program, the life cycle of these objects will be relatively short, such as: String object, because of its invariable class characteristics, the system will produce a large number of these objects, some objects can even be recycled only once.

Just think, in the case of without distinguish objects alive time, every time the garbage collection is to recycle the entire heap space, spend time relative to the President, at the same time, because every recycling need to traverse all live objects, but in fact, for the long object life cycle, this kind of traverse is ineffective, because may traverse many times, but they still exist. Therefore, generational garbage collection adopts the idea of divide and conquer to divide generations. Objects with different life cycles are placed in different generations, and the most suitable garbage collection method for different generations is adopted for recycling.


How generational

As is shown in

VMS are divided into three generations: Young Generation, Old Generation, and Permanent Generation. The persistent generation mainly stores the class information of Java classes and has little to do with the Java objects to be collected by garbage collection. The division of the young generation and the old generation has a greater impact on garbage collection.


Young generation:

All newly generated objects are first placed in the young generation. The goal of the younger generation is to collect objects with short life spans as quickly as possible.

The younger generation is divided into three sections. One Eden zone and two Survivor zones (generally). Most objects are generated in the Eden zone. When Eden is full, the surviving objects will be copied to the Survivor zone (one of the two). When this Survivor zone is full, the surviving objects in this Survivor zone will be copied to another Survivor zone. When this Survivor zone is full, Objects copied from the first Survivor zone that are still alive will be copied to “Tenured”. It should be noted that the two Survivor zones are symmetric and have no sequence relationship, so there may be objects copied from Eden and the object copied from the previous Survivor in the same zone, while only the object copied from the first Survivor to the old zone. Also, one of the Survivor zones is always empty. At the same time, Survivor zones can be configured to be multiple (more than two) depending on program requirements, which increases the duration of an object in the young generation and reduces the likelihood of it being placed in the old generation.


Old generation:

Objects that survive N garbage collections in the young generation are put into the old generation. Therefore, you can think of the tenured generation as holding objects with long life cycles.


Lasting generation:

Used to store static files, nowadays Java classes, methods, 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 needs to be set up to store the classes added during the run. The persistent generation size can be set by -xx :MaxPermSize=.


When is garbage collection triggered

Because objects are processed in generations, garbage collection regions and times are different. There are two types of GC: Scavenge GC and Full GC.


Scavenge GC

Normally, when a new object is created and Eden fails to apply for space, the Scavenge GC is triggered to clean up the Eden exploiture, and the surviving objects are moved to the Survivor zone. Then the two zones of Survivor are collated. In this way, GC is performed on the Eden area of the young generation without affecting the old generation. Since most objects start from Eden, and Eden is not allocated very large, GC in Eden will occur frequently. Therefore, a fast and efficient algorithm is generally needed to make Eden free as soon as possible.


Full GC

Clean up the entire heap, including Young, Tenured, and Perm. Full GC is slower than Scavenge due to the need to recycle the entire pair, so minimize the amount of Full GC you do. A large part of the process of tuning the JVM is tuning FullGC. There are several possible reasons for Full GC:

  • Tenured generations are full
  • Persistent generation (Perm) is full
  • System.gc() is called on display
  • The allocation strategy for each field of the Heap has changed dynamically since the last GC

1.7 JVM Tuning Summary (vi) – Generational garbage collection detail 2


Garbage collection process by generation


Choose the appropriate garbage collection algorithm

Serial collector

It is more efficient to handle all garbage collection with a single thread because there is no multi-threaded interaction. However, you can’t take advantage of multiple processors either, so this collector is suitable for single-processor machines. Of course, this collector can also be used on multiprocessor machines with small data volumes (around 100M). This can be turned on using -xx :+UseSerialGC.


Parallel collector

Parallel garbage collection is performed for the young generation, thus reducing garbage collection time. Generally used on multithreaded multiprocessor machines. Use -xx :+ useParallelgc. open. The parallel collector was introduced in THE J2SE5.0 V6 update and is enhanced in Java SE6.0 to allow parallel collection of older generations. If the tenured generation does not use concurrent collection, the default is single-threaded garbage collection, thus limiting the ability to scale.

Open with -xx :+UseParallelOldGC.

Use -xx :ParallelGCThreads= to set the number of threads for parallel garbage collection. This value can be set to equal the number of processors on the machine.

This collector can be configured as follows:

** Maximum garbage collection pause :** Specifies the maximum pause time for garbage collection, specified by -xx :MaxGCPauseMillis=. Milliseconds. If specified, the heap size and garbage collection parameters are adjusted to reach the specified value. Setting this value may reduce application throughput.

** Throughput :** Throughput is the ratio of garbage collection time to non-garbage collection time, set by -xx :GCTimeRatio=, expressed as 1/ (1+N). For example, -xx :GCTimeRatio=19 indicates that 5% of the time is spent on garbage collection. The default is 99, or 1% of the time spent garbage collection.


Concurrent collector

Most of the work can be done concurrently (the application does not stop), and garbage collection is paused only for a small amount of time. This collector is suitable for medium – and large-scale applications with high response time requirements. Open with -xx :+UseConcMarkSweepGC.

The concurrent collector mainly reduces the pause time of the aged generation by using separate garbage collection threads to track reachable objects without stopping the application. In each generation garbage collection cycle, the concurrent collector pauses the entire application briefly at the beginning of the collection and again during the collection. The second pause is slightly longer than the first, with multiple threads collecting garbage at the same time.

Concurrent collectors use processors in exchange for short pause times. On an N processor system, the concurrent collection part uses K/N available processors for collection, generally 1<=K<=N/4.

Using the concurrent collector on a host with only one processor and setting it to Incremental mode also results in shorter pause times.

** Floating Garbage: ** Because Garbage is collected at the same time as the application runs, some Garbage may be generated when the Garbage collection is completed, resulting in “Floating Garbage” that needs to be collected in the next Garbage collection cycle. As a result, concurrent collectors typically require 20% of their reserved space for floating garbage.

**Concurrent Mode Failure: ** The Concurrent collector collects while the application is running, so it is necessary to ensure that the heap has sufficient space for the program during the garbage collection period, otherwise the heap is full before the garbage collection is complete. In this case, a “concurrent mode failure” occurs, at which point the entire application is paused for garbage collection.

** Start Concurrent collector: ** Because Concurrent collections are collected while the application is running, you must ensure that sufficient memory is available to the program before the collection is complete, otherwise a “Concurrent Mode Failure” will occur. By setting – XX: how much is left CMSInitiatingOccupancyFraction = specify the heap to perform concurrent collection


summary

Serial processor:

— Application: Small amount of data (about 100M); Single processor applications with no response time requirements.

Cons: Only for small applications

Parallel processor:

Application scenario: Medium – and large-sized applications with high throughput requirements, multiple cpus, and no requirements on application response time. Examples: background processing, scientific calculation.

Disadvantages: Application response time may be extended during garbage collection

Concurrent processor:

Application: Medium – and large-sized applications with multiple cpus and high requirements on application response time. Examples: Web server/application server, telecom switching, integrated development environment.

That’s the end of the article

Here is a mind map of the JVM and performance tuning for those interested

Like xiaobian to share technical articles can like attention oh!

Spring, Mybatis, JVM, Zookeeper, Spring MVC, Redis distributed lock, etc.