If you want a more comprehensive understanding of the underlying principles of the JVM, you can read Zhou Zhiming’s in-depth Understanding of the Java Virtual Machine — Advanced JVM Features and Best Practices (2nd edition).
First, JVM memory structure
The Memory space of the Java VIRTUAL machine is divided into five parts:
- Program counter
- Java virtual machine stack
- Local method stack
- The heap
- Methods area
The biggest difference between JDK 1.8 and JDK 1.7 is that metadata sections replace permanent generations. Similar in nature to permanent generations, meta-spaces are implementations of method areas in the JVM specification. However, the biggest difference between a meta-space and a persistent generation is that the metadata space is not in the virtual machine, but uses local memory.
1. Program counter (PC register)
Definition of a program counter
A program counter is a small memory space that is the address of the bytecode instruction being executed by the current thread. If the current thread is executing a local method, the program counter is Undefined.
Function of program counter
- The bytecode interpreter realizes the flow control of the code by changing the program counter to read the instructions in turn.
- In the case of multithreading, the program counter keeps track of where the current thread executed, so that when the thread switches back, it knows where it last executed.
Program counter features
- It’s a small memory space.
- Threads are private; each thread has its own program counter.
- Life cycle: Created as a thread is created and destroyed as a thread terminates.
- Is the only one who won’t show up
OutOfMemoryError
Memory area of.
2. Java Virtual Machine stack (Java stack)
Definition of the Java virtual machine stack
The Java virtual machine stack is an in-memory model that describes how Java methods run.
The Java virtual machine stack creates an area called stack frames for each Java method that is about to run. This area is used to store information about the method as it runs, such as:
- Local variable scale
- The operand stack
- Dynamic link
- Method exit information
- .
The process of pushing and unloading
When a local variable needs to be created during a method run, the value of the local variable is stored in the local variable table in the stack frame.
The stack frame at the top of the Java virtual machine stack is the currently executing active stack, that is, the currently executing method, and the PC register points to this address. Only the local variables of the active stack frame can be used by the operand stack. When another method is called in this stack frame, the corresponding stack frame is created again. The newly created stack frame is pushed to the top of the stack and becomes the current active stack frame.
After the method completes, the current stack frame is removed and the return value of the stack frame becomes an operand of the operand stack in the new active stack frame. If no value is returned, the operand of the operand stack in the new active stack frame does not change.
Because the Java virtual machine stack is thread-specific and data is not shared by threads, data consistency issues are not a concern and synchronization locks are not an issue.
Java virtual machine stack characteristics
- The local variable table is created with the stack frame, its size is determined at compile time, and it only needs to be allocated a predetermined size when it is created. During the operation of the method, the size of the local variable scale does not change.
- There are two types of exceptions to the Java virtual machine stack: StackOverFlowError and OutOfMemoryError.
- StackOverFlowError If the size of the Java virtual machine stack does not allow dynamic scaling, a StackOverFlowError exception is raised when the thread request stack depth exceeds the maximum depth of the current Java virtual machine stack.
- OutOfMemoryError If dynamic scaling is allowed, an OutOfMemoryError is thrown when the thread is running out of memory when it requests the stack and can no longer expand dynamically.
- The Java virtual machine stack is also thread-private, created as a thread is created and destroyed as a thread terminates.
When StackOverFlowError occurs, there can be a lot more memory.
3. Local method stack (C stack)
Definition of the local method stack
The Native method stack is the space reserved for the JVM to run Native methods. Since many Native methods are implemented in C, it is often called the C stack. It is similar to what the Java virtual machine stack implements, except that the local method stack is an in-memory model that describes how the local method runs.
Stack frame change process
When a local method is executed, a stack frame is also created on the local method stack to store the local variator, operand stack, dynamic linkage, method exit information, and so on.
After the method completes, the corresponding stack frame is removed from the stack, freeing up memory. StackOverFlowError and OutOfMemoryError exceptions are also thrown.
If the Java VIRTUAL machine does not support Native methods or does not rely on a traditional stack itself, it may not provide a Native method stack. If a local method stack is supported, this stack is typically allocated by thread at thread creation time.
4. The heap
The definition of the heap
The heap is the memory space used to hold objects, and almost all objects are stored in the heap.
The characteristics of the pile
- Threads share, the entire Java VIRTUAL machine has only one heap, and all threads access the same heap. The program counter, Java virtual machine stack, and local method stack are all one thread at a time.
- Created when the VM starts.
- It is a major recycling site.
- It can be further divided into: Cenozoic (Eden region From Survior To Survivor) and old age.
Different regions hold objects with different lifecycles, so different garbage collection algorithms can be used for different regions to be more targeted.
The size of the heap can be fixed or expanded, but for mainstream virtual machines the size of the heap is extensible, so OutOfMemoryError is thrown when the thread requests memory allocation but the heap is full and can no longer be expanded.
The memory used by the Java heap does not need to be contiguous. Since the heap is shared by all threads, access to it needs to be synchronized, and methods and properties need to be consistent.
5. Methods area
Method area definition
The method area defined in the Java Virtual Machine specification is a logical part of the heap. The method area stores the following information:
- Information about classes that have been loaded by the VM
- constant
- A static variable
- Just-in-time compiler compiled code
Characteristics of method area
- Thread sharing. The method area is a logical part of the heap and, like the heap, is shared by threads. There is only one method area in the entire virtual machine.
- The permanent generation. The information in the method area generally needs to exist for a long time, and it is the logical partition of the heap, so the method area is called “permanent generation” by the partition method of the heap.
- The memory reclamation efficiency is low. The information in the method area is usually long-lasting, and only a small amount of information may be invalid after recycling. The main collection objectives are: constant pool collection; Unload the type.
- The Java Virtual Machine specification is lax about method areas. Like the heap, it allows fixed size, dynamic scaling, and no garbage collection.
Run-time constant pool
The method area holds: class information, constants, static variables, and just-in-time compiler compiled code. Constants are stored in the runtime constant pool.
When a class is loaded by the Java Virtual machine, constants in the.class file are stored in the runtime constant pool of the methods area. And at run time, new constants can be added to the constant pool. The intern() method of the String class, for example, adds String constants to the constant pool at run time.
6. Direct memory (off-heap memory)
Direct memory is memory other than the Java virtual machine, but may also be used by Java.
Operating direct memory
NIO introduced a channel – and buffer-based IO approach. It can directly allocate memory outside of the Java VIRTUAL machine by calling local methods, and then manipulate that memory directly through a DirectByteBuffer object stored in the heap, rather than copying data from external memory to the heap, thus improving the efficiency of data manipulation.
The size of direct memory is not controlled by the Java virtual machine, but since it is memory, OutOfMemoryError is thrown when there is insufficient memory.
Direct memory compared to heap memory
- Direct memory requisition space costs higher performance
- Direct memory reads IO better than ordinary heap memory.
- Direct memory function chain: local IO -> Direct memory -> Local IO
- Heap memory chain: local IO -> Direct memory -> Indirect memory -> Direct memory -> local IO
When configuring VM parameters, the server administrator sets parameters such as -xmx based on the actual memory. However, the direct memory is often ignored. As a result, the sum of memory regions is greater than the physical memory limit, and an OutOfMemoryError occurs during dynamic expansion.
HotSpot VIRTUAL machine object exploration
1. Memory layout of the object
In the HotSpot VIRTUAL machine, the memory layout of objects is divided into three areas:
- Object Header
- Instance Data
- Align Padding
Object head
The object header records some of the data that the object needs to use during execution:
- Hash code
- GC generational age
- Lock status flag
- The lock held by the thread
- Biased thread ID
- Bias timestamp
The object header may contain a type pointer that determines which class the object belongs to. If the object is an array, the object header also includes the array length.
The instance data
The instance data part is the value of the member variables, including the parent and parent member variables.
Alignment filling
Used to ensure that the total length of the object is an integer multiple of 8 bytes.
HotSpot VM’s automatic memory management system requires that the object’s size be an integer multiple of 8 bytes. The object header is exactly a multiple (1 or 2) of 8 bytes, so when the object instance data part is not aligned, it needs to be filled by alignment.
Alignment padding does not necessarily exist and has no special meaning. It simply serves as a placeholder.
2. Object creation process
Class loading check
When a virtual machine encounters a new instruction while parsing a.class file, it first checks to see if there is a symbolic reference to the class in the constant pool, and to see if the class represented by the symbolic reference has been loaded, parsed, and initialized. If not, the corresponding class loading process must be performed first.
Allocate memory for newborn objects
The size of the memory required by the object is fully determined after the class is loaded, and a corresponding chunk of memory is allocated from the heap for the new object. There are two ways to allocate memory in the heap:
-
Pointer to the collision If the Java heap memory is absolutely neat specification USES is “replication algorithm” (or “mark finishing method”), the free memory and has been using memory with a pointer as a cut-off point indicator, so only when allocating memory need to free memory pointer move a paragraph of distance with the object of the same size, this distribution is called a pointer “bumped”.
-
Free list If the Memory in the Java heap is disorganized and used memory is interlaced with free memory (indicating mark-purge, fragmentation), then pointer collisions cannot simply be performed, and the VM must maintain a list of which memory blocks are free and available. Find a chunk of memory from the free list that is large enough to allocate to the object instance. This approach is called a “free list.”
Initialize the
After allocating the memory, assign initial values to the member variables in the object, set the object header information, and call the constructor method of the object for initialization.
At this point, the entire object creation process is complete.
3. Access mode of the object
Storage for all objects is allocated in the heap, but references to this object are allocated in the stack. That is, when an object is created, memory is allocated in both places, the memory allocated in the heap actually creates the object, and the memory allocated in the stack is just a pointer (reference) to the heap object. Objects can be accessed differently depending on the type of address the reference is stored in.
Handle access mode
The heap needs to have a memory area called the “handle pool”, which contains the specific address information of the object instance data and the type data.
A variable of a reference type holds the handle address of the object. When accessing an object, you first need to find the handle to the object by referring to the variable of the type, and then find the object based on the address of the object in the handle.
Direct pointer access
Variables of the reference type store the address of the object directly, thereby eliminating the need for a handle pool and providing direct access to the object through reference. However, the memory space where the object resides requires an additional policy to store the address of the class information to which the object belongs.
It should be noted that HotSpot uses the second approach, the direct pointer approach to accessing objects, which requires only one addressing operation and is twice as fast as the handle approach. But as mentioned above, it requires additional policies to store the address of the object’s class information in the method area.
Garbage collection strategy and algorithm
Program counters, virtual machine stacks, and local method stacks live and die with the thread; The stack frame goes on the stack at the beginning of the method and goes off the stack at the end of the method. Memory allocation and collection in these areas are deterministic, and there is no need to worry too much about collection because when a method terminates or a thread terminates, memory will follow.
In the case of the Java heap and method areas, we don’t know what objects will be created until the program is running, and it is this part of memory that is allocated and reclaimed dynamically and that the garbage collector focuses on.
1. Determine whether the object is alive
If an object is not referenced by any object or variable, it is an invalid object and needs to be reclaimed.
Reference counting method
Maintains a counter counter in the object header that is +1 if the object is referenced once; Counter -1 if reference is invalid. When the counter is 0, the object is considered invalid.
Reference counting algorithm is easy to implement and efficient to judge, so it is a good algorithm in most cases. However, reference counting algorithms are not used in mainstream Java virtual machines to manage memory, mainly because it is difficult to solve the problem of circular references between objects.
For example, 👉 objects objA and objB both have fields instance. Let obja. instance = objB and objb. instance = objA. Since they reference each other, their reference counts are not zero. The reference counting algorithm then fails to tell the GC collector to reclaim them.
Accessibility analysis
All objects that are directly or indirectly associated with GC Roots are valid objects, and objects that are not associated with GC Roots are invalid objects.
GC Roots refers to:
- Objects referenced in the Java virtual machine stack (the local variable table in the stack frame)
- Objects referenced in the local method stack
- The object referenced by the constant in the method area
- The object referenced by the class static property in the method area
GC Roots do not include objects referenced by objects in the heap, so there is no problem with circular references.
2. Type of reference
Determining whether an object is alive or not is related to reference. Prior to JDK 1.2, reference definitions in Java were traditional. An object could only be referred to or not referred to. We wanted to describe objects that stayed in memory when there was enough space; If memory space is too tight after garbage collection, these objects can be discarded. Many system cache functions fit this application scenario.
After JDK 1.2, Java expanded the concept of references into the following four categories. Different reference types mainly reflect the influence of the object’s reachable state and garbage collection.
Strong Reference
References such as “Object obj = new Object()” are strong references, and as long as strong references exist, the garbage collector will never reclaim the referenced Object. However, if we mistakenly maintain a strong reference, such as assigning a value to a static variable, the object will not be recycled for a long time, resulting in a memory leak.
Soft Reference
A soft reference is a weaker reference to a strong reference that exempts an object from some garbage collection and only attempts to reclaim the object it points to if the JVM decides it is out of memory. The JVM ensures that the object to which the soft reference points is cleaned up before an OutOfMemoryError is thrown. Soft references are often used to implement memory-sensitive caches, so that if there is free memory, the cache can be retained temporarily and cleaned up when it is low, ensuring that the cache is used without running out of memory.
Weak Reference
Weak references are weaker than soft references. When a JVM does garbage collection, objects associated only with weak references are reclaimed, regardless of whether memory is sufficient.
Phantom Reference
Virtual reference, also known as ghost reference or phantom reference, is the weakest kind of reference relationship. Whether an object has a virtual reference or not has absolutely no effect on its lifetime. It simply provides a mechanism to ensure that objects do something after being finalize, for example, it is usually used to do what is called post-mortem cleanup.
3. Reclaim invalid objects from the heap
It is not impossible for unreachable objects to survive in reachability analysis.
Determines whether finalize() is necessary
The JVM determines whether it is necessary for this object to execute finalize(), and if the object does not override Finalize (), or finalize() has already been called by the virtual machine, then it is considered “not necessary to execute”. Then the object is basically really recycled.
If the object is judged to be necessary to execute finalize() methods, then the object will be put into an F-queue, and the virtual machine will execute those Finalize () methods with lower priority, but it will not ensure that all Finalize () methods will be executed. If the Finalize () method has a time-consuming operation, the VIRTUAL machine will stop pointing to the Finalize () method and clear the object.
The object regenerates or dies
If this is assigned to a reference when the Finalize () method is executed, the object is reborn. If not, it is cleared by the garbage collector.
The finalize() method of any object will be called automatically by the system only once. If the object faces the next collection, its Finalize () method will not be executed again, and it will be invalid to continue to save itself in Finalize ().
4. Reclaim method area memory
The method area stores long-lived class information, constants, static variables, and only a small amount of garbage is removed per garbage collection. Two types of garbage are mainly removed in the method area:
- Abandoned constants
- Useless class
Decision abandonment constant
Constants in the constant pool are purged as long as they are not referenced by any variable or object. For example, a String “bingo” is added to the constant pool, but there is no String object in the system that references the “bingo” constant in the constant pool, and there is no other place that references the literal. If necessary, the “bingo” constant will be cleaned out of the constant pool.
Determine useless classes
To determine whether a class is a “useless class”, the conditions are more stringent.
- All objects of this class have been cleared
- The ClassLoader that loaded the class has been reclaimed
- The java.lang.Class object of this Class is not referenced anywhere, and the methods of this Class cannot be accessed anywhere through reflection.
When a Class is loaded into the method area by the virtual machine, there is an object in the heap that represents that Class: java.lang.class. This object is created when the class is loaded into the method area and cleared when the method area class is deleted.
Garbage collection algorithms
Once you’ve learned how to identify invalid objects, useless classes, and discarded constants, the rest of the job is to recycle them. Common garbage collection algorithms are the following:
Mark-clear algorithm
The process of marking is to traverse all GC Roots, and then mark all objects reachable by GC Roots as living objects.
The cleanup process traverses all objects in the heap, removing all unmarked objects. At the same time, the tagged objects are cleared for the next garbage collection.
This approach has two drawbacks:
- Efficiency problem: Both marking and cleaning processes are inefficient.
- Space issues: A large number of discrete memory fragments are generated after token clearing, so that later, when larger objects need to be allocated, enough contiguous memory cannot be found and another garbage collection action has to be triggered early.
Replication algorithm (New generation)
To solve the efficiency problem, “copy” collection algorithms emerged. It divides the available memory by capacity into two equally sized pieces, one of which is used at a time. When this block of memory is used up and garbage collection is required, the surviving objects are copied onto the other block, and the first block of memory is cleared entirely. There are pros and cons to this algorithm:
- Advantages: There is no memory fragmentation problem.
- Cons: Memory shrinks to half of its original size, wasting space.
To solve the space utilization problem, the memory can be divided into three blocks: Eden, From Survivor, and To Survivor, with a ratio of 8:1:1. Eden and Survivor can be used each time. At the time of reclamation, the surviving objects in Eden and Survivor are copied to another Survivor space at a time, and Eden and the Survivor space that was just used are cleaned up. Only 10% of memory is wasted.
However, we cannot guarantee that no more than 10% of the objects will survive each collection. When Survivor space is insufficient, we need to rely on other memory (referring to the old age) for allocation guarantee.
Distribution of guarantee
When allocating memory space for an object, MinorGC is triggered for garbage collection if the free area in Eden+Survivor does not fit the object. However, if more than 10% of the objects are still alive after the Minor GC, the surviving objects will be moved directly to the old age through the allocation guarantee mechanism, and the new objects will be stored in the Eden region.
Mark-tidy Algorithm (old age)
Marking: The first stage is identical to the marking/clearing algorithm, which traverses GC Roots and marks the surviving objects.
Defragment: Move all surviving objects in memory address order, and then reclaim all memory beyond the end memory address. Therefore, the second stage is called the finishing stage.
This is an old garbage collection algorithm. Old objects generally have a long life, so a large number of objects will survive each garbage collection. If the replication algorithm is adopted, a large number of surviving objects need to be copied each time, which is very low efficiency.
Generational collection algorithm
The memory is divided into several blocks according to the object lifetime. Generally, the Java heap is divided into new generation and old generation, and the most appropriate collection algorithm is adopted according to the characteristics of each generation.
- New generation: Replication algorithms
- The old days: mark-clean algorithm, mark-tidy algorithm
4. HotSpot garbage collector
The HotSpot VIRTUAL machine provides a variety of garbage collectors, each with its own characteristics, and while we are comparing them, we are not trying to pick the best collector. We have chosen only the collector that is most appropriate for our specific application.
1. New generation garbage collector
Serial garbage collector (single thread)
Start only one GC thread for garbage collection and Stop all user threads during garbage collection.
The average client application requires less memory, does not create many objects, and does not have much heap memory, so the garbage collector takes a short time to collect, even if it stops all user threads during this time, it will not feel significantly stuck. Serial garbage collector is therefore suitable for use by clients.
Because the Serial collector uses only one GC thread, it avoids the overhead of thread switching, making it simple and efficient.
ParNew garbage collector (multithreaded)
ParNew is the multithreaded version of Serial. Garbage cleaning is done in parallel by multiple GC threads. But The clean-up process still needs to Stop The World.
ParNew pursues “low pause times”, the only difference from Serial is the use of multi-threading garbage collection, in multi-CPU environment performance can be somewhat improved than Serial. However, thread switching requires additional overhead and therefore does not perform as well as Serial in a single-CPU environment.
Insane. Parallel Insane.
The Parallel Insane, like ParNew, is a multi-threaded, new generation garbage collector. But there are big differences:
- Insane: Chase CPU throughput, be able to perform specified tasks in a relatively short time, and therefore be suitable for background computing without interaction.
- ParNew: Seeks to reduce user pause time, suitable for interactive applications.
Throughput = time to run user code/(Time to run user code + garbage collection time)
The pursuit of high throughput can be achieved by reducing the amount of time the GC spends performing the actual work, however, running GC only occasionally means that there is a lot of work to be done each time the GC runs because of the high number of objects that accumulate in the heap during that time. Individual GC takes more time to complete, resulting in higher pause times. Given the low pause times, it is best to run GC frequently to complete more quickly, which in turn leads to throughput degradation.
- Run -xx :GCTimeRadio to set the percentage of the garbage collection time to the total CPU time.
- Set the maximum garbage processing pause time with the -xx :MaxGCPauseMillis parameter.
- Run the -xx :+UseAdaptiveSizePolicy command to enable the adaptive policy. Once we set the heap size and MaxGCPauseMillis or GCTimeRadio, the collector automatically adjusts the size of the new generation, the ratio of Eden to Survivor, and the age at which the object enters the old age. As close as possible to the MaxGCPauseMillis or GCTimeRadio we set.
2. Old age garbage collector
Serial Old garbage collector
Serial Old collectors are older versions of Serial, both single-threaded collectors that enable only one GC thread and are suitable for client applications. The only difference is that Serial Old works in the Old days and uses a mark-tidy algorithm. Serial works in the new generation, using a “copy” algorithm.
Parallel Old garbage collector (Multithreaded)
The Parallel Old collector is an older version of the Parallel Insane, which seeks CPU throughput.
CMS garbage collector
The CMS(Concurrent Mark Sweep) collector is a collector whose goal is to achieve the shortest collection pause time (pursuit of low pauses). It allows the user thread and the GC thread to execute concurrently during garbage collection, so that the user does not feel a significant lag during garbage collection.
- Initial tag: Stop The World, marking all objects directly associated with GC Roots using only one initial tag thread.
- Concurrent tagging: Multiple tagging threads are used to execute concurrently with the user thread. This process performs reachability analysis and marks all abandoned objects. It’s slow.
- Re-mark: Stop The World, execute concurrently with multiple marking threads, and mark The newly discarded objects that just appeared during The concurrent marking process.
- Concurrent cleanup: Only one GC thread is used, executed concurrently with the user thread, to clean the object just marked. This process is very time consuming.
Concurrent tagging and concurrent cleanup processes take the longest time and can work with user threads, so overall, the CMS collector’s memory reclamation process is performed concurrently with the user thread.
Disadvantages of CMS:
- Low throughput
- Unable to handle floating garbage, resulting in frequent Full GC
- Create debris space using a mark-clean algorithm
For generating the fragments of the space problem, can through the open – XX: + UseCMSCompactAtFullCollection, in each Full GC after the completion of a memory compression, will be scattered everywhere, to a piece of object. Set parameters – XX: CMSFullGCsBeforeCompaction tell CMS, after N times again after Full GC memory consolidation.
3. G1 Generic garbage collector
G1 is a garbage collector for server-side applications that doesn’t have a concept of generation and generation, but instead divides the heap into separate regions. To collect garbage, estimate the amount of garbage in each Region and collect garbage from the Region with the highest garbage collection value. Therefore, the garbage collection efficiency is maximized.
As a whole, G1 is a collector based on a mark-tidy algorithm, and locally (between two regions) based on a copy algorithm, meaning that no memory space fragmentation occurs during runtime.
Here’s a question 👇 :
An object may not be in the same Region as the object referenced within it. When garbage collection occurs, does the entire heap memory need to be scanned for a complete reachability analysis?
Don’t! Each Region has a Remembered Set that records the Region of objects referenced by all objects in the Region. Adding Remembered Set to GC Roots prevents traversal of the entire heap during reachabability analysis.
If the operation to maintain the Remembered Set is not counted, the G1 collector works in the following steps:
- Initial tag: Stop The World, marking all objects directly associated with GC Roots using only one initial tag thread.
- Concurrent tagging: Execute concurrently with the user thread using a tagging thread. This process is very slow for accessibility analysis.
- Final tag: Stop The World, executed concurrently using multiple tag threads.
- Filter collection: Recycle discarded objects, also Stop The World, and use multiple filter collection threads to execute concurrently.
5. Memory allocation and reclamation strategy
Memory allocation of objects, is allocated on the heap (may also be separated after the JIT compiler for scalar type and indirect allocated on the stack), object mainly distribute on the new generation of Eden area, a few cases may be directly allocated in old age, distribution rules are not fixed, depends on the current configuration and related parameters using a combination of the garbage collector.
Here are some of the most common memory allocation rules for you to learn.
1. Objects are allocated in Eden preferentially
In most cases, objects are allocated in the Eden region of the new generation. When the Eden area does not have enough space to allocate, the virtual machine will initiate a Minor GC.
👇Minor GC vs Major GC/Full GC:
- Minor GC: Collect the new generation (including Eden and Survivor regions). Because Java objects are mostly ephemeral, Minor GC is very frequent and generally fast.
- In the old days, there were Major GC’s, often accompanied by at least one Minor GC, but not always. Major GC is typically 10 times slower than Minor GC.
There is no formal definition of either Major or Full GC in the JVM specification, so it is simply assumed that the Major GC cleans up old ages and the Full GC cleans up the entire heap.
2. Big objects go straight to the old age
Large objects are Java objects that require a large amount of contiguous memory space, such as very long strings or data.
The probability that a large object can be stored in Eden area is relatively small, and the probability of allocation guarantee is relatively high. However, allocation guarantee involves a large amount of replication, resulting in low efficiency.
Virtual machine provides a – XX: PretenureSizeThreshold parameters, make more than the set value of object directly in old age distribution, the aim is to avoid happen between Eden area and two Survivor area a lot of memory copy. (Remember, the new generation uses copying algorithms to recycle garbage?)
3. Long-lived objects will enter the old age
The JVM defines an object age counter for each object. After a Minor GC occurs in the Cenozoic, the age of the surviving objects is +1, and when the age exceeds a certain value, all objects beyond that value are moved to the old age.
Use -xxMaxTenuringThreshold to set the maximum age of the new generation. Any object that exceeds this threshold will be moved to the old age.
4. Dynamic object age determination
If the total size of all objects of the same age in Survivor of the current generation is greater than half of Survivor space, objects of age >= this age can enter the old age directly without waiting until the age specified in MaxTenuringThreshold.
5. Space allocation guarantee
Before JDK 6 Update 24, the virtual machine checks to see if the maximum available contiguous space of the old generation is greater than the total space of all objects of the new generation. If this condition is true, the Minor GC is guaranteed to be safe. If not, the virtual machine checks to see if HandlePromotionFailure is set to allow guarantee failure. If so, the virtual machine continues to check if the maximum contiguous space available in the old age is greater than the average size of objects promoted to the old age over time. If so, A Minor GC will be attempted, although this Minor GC is risky; If less than, or if the HandlePromotionFailure setting does not allow risk, then do a Full GC instead.
After JDK 6 Update 24 the rule changes to: Minor GC is performed whenever the contiguous space of the old generation is greater than the total size of the new generation or the average size of the successive promotions, otherwise Full GC is performed.
By removing discarded data from the old to expand the free space of the old, so as to guarantee the new generation.
The process is to assign the guarantee.
👇 summarizes the situations that might trigger a JVM to do Full GC:
-
This method is called to advise the JVM to do Full GC. Note that this is a recommendation rather than a rule, but in many cases it will trigger Full GC, increasing the frequency of Full GC. Normally we just need to let the virtual machine manage the memory itself. We can disable system.gc () by using -xx :+ DisableExplicitGC.
-
There is insufficient space on the old s Old s space will trigger a Full GC, if after the operation space is still insufficient, will throw an error: Java. Lang. OutOfMemoryError: Java heap space
-
Permanet Generation The method area of the runtime data area in the JVM specification, also known as Permanet Generation in the HotSpot VIRTUAL machine, stores class information, constants, static variables, etc. When the system has a large number of classes to load, reflected classes, and called methods. The permanent generation may be Full, triggering the Full GC. If after the Full GC can’t recycle, then the JVM throws an error message: Java. Lang. OutOfMemoryError: PermGen space
-
Promotion failed and Concurrent mode failure promotion failed occur during CMS GC, which is the guarantee failure mentioned above. Concurrent mode failure is the result of running out of space in the old age when an object needs to be put into the old age during the CMS GC.
-
The average size of the Minor GC promoted to the old generation is larger than the remaining space of the old generation
JVM performance tuning
There are two main ways to deploy applications on high-performance hardware:
- Using large memory with a 64-bit JDK;
- Use several 32-bit virtual machines to create a logical cluster to leverage hardware resources.
1. Use 64-bit JDK to manage large memory
With larger heap memory, the frequency of garbage collection is reduced, but each garbage collection takes longer. If the heap memory is 14 GB, each Full GC will take tens of seconds. Full GC can be unbearable for a site if it happens frequently.
For systems with high user interactivity and pause time sensitivity, you can assign a Java VIRTUAL machine to a very large heap if you can be sure that your application’s Full GC frequency is low enough, or at least low enough not to affect user usage.
Possible problems:
- Long pauses due to memory reclamation;
- At this stage, 64-bit JDK performance is generally lower than 32-bit JDK;
- The application needs to be stable because heap overflows make it almost impossible to take snapshots of heap dumps (because more than 10GB of Dump files are generated), and even if snapshots are generated, they are almost impossible to analyze.
- The same program typically consumes more memory in the 64-bit JDK than in the 32-bit JDK due to pointer bloat, data type alignment padding, and other factors.
2. Build logical clusters using 32-bit JVMS
Start multiple application server processes on a physical machine, allocate different ports to each process, and then set up a front-end load balancer to distribute access requests in the form of reverse proxy.
Considering the logic cluster is built on a physical machine just for the purpose of the use of hardware resources as much as possible, do not need to be concerned with state reserves, high availability, such as thermal transfer performance requirements, also do not need to ensure that each virtual machine process has absolute balanced load, so use stateless Session replication of jewels cluster is a good choice. We only need to ensure that the cluster has affinity, that is, the equalizer will always allocate a fixed user request to a fixed cluster node for processing according to a certain rule algorithm (generally according to the SessionID allocation).
Possible problems:
- Avoid node contention for global resources. For example, if each node accesses a disk file at the same time, I/O exceptions may occur.
- It is difficult to make efficient use of resource pools. For example, connection pools are usually created independently on nodes. As a result, some node pools may be full while other nodes are still free.
- Each node is limited by 32 bits of memory;
- Applications that use a lot of local caches can cause a lot of memory waste in a logical cluster, because each logical node has a cache, so consider changing the local cache to a centralized cache.
3. Optimize case analysis and actual combat
Scene description
A small system, using 32-bit JDK, 4G memory, testing found that the server periodically threw memory overflow exceptions. To join – XX: + HeapDumpOnOutOfMemoryError (after adding the parameter, when the heap memory leak will output abnormal log), but when happening again out of memory, no generation related to abnormal log.
Analysis of the
On the 32-bit JDK, 1.6GB is allocated to the heap, and some is allocated to the JVM’s other memory. Direct memory can only be allocated up to 0.4GB of remaining memory. If NIO is used, the JVM allocates memory outside of JVM memory. Watch out for out-of-memory exceptions when “direct memory” is low.
Direct memory reclamation process
Direct memory is not JVM memory space, but its garbage collection is also the responsibility of the JVM.
While garbage collection is in progress, the virtual machine collects garbage from the direct memory. However, the direct memory can not notify the collector of garbage collection when it is running out of space like the new generation and the old generation. It can only wait until the old generation is Full and then “by the way” clean up the garbage from the memory. Otherwise, you have to wait until an out-of-memory exception is thrown, catch first, and then yell “system.gc ()” in the catch block. If the virtual machine still doesn’t listen, there is still a lot of free memory in the heap and it has to throw an overflow exception.
Class file structure
1. “Irrelevance” of the JVM
When it comes to JVM irrelevance, there are two main things:
- Platform independence: Java code can run on any operating system
- Language independence: The JVM can run code other than Java
The Java source code first needs to be compiled into a.class file using the Javac compiler, which is then executed by the JVM to start the program running.
The JVM only knows about.class files. It doesn’t care which language produced them, as long as they conform to the JVM’s specifications. There are already JRuby, Jython, Scala and other languages that can run on the JVM. They have their own syntax rules, but their compilers can compile their own source code into jVM-compliant.class files that can run them with the JVM.
The semantics of variables, keywords, and operation symbols in the Java language are ultimately combined by multiple bytecode commands, so the semantic description provided by bytecode commands is certainly more powerful than the Java language itself. Therefore, just because there are language features that the Java language itself does not support effectively does not mean that bytecodes themselves do not support effectively.
2. Class file structure
A Class file is a binary file with a strict specification of its contents. There are no Spaces in the file, all successive 0/1’s. Everything in the Class file is divided into two types: unsigned numbers and tables.
- Unsigned numbers Unsigned numbers represent values in a Class file that do not have any type but have different lengths. U1, U2, u4, and u8 represent unsigned numbers of 1/2/4/8 bytes, respectively.
- Table a conforming data type consisting of unsigned numbers or other tables as data items.
The Class file consists of the following:
- The magic number
- Version information
- Constant pool
- Access tokens
- Class index, superclass index, interface index collection
- Set of field tables
- Method table collection
- Property sheet collection
The magic number
The first four bytes of a Class file are called magic numbers and are used to indicate the type of the Class file.
The magic number in the Class file is “CAFE BABE” in hexadecimal notation. Isn’t that romantic?
Magic numbers are equivalent to file suffixes, but suffixes can be easily modified and are not safe, so it is appropriate to identify the file type in a Class file.
Version information
The next four bytes of the magic number are version information, 5-6 bytes are minor version numbers, and 7-8 bytes are major version numbers, which indicate which version of the JDK is being used in the current Class file.
Older JDK versions are backward compatible with older Class files, but cannot run later Class files, and the virtual machine must refuse to execute Class files older than its version number, even if the file format has not changed at all.
Constant pool
After the version information comes the constant pool, which holds two types of constants:
-
Literal constants Literal constants are strings that we define in our program, values that are final.
-
Symbolic references symbolic references are the various names we define: fully qualified names of classes and interfaces, field names and descriptors, method names and descriptors.
Constant pool characteristics
- The number of constants in the constant pool is not fixed, so an unsigned number of type U2 is placed at the beginning of the constant pool to store the current capacity of the constant pool.
- Each constant entry in the constant pool is a table that begins with a tag bit of type U1, which represents the type of the constant.
A constant type in a constant pool
type | tag | describe |
---|---|---|
CONSTANT_utf8_info | 1 | The character string is utF-8 encoded |
CONSTANT_Integer_info | 3 | Integer literals |
CONSTANT_Float_info | 4 | Floating point literals |
CONSTANT_Long_info | 5 | Long integer literals |
CONSTANT_Double_info | 6 | A double – precision floating-point literal |
CONSTANT_Class_info | 7 | Symbolic reference to a class or interface |
CONSTANT_String_info | 8 | String type literals |
CONSTANT_Fieldref_info | 9 | Symbolic reference to a field |
CONSTANT_Methodref_info | 10 | Symbolic references to methods in a class |
CONSTANT_InterfaceMethodref_info | 11 | Symbolic references to methods in the interface |
CONSTANT_NameAndType_info | 12 | Symbolic reference to a field or method |
CONSTANT_MethodHandle_info | 15 | Represents a method handle |
CONSTANT_MethodType_info | 16 | Identify method types |
CONSTANT_InvokeDynamic_info | 18 | Represents a dynamic method call point |
For CONSTANT_Class_info (a constant of this type represents a symbolic reference to a class or interface), its two-dimensional table structure is as follows:
type | The name of the | The number of |
---|---|---|
u1 | tag | 1 |
u2 | name_index | 1 |
The tag is the flag bit used to distinguish between constant types. Name_index is an index value that points to a constant of type CONSTANT_Utf8_info in the constant pool. This constant represents the fully qualified name of the class (or interface). In this case, name_index 0x0002 refers to the second constant in the constant pool.
CONSTANT_Utf8_info constants have the following structure:
type | The name of the | The number of |
---|---|---|
u1 | tag | 1 |
u2 | length | 1 |
u1 | bytes | length |
Tag is the type of the current constant; Length represents the length of the string; Bytes are the contents of the string (encoded in abbreviated UTF8)
Access tokens
The next two bytes after the constant pool end represent the access flag, which identifies some Class or interface level access information, including whether the Class is a Class or an interface; Whether it is defined as public; Is modified by abstract/final.
Class index, superclass index, interface index collection
Both the Class index and the parent index are u2-type data, while the interface index set is a set of U2-type data. These three data are used to determine the Class inheritance relationship in the Class file. The class index is used to determine the fully qualified name of the class, and the superclass index is used to determine the fully qualified name of the class’s parent.
Because Java does not allow multiple inheritance, there is only one parent class index. All Java classes except java.lang.Object have a parent class, so none of the Java classes except java.lang.Object have a parent class index of zero. A class may implement more than one interface, so it is described by a collection of interface indexes. The first item in this collection is u2 data, which represents the capacity of the index table, followed by the name index of the interface.
The class index and the superclass index are represented by two index values of type U2, each pointing to a class descriptor constant of type CONSTANT_Class_info. The total index value of this constant can be used to find the fully qualified name string defined in a constant of type CONSTANT_Utf8_info.
Set of field tables
The field table collection stores the member variables involved in this class, including instance variables and class variables, but not local variables in methods.
Each field table represents only one member variable, and all member variables in this class make up the field table set. The field table structure is as follows:
type | The name of the | The number of | instructions |
---|---|---|---|
u2 | access_flags | 1 | Field access flag, slightly different from class |
u2 | name_index | 1 | Index of field names |
u2 | descriptor_index | 1 | Descriptor that describes the data type of the field. Basic data types are represented by uppercase letters; Object types are represented by the fully qualified name of the L object type. |
u2 | attributes_count | 1 | The length of the property sheet collection |
u2 | attributes | attributes_count | A collection of property tables used to store additional information about a property, such as its value. |
Fields inherited from a parent class (or interface) do not appear in the field table collection, but fields that do not originally exist in Java code may appear, such as fields that point to an external class instance that are automatically added to an internal class in order to maintain access to external classes.
Method table collection
The method table structure is similar to the property table.
Volatile and TRANSIENT keywords do not modify methods, so the ACC_VOLATILE and ACC_TRANSIENT flags are not included in the access flags of the method table.
The property sheet collection of the method table contains a Code property sheet that stores the bytecode instructions compiled by the compiler for the current method.
Property sheet collection
Each attribute corresponds to an attribute table. The structure of the attribute table is as follows:
type | The name of the | The number of |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
Class loading timing
1. Class lifecycle
The entire life cycle of a class, from when it is loaded into virtual machine memory to when it is unloaded, consists of the following seven phases:
- loading
- validation
- To prepare
- parsing
- Initialize the
- use
- uninstall
The three stages of validation, preparation, and parsing are collectively called connection.
The load, validate, prepare, initialize, and unload phases are in a definite order, and the loading process of a class must begin in this order, while the parsing phase may not: It can be started after initialization in some cases in order to support runtime binding in the Java language.
2. When “initialization” begins during class loading
The Java Virtual Machine specification does not enforce rules on when the first phase of the class loading process (i.e., load) begins, but it does have strict rules about the “initialization” phase. Classes must be “initialized” immediately in only five cases:
- When new, putStatic, getstatic, and Invokestatic bytecode instructions are encountered, initialization needs to be triggered if the class is not already initialized.
- When a reflection call is made to a class, initialization needs to be triggered if the class is not already initialized.
- When initializing a class, if the parent class has not been initialized, initialize the parent class first.
- When a vm starts, it needs to specify a main class containing the main() method. The vm initializes the main class first.
- When using JDK 1.7 dynamic language support, if a Java lang. Invoke. The final analytical results for MethodHandle instance REF_getStatic, REF_putStatic, REF_invokeStatic method handles, If the class to which the method handle corresponds has not been initialized, it needs to be initialized first.
The behavior in these five scenarios is called an active reference to a class, and all other ways of referring to a class that do not trigger initialization are called passive references.
3. Passive reference Demo
Demo1
/** * Passively referencing Demo1: * Referencing a static field of the parent class by subclass does not cause subclass initialization. * *@author ylb
*
*/
class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("SubClass init!"); }}public class NotInitialization {
public static void main(String[] args) {
System.out.println(SubClass.value);
// SuperClass init!}}Copy the code
For static fields, only the class that directly defines the field is initialized, so referring to a static field defined in a parent class by its subclass triggers initialization of the parent class but not the subclass.
Demo2
/** * Passively referencing Demo2: * Referencing a class through an array definition does not trigger initialization of the class. * *@author ylb
*
*/
public class NotInitialization {
public static void main(String[] args) {
SuperClass[] superClasses = new SuperClass[10]; }}Copy the code
This code does not trigger the initialization of the parent class, but does trigger the initialization of the “[L full class name” class, which is automatically generated by the virtual machine and inherits directly from java.lang.Object, and the creation action is triggered by the bytecode instruction Newarray.
Demo3
* Constants are stored in the constant pool of the calling class at compile time. They are not directly referenced to the class that defines the constant, so they do not trigger initialization of the class that defines the constant. * *@author ylb
*
*/
class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final String HELLO_BINGO = "Hello Bingo";
}
public class NotInitialization {
public static void main(String[] args) { System.out.println(ConstClass.HELLO_BINGO); }}Copy the code
Once compiled, the constants are stored in the Constant pool of the NotInitialization Class. The NotInitialization Class file has no reference point to a ConstClass, and the two classes are completely disconnected from each other after being translated to Class.
4. Interface loading process
The interface loading process is slightly different from the class loading process.
When a class is initialized, all of its parents are required to be initialized. However, when an interface is initialized, all of its parents are not required to be initialized. When the parent interface is used, it is initialized.
The process of class loading
The class loading process consists of five stages: load, validate, prepare, parse, and initialize.
1. The load
Loading process
“Loading” is a phase of the “class loading” process, and the two terms should not be confused. During the load phase, the virtual machine needs to do three things:
- Gets the binary byte stream of a class by its fully qualified name.
- Transform the static structure represented by the binary byte stream into the runtime data structure of the method area.
- Create a java.lang.Class object in memory that represents this Class and acts as an access point to the various data of this Class in the method area.
Gets a binary byte stream
For Class files, the virtual machine does not specify where or how to get them. In addition to reading directly from a compiled.class file, there are several ways:
- Read from zip packages such as JAR, WAR, etc
- Obtained from the network, for example, Applect
- Generate a binary byte stream of proxy classes through dynamic proxy counting
- The corresponding Class Class is generated from the JSP file
- Read from the database, for example, some middleware servers may choose to install programs into the database to complete the distribution of program code between clusters.
“Non-array class” vs. “Array class” load comparison
- The non-array class loading phase can be done using the system-provided boot class loader or by a user-defined class loader. Developers can define their own class loaders to control how the byte stream is retrieved (for example, overwriting a classloader’s loadClass() method).
- The array class itself is not created by the class loader; it is created directly by the Java virtual machine, and the class loader creates the element classes in the array.
Matters needing attention
- The virtual machine specification does not specify where the Class object is stored. In the case of the HotSpot virtual machine, the Class object is special because it is an object, but it is stored in the method area.
- The load phase intersects with parts of the connect phase, which may have started before the load phase is complete. However, the practice of these two stages still maintains a fixed sequence.
2. Verify
The importance of validation
The verification phase ensures that the information contained in the byte stream of the Class file meets the requirements of the current VIRTUAL machine and does not compromise the security of the virtual machine.
Verification process
- File format verification Verifies that the byte stream complies with the Class file format specification and can be processed by the vm of the current version. The verification points are as follows:
- Does it start with the magic number 0XCAFEBABE
- Check whether the major and minor versions are within the processing range of the current VM
- Whether the constant pool has unsupported constant types
- Whether an index that points to a constant points to a nonexistent constant
- CONSTANT_Utf8_info specifies whether a constant of type CONSTANT_Utf8_info contains data that does not conform to UTF8 encoding
- .
- Metadata validation performs semantic analysis of bytecode description information to ensure that it conforms to Java syntax specifications.
- Bytecode verification is the most complex stage in the verification process. It is to conduct semantic analysis on the method body to ensure that the method will not have events that harm virtual machines when it is running.
- Symbolic reference validation this phase occurs during the parsing phase to ensure that parsing is performed properly.
3. Prepare
The preparation phase is when memory is formally allocated and initial values are set for class variables (or “static member variables”). Memory used by these variables (excluding instance variables) is allocated in the method area.
The initial value is “normally” the zero value of the data type (0, NULL…) , suppose a class variable is defined as:
public static int value = 123;
Copy the code
The value variable will have an initial value of 0 instead of 123 after the preparation phase, because no Java methods have been executed yet.
A “special case” exists: if a class field has a ConstantValue attribute in its field property table, the value will be initialized to the value specified by the ConstantValue attribute in the preparation phase, assuming that the above definition of the class variable value becomes:
public static final int value = 123;
Copy the code
During the preparation phase, the virtual machine assigns value to 123 based on the ConstantValue setting.
4. The parsing
The parsing phase is the process by which the virtual machine replaces symbolic references in the constant pool with direct references.
5. The initialization
The class initialization phase is the final step in the class loading process and is the execution of the class constructor < Clinit >() method.
The < Clinit >() method is generated by combining the assignment action of all class variables in the class that the compiler automatically collects with statements in the static {} block, determined by the order in which the statements appear in the source file.
Only variables defined before the static block can be accessed, and variables defined after it can be assigned in the preceding static block, but cannot be accessed. The following code is shown:
public class Test {
static {
i = 0; // Assigning a value to a variable will compile normally
System.out.println(i); // The compiler will say "illegal forward reference"
}
static int i = 1;
}
Copy the code
The <clinit>() method does not require an explicit call to the parent constructor, and the virtual machine guarantees that the <clinit>() method of the parent class has been executed before the <clinit>() method of the subclass is executed.
Because the <clinit>() method of the parent class executes first, it means that the static statement block defined in the parent class takes precedence over the variable assignment operations of the child class. The following code is shown:
static class Parent {
public static int A = 1;
static {
A = 2; }}static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B); 2 / / output
}
Copy the code
The <clinit>() method is not required, and the compiler may not generate a <clinit>() method for a class that has no static block and no assignment to a class variable.
Static code blocks cannot be used in the interface, but the interface also needs to be explicitly initialized for static member variables defined in the interface via the < Clinit >() method. But unlike classes, an interface’s <clinit>() method does not need to execute the parent’s <clinit>() method first, and the parent interface is initialized only when a variable defined in the parent interface is used.
The virtual machine ensures that a class’s <clinit>() methods are locked and synchronized correctly in a multithreaded environment. If multiple threads initialize a class at the same time, only one thread will execute the class’s <clinit>() method.
Class loaders
Classes and classloaders
Determine whether classes are “equal”
The uniqueness of any class in the Java virtual machine is established by the class loader that loads it and by the class itself. Each class loader has a separate class namespace.
Therefore, comparing two classes to be “equal” only makes sense if they are loaded by the same Class loader. Otherwise, even if the two classes come from the same Class file and are loaded by the same virtual machine, as long as they are loaded by different Class loaders, the two classes must be different.
Equality includes equals(), isInstance(), and the use of the instanceof keyword.
Loader type
The system provides three types of loaders:
- Bootstrap ClassLoader: is responsible for storing
<JAVA_HOME>\lib
In the directory, and can be recognized by the virtual machine (only by the file name, such as rt.jar, does not load the library even if it is placed in the lib directory). - Extension ClassLoader: Takes care of loading
<JAVA_HOME>\lib\ext
For all libraries in the directory, developers can use the extended class loader directly. - Application ClassLoader: Since this ClassLoader is the return value of the getSystemClassLoader() method in ClassLoader, it is also commonly referred to as the “system ClassLoader.” It is responsible for loading the libraries specified on the user’s classpath. Developers can use this class loader directly, and this is generally the default class loader if the application does not have its own custom class loader.
Of course, you can also add your own class loaders if necessary.
2. Parental delegation model
What is the parental delegation model
The parent delegate model describes the hierarchical relationships between class loaders. It requires that all class loaders have their own parent class loaders except for the top level boot class loaders. (Parent-child relationships are typically not implemented as inherited relationships, but rather as combinatorial relationships that duplicate the parent loader’s code.)
The working process of the
If a classloader receives a classload request, it does not try to load the class itself at first. Instead, it delegates the request to the parent classloader. This is true at every level of classloaders, so all load requests should eventually be passed to the top level of the starting classloader. Only when the parent loader reports that it cannot complete the load request (cannot find the required class) will the child loader attempt to load it itself.
This procedure is implemented in the loadClass() method in java.lang.classLoader.
Why use the parent delegate model
Classes like java.lang.Object stored in rT.jar will eventually delegate to the top bootloader, regardless of which class loader is used, so that the same Object class is loaded by different loaders.
On the other hand, instead of using the parent delegate model and having each class loader load it by itself, if the user writes a class called java.lang.Object and places it in the classpath, there will be multiple Object classes. The most fundamental behavior in the Java type system is not guaranteed.