In depth understanding of JVM-JVM object creation and memory allocation mechanism-03-2
The previous chapter talked about the object creation part of object creation and memory allocation mechanism, and this chapter talked about memory allocation mechanism.
Object memory allocation mechanism
Object memory allocation flowchart:
Allocation on object stack
In most people's understanding, objects are placed in the heap, so is it possible to put them on the stack? The answer is yes.
Through JVM memory allocation, we can know that objects in JAVA are allocated on the heap. When objects are not referenced, we need to rely on GC to recover memory. If there are a large number of objects, it will bring great pressure to GC and indirectly affect the performance of the application. In order to reduce the number of temporary objects allocated in the heap, the JVM determines that the object will not be accessed externally through escape analysis. If there is no escape, the object can be allocated memory on the stack, so that the memory space occupied by the object can be destroyed with the stack frame out of the stack, which reduces the pressure of garbage collection.
Object escape analysis: it is to analyze the dynamic scope of an object. When an object is defined in a method, it may be referenced by external methods, such as passing it to other places as call parameters.
For example, these two codes:
public User test1() { User user = new User(); user.setId(1); user.setName("zhuge"); //TODO save to database return user; } public void test2() { User user = new User(); user.setId(1); user.setName("zhuge"); //TODO save to database }
If the user object of test1 is returned, it may be called by other objects, so its scope of action is uncertain. If the test2 object is not returned, it is not an escaped object. Such an object can be allocated in the stack, and the stack space can be released when the method ends.
In this case, the JVM can optimize the memory allocation location of the object by turning on the escape analysis parameter (- XX:+DoEscapeAnalysis), so that it is preferentially allocated on the stack through scalar replacement (allocation on the stack). After JDK7, escape analysis is turned on by default. If you want to turn off the use parameter (- XX:-DoEscapeAnalysis)
Scalar substitution: when it is determined through escape analysis that the object will not be accessed externally and the object can be further decomposed, the JVM will not create the object, but decompose several member variables of the object to be replaced by the member variables used in this method. These replaced member variables allocate space on the stack frame or register, so that there will not be insufficient allocation of object memory because there is no large continuous space. Turn on the scalar substitution parameter (- XX:+EliminateAllocations), which is turned on by default after JDK7. The popular point is that when an object is allocated in the stack and the stack memory is insufficient, the variables in the object are disassembled so that it can be divided into multiple small memory and put into the stack.
Scalar and aggregate quantity: scalar is the quantity that cannot be further decomposed, and the basic data type of JAVA is scalar (such as int, long and other basic data types and reference types). The opposition of scalar is the quantity that can be further decomposed, and this kind of quantity is called aggregate quantity. In JAVA, an object is an aggregate that can be further decomposed.
Example of allocation on stack:
/** * Allocation on stack, scalar substitution * The code calls alloc() 100 million times. If it is allocated to the heap, it needs more than 1GB of heap space. If the heap space is less than this value, GC will be triggered. * * GC will not occur with the following parameters * -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations * Using the following parameters to turn off escape analysis will cause a large number of GC * -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations * */ public class AllotOnStack { public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { alloc(); } long end = System.currentTimeMillis(); System.out.println(end - start); } private static void alloc() { User user = new User(); user.setId(1); user.setName("zhuge"); } }
Because the user object in the alloc method does not escape, if the escape analysis is enabled, the user object will be allocated to the stack, and the user object will be destroyed after the execution of the method. Even if the object is called 100 million times by the main method, the object will be destroyed after the execution of the method. However, if the escape analysis is turned off, the user object will be allocated to the heap, and the object will not be destroyed even after the execution of the method, Instead, wait until the heap memory is full before being recycled, and you will see the gc recycling (the heap memory parameter of the above code is set to 15 megabytes)
Conclusion: allocation on stack depends on escape analysis and scalar substitution
Objects are assigned in Eden area
In most cases, objects are allocated in the Eden area in the Cenozoic. When the Eden area does not have enough space for allocation, the virtual machine will initiate a Minor GC. Let's do a practical test.
Before testing, let's take a look at the difference between Minor GC and Full GC?
The new generation GC has a very fast garbage collection speed.
Major GC/Full GC: generally, the garbage of the old generation, the young generation and the method area will be recycled. The speed of major GC is generally more than 10 times slower than that of Minor GC.
Eden and Survivor areas are 8:1:1 by default
A large number of objects are allocated in eden area. When eden area is full, minor gc will be triggered, and more than 99% of objects may become garbage and be recycled. The remaining surviving objects will be moved to the empty survivor area. The next time eden area is full, minor gc will be triggered to recycle the garbage objects in eden area and survivor area, and move the remaining surviving objects to another empty survivor area at one time, Because the objects of the Cenozoic Era live and die day and day, and the survival time is very short, the default 8:1:1 ratio of the JVM is very appropriate. Let the eden area be as large as possible and the survivor area be enough,
The JVM has this parameter by default -xx:+useadaptivesizepolicy (enabled by default), which will cause the 8:1:1 ratio to change automatically. If you do not want to change the ratio, you can set the parameter -XX:-UseAdaptiveSizePolicy
The object transfer diagram of Eden and Survivor area is in JVM memory model This chapter has talked about
Example:
//Add running JVM parameters: - XX:+PrintGCDetails public class GCTest { public static void main(String[] args) throws InterruptedException { byte[] allocation1, allocation2/*, allocation3, allocation4, allocation5, allocation6*/; allocation1 = new byte[60000*1024]; //allocation2 = new byte[8000*1024]; /*allocation3 = new byte[1000*1024]; allocation4 = new byte[1000*1024]; allocation5 = new byte[1000*1024]; allocation6 = new byte[1000*1024];*/ } } Operation results: Heap PSYoungGen total 76288K, used 65536K [0x000000076b400000, 0x0000000770900000, 0x00000007c0000000) eden space 65536K, 100% used [0x000000076b400000,0x000000076f400000,0x000000076f400000) from space 10752K, 0% used [0x000000076fe80000,0x000000076fe80000,0x0000000770900000) to space 10752K, 0% used [0x000000076f400000,0x000000076f400000,0x000000076fe80000) ParOldGen total 175104K, used 0K [0x00000006c1c00000, 0x00000006cc700000, 0x000000076b400000) object space 175104K, 0% used [0x00000006c1c00000,0x00000006c1c00000,0x00000006cc700000) Metaspace used 3342K, capacity 4496K, committed 4864K, reserved 1056768K class space used 361K, capacity 388K, committed 512K, reserved 1048576K
Allocation1 takes up a lot of space. 60000k cannot be allocated on the stack, so it will be allocated to the heap. When you see the print information, you can see that a total of 65536k in Eden area has been used for 100% (even if there is no object in Eden area, some parts will be occupied by some things of JVM, so you see that it takes up 100%). The from//to/Survivor area has not been used for 10752k. If another object needs to be allocated to the heap, Minor GC will occur, The objects in Eden area must be transferred to survivor area, but we can see that 10752k in survivor area is less than 60000k, so survivor can't let go. We can only let allocation1 enter the elderly generation in advance. See the operation of the following code.
//Add running JVM parameters: - XX:+PrintGCDetails public class GCTest { public static void main(String[] args) throws InterruptedException { byte[] allocation1, allocation2/*, allocation3, allocation4, allocation5, allocation6*/; allocation1 = new byte[60000*1024]; allocation2 = new byte[8000*1024]; /*allocation3 = new byte[1000*1024]; allocation4 = new byte[1000*1024]; allocation5 = new byte[1000*1024]; allocation6 = new byte[1000*1024];*/ } } Operation results: [GC (Allocation Failure) [PSYoungGen: 65253K->936K(76288K)] 65253K->60944K(251392K), 0.0279083 secs] [Times: user=0.13 sys=0.02, real=0.03 secs] Heap PSYoungGen total 76288K, used 9591K [0x000000076b400000, 0x0000000774900000, 0x00000007c0000000) eden space 65536K, 13% used [0x000000076b400000,0x000000076bc73ef8,0x000000076f400000) from space 10752K, 8% used [0x000000076f400000,0x000000076f4ea020,0x000000076fe80000) to space 10752K, 0% used [0x0000000773e80000,0x0000000773e80000,0x0000000774900000) ParOldGen total 175104K, used 60008K [0x00000006c1c00000, 0x00000006cc700000, 0x000000076b400000) object space 175104K, 34% used [0x00000006c1c00000,0x00000006c569a010,0x00000006cc700000) Metaspace used 3342K, capacity 4496K, committed 4864K, reserved 1056768K class space used 361K, capacity 388K, committed 512K, reserved 1048576K
It is obvious that 34% of the space in the old age of ParOldGen has been used, but it has not been used before. That is, the allocation1 object has been put into the old age. The space on the old generation is enough to store allocation1, so there will be no Full GC. After Minor GC is executed, if the objects allocated later can exist in the eden area, memory will still be allocated in the eden area. You can perform the following code verification:
public class GCTest { public static void main(String[] args) throws InterruptedException { byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6; allocation1 = new byte[60000*1024]; allocation2 = new byte[8000*1024]; allocation3 = new byte[1000*1024]; allocation4 = new byte[1000*1024]; allocation5 = new byte[1000*1024]; allocation6 = new byte[1000*1024]; } } Operation results: [GC (Allocation Failure) [PSYoungGen: 65253K->952K(76288K)] 65253K->60960K(251392K), 0.0311467 secs] [Times: user=0.08 sys=0.02, real=0.03 secs] Heap PSYoungGen total 76288K, used 13878K [0x000000076b400000, 0x0000000774900000, 0x00000007c0000000) eden space 65536K, 19% used [0x000000076b400000,0x000000076c09fb68,0x000000076f400000) from space 10752K, 8% used [0x000000076f400000,0x000000076f4ee030,0x000000076fe80000) to space 10752K, 0% used [0x0000000773e80000,0x0000000773e80000,0x0000000774900000) ParOldGen total 175104K, used 60008K [0x00000006c1c00000, 0x00000006cc700000, 0x000000076b400000) object space 175104K, 34% used [0x00000006c1c00000,0x00000006c569a010,0x00000006cc700000) Metaspace used 3343K, capacity 4496K, committed 4864K, reserved 1056768K class space used 361K, capacity 388K, committed 512K, reserved 1048576K
allocation3, allocation4, allocation5 and allocation6 are all placed in Eden district
Large objects directly enter the elderly generation
Large objects are objects that require a lot of continuous memory space (such as strings and arrays). JVM parameter - XX:PretenureSizeThreshold can set the size of large objects. If the object exceeds the set size, it will directly enter the older generation, not the younger generation. This parameter is only valid under the two collectors of Serial and ParNew.
For example, set the JVM parameters: - XX:PretenureSizeThreshold=1000000 (in bytes) - XX:+UseSerialGC, and then execute the first program above. You will find that the large object has entered the old age directly
Why?
In the younger generation, the transfer operation from Eden area to survivor is copied in the past. In order to avoid reducing the efficiency of the copy operation when allocating memory for large objects, large objects are directly put into the old age.
The long-term survivors will enter the elderly generation
Since the virtual machine adopts the idea of generational collection to manage memory, it must be able to identify which objects should be placed in the new generation and which objects should be placed in the old generation. To do this, the virtual machine gives each object an object Age counter.
If the object can still survive after Eden was born and passed the first Minor GC and can be accommodated by Survivor, it will be moved to Survivor space and the object age will be set to 1. Every time an object Survivor survives MinorGC, its age increases by 1 year. When its age increases to a certain extent (the default is 15 years old, CMS collector is 6 years old by default, and different garbage collectors will be slightly different), it will be promoted to the elderly generation. The age threshold for the object to be promoted to the old age can be set through the parameter - XX:MaxTenuringThreshold.
stay JVM memory model This chapter also discusses in detail
Object dynamic age judgment
If the total size of a batch of objects is greater than 50% of the memory size of the Survivor area in the current Survivor area (one area is the s area where the objects are placed), then objects greater than or equal to the maximum age of this batch of objects can directly enter the elderly generation. For example, there are a batch of objects in the Survivor area, The sum of multiple age objects with age 1 + age 2 + age n exceeds 50% of the Survivor area. At this time, objects with age n or above will be put into the elderly generation. This rule actually hopes that those who may survive for a long time will enter the elderly generation as soon as possible. The object dynamic age judgment mechanism is generally triggered after minor gc.
For example, there are 60 objects in the survivor area. Ten objects are 1, twenty objects are 2, twenty objects are 3, and ten objects are 4. If minor gc occurs, it is found that the first 50 objects account for 50% of the survivor area, the maximum age of the first 50 objects is 3, and the n mentioned above is 3, so the objects with age > = 3 will be put into the elderly generation, That is, the last 30 objects (aged 3 and 4) are all put into the old age.
Old age space allocation guarantee mechanism
Before every minor gc of the younger generation, the JVM will calculate the remaining available space in the old age
If this free space is less than the sum of the sizes of all existing objects in the younger generation (including garbage objects)
It depends on whether the parameter "- XX:-HandlePromotionFailure" (jdk1.8 is set by default) is set
If you have this parameter, you will see whether the available memory size of the older generation is greater than the average size of objects entering the old age after each minor gc. Generally speaking, if we have done minor gc twice before, and after these two gc, there are objects entering the old age, for example, 8m for the first time, 6m for the second time, with an average of 7m, then judge whether the remaining space of the old age is greater than 7m before this minor gc.
If the result of the previous step is less than or the parameters mentioned above are not set, a full GC will be triggered to recycle the garbage of the old generation and the young generation. If there is still not enough space to store new objects after recycling, an "OOM" will occur. (the reason why we should do this is to avoid the occurrence of full GC when the objects in the old generation are larger than those in the old generation. In this way, the previous minor gc will be done in vain, but it is still necessary to full GC. I might as well first full GC, clear some objects and put them in this time, so as to save a minor gc. Although the JVM actually needs to do a minor gc after the full GC is completed, the minor gc this time must be moved and copied to (few objects in other areas)
Of course, if the size of the remaining surviving objects that need to be moved to the old age after minor gc is still larger than the available space of the old generation, full gc will be triggered again. After full gc, if there is still no space to put the surviving objects after minor gc, OOM will also occur.
In a word, it is actually to advance the full gc to the minor gc to avoid the full gc after the minor gc. However, after the full gc, the minor gc needs to be done to transfer the objects to the old age. If the space in the old age is enough, nothing will matter. If it is not enough, the full gc needs to be done again. If the full gc is not enough this time, the "OOM" will occur.
Object memory reclamation
There are almost all object instances in the heap. The first step before garbage collection is to judge which objects have died (that is, objects that can no longer be used by any way).
How to judge that the object has died
Citation counting method
Add a reference counter to the object. Whenever a place references it, the counter will increase by 1; When the reference fails, the counter is decremented by 1; At any time, objects with a counter of 0 can no longer be used.
This method is simple and efficient, but the mainstream virtual machines do not choose this algorithm to manage memory. The main reason is that it is difficult to solve the problem of circular reference between objects. The so-called cross reference between objects is shown in the following code: except that objects objA and objB refer to each other, there is no reference between the two objects. However, because they refer to each other, their reference counters are not 0, so the reference counting algorithm cannot notify the GC collector to recycle them.
public class ReferenceCountingGc { Object instance = null; public static void main(String[] args) { ReferenceCountingGc objA = new ReferenceCountingGc(); ReferenceCountingGc objB = new ReferenceCountingGc(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; } }
When objA executes the new operation, the reference counter is 1
When objB executes the new operation, the reference counter is 1
objB is referenced by the member variable of objA. At this time, the objB counter is 2;
objA is referenced by the member variable of objB, and the objA counter is 2;
objA=null, that is, the instance memory pointing to the heap space is empty. At this time, the counter - 1 is 1. Similarly, objB is 1,
But at this time, objA and objB are garbage objects, because their heap instance memory has been destroyed, but their counter is not 0, so these two garbage objects cannot be recycled.
Reachability analysis algorithm
Take the "GC Roots" object as the starting point and start to search down the referenced objects from these nodes. All the objects found are marked as non garbage objects, and the other unmarked objects are garbage objects. The JVM uses the reachability analysis algorithm
GC Roots root node: local variables of thread stack, static variables, variables of local method stack, etc
For this reachability analysis algorithm, in the above code example, gcroot is objA, the reference chain is objA - > instance - > objb, execute objA=null, then objA - > null, then objA can be recycled without referencing any object
Common reference types
java reference types are generally divided into four types: strong reference, soft reference, weak reference and virtual reference
Strong reference: common variable reference
public static User user = new User();
Soft reference: wrap objects with SoftReference soft reference objects. Normally, they will not be recycled. However, after GC is completed, if it is found that there is no space to store new objects, these soft reference objects will be recycled. Soft references can be used to implement memory sensitive caching.
public static SoftReference<User> user = new SoftReference<User>(new User());
Soft reference has important applications in practice, such as the browser's back button. When you press back, will the page content displayed during back be requested again or removed from the cache? It depends on the specific implementation strategy.
(1) If a web page is recycled at the end of browsing, you need to rebuild it when you press back to view the previously viewed page
(2) If the browsed web pages are stored in memory, it will cause a lot of waste of memory, and even cause memory overflow
Weak reference: wrap the object with the object of WeakReference soft reference type. Weak reference is similar to no reference. GC will recycle it directly and rarely use it
public static WeakReference<User> user = new WeakReference<User>(new User());
Virtual reference: virtual reference is also known as ghost reference or phantom reference. It is the weakest reference relationship and is almost unnecessary
The finalize() method finally determines whether the object is alive or not
Even in the reachability analysis algorithm, unreachable objects are not "must die". At this time, they are temporarily in the "Probation" stage. To really declare an object dead, at least they have to go through the process of re labeling.
The premise of marking is that the object finds no reference chain connected to GC Roots after accessibility analysis.
1. Mark and screen for the first time.
The filter is based on whether it is necessary for this object to execute the finalize() method.
When the object does not override the finalize method, the object will be recycled directly.
2. second marking
If this object overrides the finalize method, which is the last chance for the object to escape the fate of death. If the object wants to successfully save itself in finalize(), it can only re establish an association with any object in the reference chain. For example, if it assigns its value to a class variable or a member variable of the object, it will remove the collection of "to be recycled" at the second marking. If the object has not escaped at this time, it is basically recycled.
Note: the finalize() method of an object will only be executed once, that is, there is only one chance to save yourself by calling the finalize method.
Example code:
public class OOMTest { public static void main(String[] args) { List<Object> list = new ArrayList<>(); int i = 0; int j = 0; while (true) { list.add(new User(i++, UUID.randomUUID().toString())); new User(j--, UUID.randomUUID().toString()); } } } //The User class needs to override the finalize method @Override protected void finalize() throws Throwable { OOMTest.list.add(this); System.out.println("Close the resource, userid=" + id + "About to be recycled"); }
The finalize() method is expensive and uncertain to run, and cannot guarantee the call order of each object. Now it has been officially declared as a syntax that is not recommended. Some materials describe that it is suitable for cleaning up such as "closing external resources", which is completely a self consolation for the purpose of the finalize() method. All the work that finalize() can do can be done better and more timely by using try finally or other methods, so it is suggested that you can completely forget this method in the Java language.
How to judge whether a class is useless
The method area mainly recycles useless classes, so how to judge whether a class is useless?
A class can be considered a "useless class" only if it meets the following three conditions at the same time:
- All object instances of this class have been recycled, that is, there are no instances of this class in the Java heap.
- The ClassLoader that loaded this class has been recycled.
- The corresponding Java Lang. class object is not referenced anywhere, and the method of this class cannot be accessed anywhere through reflection.