Recovery time
Garbage collection timing, from a developer’s perspective, has two points:
- 1 active collection, such as a manual call to system.gc ();
- 2 passive recycling, e.g. LargeObj large = new LargeObj(); The LargeObj() object is found to be too large for the remaining space, and the garbage collection mechanism is triggered.
Most cases are passive recycling, we will analyze the process of passive recycling:
- 1 LargeObj large = new LargeObj(); This line of code creates two objects. One is the reference to large, which is placed in the method stack. A new LargeObj() object is placed in the heap and gc is triggered if the heap does not fit LargeObj().
- We can make the heap into a table. Gc will check whether the current object is recyclable from top to bottom and left to right line by line. If the object is invalid or weak, it will be directly recycled. If it is a soft reference, it is only marked;
- 3. After step 2, gc collects some invalid objects and weak references, and then sees if there is enough memory. If there is enough memory, gc goes to step 6, and if there is not enough memory, it goes to Step 4.
- 4. The soft reference marked in step 2 is reclaimed because the memory is not enough.
- 5 After step 4, if the memory is sufficient, go to Step 6. If the memory is insufficient, an OOM exception is directly thrown.
- 6 allocate memory for LargeObj() directly in the heap space, and put large on the stack, pointing to LargeObj() object;
How to determine whether it is recyclable
Reachability analysis algorithm: detect whether each object in the heap is reachable to GCRoot, if reachable, it is a live object, if not, it is a dead object; Dead objects can be reclaimed directly.
Points that can be used as GCRoot:
Object referenced by static properties and constants in method area 2 Object referenced by virtual machine stack and local method stack 3 Member variable of active thread
Let’s write a demo to verify that the object referenced in the virtual machine stack is GCRoot
public class Test2 { public static int _10M = 10 * 1024 * 1024; Private byte[] Memory = new byte[_10M]; Public static void main(String[] args) {system.out.println (" method not on the stack "); // Print memory system.gc (); printMemory(); // test(); // After the test method is out of the stack, recycle again, and print the memory state system.gc (); System.out.println(" method out of stack "); printMemory(); } public static void test() {public static void test() {public static void test() {public static void test(); //test2 = null; // While the method is still on the stack, try to reclaim and print the memory state system.gc (); System.out.println(" create local variable, method not out of stack "); printMemory(); } public static void printMemory() {String total = Runtime.geTrunTime ().totalMemory() / 1024 / 1024 + "M"; System.out.println("total: " + total); String free = Runtime.getRuntime().freeMemory() / 1024 / 1024 + "M"; System.out.println("free: " + free); }}Copy the code
The above code logic is very simple, we first print the current memory state; Then enter the test code (holding a memory block of 10M) to print the memory status, and finally wait for the test code to finish execution (out of the stack), and then print the memory status. The log is as follows:
Method unpushed Total: 245M free: 242M Creates local variables, method unpushed Total: 245M free: 233M Method unpushed Total: 245M free: 243MCopy the code
The test2 object holds a 10-megabit array, which is reachable to test2, so even gc() does not reclaim it. So it prints 233M free space. By the time the test() method is removed from the vm stack, the test2 object is no longer GCRoot, so 10M memory can be reclaimed, so when we perform gc() again, the available memory is 243M. This proves that only objects held by references in the virtual machine stack can be GCRoot; Now open the comment for the “keyPoint” point and run it again as follows:
Method unpushed Total: 245M free: 242M Creates local variables, method unpushed Total: 245M free: 242M Method unpushed Total: 245M free: 243MCopy the code
As you can see, if test2=null; Then 10M of memory can be reclaimed directly, because even though new Test2() is still in the heap and Test2 is a reference in the vm stack, Test2 no longer refers to new Test2(), so new Test2() is not GCRoot. So 10 meters of memory is being reclaimed. Other gcroots can be verified in the same way.
How to recycle
Since memory can be thought of as a table, there are probably two strategies for recycling unused cells:
- 1 mark the useless first, and then clean up all the useless;
- 2 Mark the useful ones first, then copy the useful ones and clean the whole grid again.
It can be broken down into the following three methods:
- 1 mark – clean up
Scanning the entire block of memory from top to bottom from left to right, marking unused memory, and then cleaning up all marked cells has two disadvantages; 1. If the reclaimed memory is incoherent, it will cause a large number of memory fragments. 2.
- 2 mark – tidy
First from top to bottom from left to right to scan the entire memory fast, will be useless memory for marking, and then will be useless memory are moved to one end, useful memory is on the other end, and then directly to the useless memory end can be cleared. The advantage is to avoid memory fragmentation, but the disadvantage is still low efficiency, the more the collection is slower.
- 3 Replication Algorithm
Divide the memory into two pieces of the same size, A and B, and use one piece each time, for example, A(we call A the use block and B the live block). When the memory is full, scan A, copy the live objects on A to B, and then clear ALL A, and then swap the roles of A and B. The advantage is that it is fast. Instead of iterating through recycling, it can directly empty A at A time. The disadvantage is that it wastes memory, only half of the memory can be used at A time, which increases the frequency of GC () in A way, which is not cost-effective. If we know that A large number of objects can be recycled each time, for example, 4/5 will be recycled, that is, only 1/5 will survive, then we can adjust the ratio of A and B to 4:1, that is, 4/5 will be given to A for the objects created, and 1/5 will be given to B for the objects left after recycling. So what if there are a couple of times where more than a fifth of the objects are alive, we need a memory guarantee to deal with that, and that’s where the generational strategy comes in.
Now that we know that all of the above memory allocation methods have drawbacks, such as inefficiency or waste of space, we can choose different reclamation algorithms for different scenarios. In general, we can classify objects into two broad categories: long-worker objects and short-worker objects.
- Long worker object: an object with a long lifetime that survives multiple gc() cycles, such as an object pointed to by a member variable.
- Short worker object: short life cycle, short lifetime, usually a single GC () to kill it, such as the local variable to the object, after the method stack GG.
Therefore, if it is for the recycling of long-working objects, because there are too many surviving objects, only a little can be recycled at a time, then the replication algorithm will not work, so many surviving objects, equivalent to almost all replication; And the number of viable blocks means that the proportion of viable blocks is large, so it will waste too much memory; So the copy algorithm doesn’t work; So if you use mark-collation, because a lot of survival, so less dead, mark collation is recycling dead objects, so less recycling, but improve efficiency, is suitable for!
Similarly, for the collection of temporary objects, less alive, more dead, so with mark-collation, a lot of recycling, not appropriate, then with the copy algorithm, because there are fewer alive, so there is less to copy; Moreover, the survival stack is small in proportion, so it does not waste much memory, so it is suitable for replication algorithms.
Based on this, we can: for the long work object using mark-collation algorithm, for the short work object, using the copy algorithm, which is called the generation algorithm. The place where the long worker object is placed is called the old age, and the place where the short worker object is placed is called the new generation, and then different recycling algorithms are used for different generations.
At this point, can be summed up as a sentence: the old era adopts mark-collation algorithm, the new generation adopts replication algorithm.
So, which objects are put into the new generation and which objects are put into the old age?
How to allocate
Let’s start with the object allocation process (now assume that both the new generation and the old generation are empty):
- 1 create an object User User = new User();
- 2 Newly created new User() objects are allocated to the new generation first, if the new generation can fit.
- 3. If the new generation cannot be released, a GC () is attempted for the new generation, called MinorGC. After this GC (), the age of all surviving objects will be +1.
- 4 if the new generation still can’t put the new User() object after the GC (), it can be directly put into the old age, which can be referred to as “volume standard”.
- 5 If the old age can not be released, then the old age will perform a GC (), called MajorGC.
- 6 If the soft reference cannot be recovered after gc(), perform a secondary GC (), that is, reclaim the soft reference.
- 7 If the alarm persists, an OOM exception is displayed.
To sum up, there are two conditions to enter the old age:
- Age pass: objects of age 15 (surviving 15 gc occurrences) are placed in the old age. This is temporal.
- Size up to standard: the new generation can’t put down after MinorGC, so it goes directly to the old age. This is spatial
We said that the replication algorithm needs to have a memory guarantee in case the survivable block doesn’t fit, and the memory guarantee in this case is old age, that is, if the new generation survivable block B doesn’t fit any survivable objects, then it’s old age. That’s what we’re talking about above. And we also know that if we create a large object that the new generation can’t let go of, then MinorGC of the new generation will be triggered. In other words, if we create a large object frequently, we will cause MinorGC of the new generation, and when GC happens, there will be a stop world process, that is, stopping all threads. This can cause a lag, so we try to avoid creating large objects.
New generation of specific recycling process
The new generation uses the replication algorithm. As we mentioned above, in order to avoid wasting memory, the allocation ratio of the replication algorithm is not 1:1. The allocation ratio of HotSpot VIRTUAL machine is 9:1, or more specifically 8:1:1. Among them, Eden has 8 copies, and each of the two survivor zones has 1 copy. Each time the objects come, Eden and one survivor zone are used to store the objects, and the other survivor block is used to store the objects that survive after GC (). The survivor block we will use is survivor from, and the survivor block that holds the surviving object is survivor to. Here’s a simulation of the process:
- 1 create object User User = new User();
- 2 Use Eden + survivor from to store the new User() object and see if it can fit.
- If the Eden + survivor from block does not fit, we MinorGC the Eden + survivor from block: copy the surviving object to the survivor to block with age +1, then empty Eden and survivor from block.
- 4 In this case, Eden and survivor FROM are empty, and survivor to stores the surviving object. In this case, the identity of survivor FROM and survivor to is exchanged, that is, Eden + survivor to is used for the next memory allocation. The survivor block uses survivor from.
The subsequent allocation strategy is the same as above.
So why do we use 8:1:1 three blocks of ram, can’t we just use 9:1?
We know that the replication algorithm principle is to use two pieces of the same size of the block of memory, as a using block, a block as a survival, but it will waste of space, use the tactics of 9:1, but in that case, copy when there is a risk, so the need for additional guarantee, so we can’t think of a way to: Is memory 9:1 when used and 1:1 when copied? There are!
Now we divide the memory into three pieces: A:B1:B2 = 8:1:1, we first use A+B1 to store objects, now we use 9 copies (9:1 in use), when waiting for GC () to replicate, we are equivalent to let B1 and B2 copy each other (1:1 in replication), on the premise that the surviving objects are less than or equal to 10%(most cases are satisfied). After that, we will use :A+B2 for the next use, and use B1 as the survival block. As you can see, using the 8:1:1 approach is equivalent to providing A primary block A and two mount blocks B1 and B2, which is more flexible. With 9:1, none of this is possible.