Liver a very hardcore SUMMARY of the JVM foundation, writing is not easy, friends quickly like, forward arrangements!

The original link is said to take the JVM an hour to read

What are the main functions of the JVM?

The JVM, which stands for Java Virtual Machine (Java Virtual Machine), shields information specific to a particular operating system platform, allowing Java programs to run on different platforms only by generating object code (bytecode) that runs on the Java Virtual Machine.

Describe the memory area of Java?

During the execution of a Java program, the JVM divides the memory it manages into several different areas, some of which are thread private and some of which are thread shared. Java memory areas are also called runtime data areas and are divided as follows:

  • The virtual machine stack: The Java virtual machine stack is the data area that is private to the thread. The Java virtual machine stack has the same life cycle as the thread. The virtual machine stack is also the location where local variables are stored. Method creates one in the virtual machine stack during executionThe stack frame (stack frame). Each method performs a process that corresponds to a stack and a stack.

  • Local method stack: The local method stack is also the data area that is private to the thread. The local method stack stores the area that is mainly stored in Java for methods decorated with native keywords.

  • Program counter: The program counter is also the thread’s private data area. This area is used to store the instruction address of the thread, and is used to determine the branch, loop, jump, exception, thread switch, and recovery functions of the thread, all of which are done by the program counter.

  • Method area: A method area is an area of memory shared by each thread that is used to store data such as virtual machine-loaded class information, constants, static variables, and just-in-time compiler compiled code.

  • Heap: THE heap is the data area shared by threads. The heap is the largest storage area in the JVM, and all object instances are allocated to the heap. After JDK 1.7, the string constant pool was removed from the permanent generation and stored in the heap.

    Memory allocation of heap space (default) :

    • The old days: two-thirds of the heap space
    • Younger generation: one-third of the heap space
      • Eden area: 8/10 of the young generation space
      • Survivor 0:1/10 of the young generation space
      • Survivor 1:1/10 of the young generation space

    To view the default JVM parameters, run the following command on the command line.

    java -XX:+PrintFlagsFinal -version
    Copy the code

    The output is quite large, but only two lines reflect the memory allocation results above

  • Runtime Constant Pool: The Runtime Constant Pool, also known as the Runtime Constant Pool, is part of the method area, which has a funny name and is often referred to as non-heap. It does not require that constants be generated only at compile time, meaning that instead of putting constants in the constant pool at compile time, new constants can be put into the constant pool at run time. A typical example is the String intern method.

Describe the class loading mechanism in Java?

The Java virtual machine is responsible for loading the data describing the Class from the Class file to the system memory, verifying, converting, parsing, and initializing the Class data, and finally forming Java types that can be directly used by the VIRTUAL machine. This process is called the Java Class loading mechanism.

From the moment a class is loaded into the virtual machine memory to the moment it is unloaded.

The class loading mechanism consists of five steps: load, link, initialize, use, and unload, in a determined order.

The link stage will be subdivided into three stages, namely, verification, preparation and analysis. The order of these three stages is uncertain, and these three stages are usually carried out interactively. The parsing phase usually starts after initialization, in order to support the Runtime binding feature of the Java language (also known as dynamic binding).

Let’s talk about these processes.

loading

The Java Virtual Machine Specification doesn’t enforce when to start loading this process, so we’re free to do so. Loading is the first stage of the entire class loading process, and in this stage, the Java virtual machine needs to do three things:

  • Gets the binary byte stream that defines a class by its fully qualified name.
  • Convert a storage structure represented by this byte stream into a data structure in the method area of the runtime data area.
  • An in-memory Class object is generated that represents the access point to the data structure.

The Java Virtual Machine Specification does not specify how fully qualified names are obtained, so there are many ways to obtain fully qualified names in the industry:

  • Read from the ZIP package and eventually change to JAR, EAR, WAR format.
  • The Web Applet is the most common application to obtain it from the network.
  • Dynamic generation at run time is the most used dynamic proxy technique.
  • Generated by other files, such as JSP application scenarios, JSP files generate the corresponding Class files.
  • Reading from a database is a smaller scenario.
  • Can be obtained from encrypted files, which is a typical protection against Class files being decompiled.

The loading phase can be completed using either the built-in boot class loader of the VM or a user-defined class loader. Programmers can control the access to byte streams by defining their own class loaders.

Array loading is not created by the class loader, it is allocated directly in memory, but the element types of the array (the array excludes all dimension types) are ultimately loaded by the class loader.

validation

The next step after loading is validation. Since we described in the previous step that a Class object was generated in memory as an entry point to its representative data structure, this step is to ensure that the contents of the byte stream in the Class file comply with the Requirements of the Java Virtual Machine Specification. Ensure that this information does not threaten vm security after it is run as code.

The verification stage is mainly divided into four stages of inspection:

  • File format verification.
  • Metadata validation.
  • Bytecode verification.
  • Symbol reference validation.

File format validation

This phase may include the following verification points:

  • Magic number whether to0xCAFEBABEAt the beginning.
  • Check whether the major and minor versions are acceptable for the current Java VM.
  • Is there any unsupported constant type in the pool constant?
  • Is there any index value that points to a constant that does not exist or that does not conform to a type?
  • CONSTANT_Utf8_info whether there is data in the constant that does not conform to UTF8 encoding.
  • Whether any other information has been deleted or added to parts of the Class file and the file itself.

In fact, there are more verification points than that, and these are just a few excerpts from HotSpot source code.

Metadata validation

This stage is mainly to conduct semantic analysis of the information described by bytecode to ensure that the described information complies with the Java Language Specification. Verification points include

  • Verify that the class has a parent (all classes except the Object class should have a parent).
  • Verify that the parent of a class inherits from a class that is not allowed to inherit.
  • If the class is not abstract, whether the class implements all the methods required in the parent class or interface.
  • Whether final fields are overwritten, whether improper overloading occurs, and so on.

Keep in mind that this stage is just a validation of the Java Language Specification.

Bytecode verification

Bytecode verification is the most complex phase, which is mainly to determine whether the program semantics are legal and logical. In this phase, the method body of the Class (the Code attribute in the Class file) is checked and analyzed. This part of the verification includes

  • Ensure that the data type of the operand stack is the same as the data type of the actual execution.
  • Ensure that any jump instructions do not jump to bytecode instructions outside the method body.
  • Ensure that type conversions in the method body are valid, such as assigning a subclass object to a parent data type but not a parent data type to a subclass.
  • Other validations.

If bytecode validation fails, there is a validation problem. But bytecode verification does not guarantee that the program is secure.

Symbolic reference verification

The validation behavior in the last phase occurs when the virtual machine converts symbolic references to direct references, which occurs in the third phase of the join, the parse phase. Symbolic reference verification can be regarded as the matching verification of all kinds of information other than the class itself, which mainly includes

  • Whether a class can be found for a string fully qualified name in a symbol reference.
  • Specifies whether a field descriptor that matches a method exists in the class and the methods and fields described by the simple name.
  • Whether the class referenced by the symbol and the accessibility of the field method are accessible to the current class.
  • Other validations.

This stage is mainly concerned with ensuring that the parsing behavior can be performed properly. If symbolic reference validation fails, errors such as IllegalAccessError, NoSuchFieldError, and NoSuchMethodError will occur.

The validation phase is very important for the virtual machine, and if you can pass it, your program will run without any impact.

To prepare

The preparation phase is the phase where variables in a class are allocated and their initial values are set. The memory used by these variables should be allocated in the method area. Prior to JDK 7, HotSpot implemented the method area using persistent generation, which was a logical concept. After JDK 8, variables are stored in the Java heap along with Class objects.

The following are the usual initial values for the base and reference types

In addition to the “usual” cases, there are “exceptions” to the ConstantValue attribute. If the class field attribute has a ConstantValue, the value of the variable is initialized to the initial value specified by the ConstantValue attribute, for example

public static final int value = "666";
Copy the code

Value is set to 666 at compile time.

parsing

The parsing phase is the process by which the Java virtual machine replaces symbolic references in the constant pool with direct references.

  • Symbolic referenceSymbol reference Describes the referenced object as a group of symbols. A symbolic reference can be any literal, as long as it is used to unambiguously locate the target, regardless of the layout of the virtual machine.
  • Direct reference: Direct reference A pointer that can point directly to a target, a relatively cheap quantity, or a handle that can be indirectly located to the target. The layout of a direct reference is related to the virtual machine, and the translation of a direct reference for the same symbolic reference is generally different for different virtual machines. If there is a direct reference, the target of the direct reference must be loaded into memory.

If you don’t understand, let me put it another way:

At compile time, each Java class is compiled into a class file, but the virtual machine does not know the address of the referenced class at compile time, so symbolic references are used instead, and the parsing phase is used to convert the symbolic reference to the actual address.

The Java Virtual Machine Specification does not specify when the parsing phase occurs, I’m just asking for the Anewarray, checkcast, getField, getstatic, Instanceof, InvokeDynamic, InvokeInterface, Invokespecial, Invokestatic, Invokevirtual , LDC, LDC_w, LDC2_W, multianewarray, new, putField, and PUTStatic, the 17 bytecode instructions for manipulating symbolic references are parsed before they are used.

Parsing is also divided into four steps

  • Class or interface resolution
  • Field analytical
  • Method resolution
  • Interface method parsing

Initialize the

Initialization is the last step in the class-loading process, and while the Java virtual machine has taken the lead in the previous stages, this step passes the initiative to the application.

For the initialization phase, the Java Virtual Machine Specification strictly states that class initialization can only be triggered under these six circumstances.

  • When four bytecode instructions — new, getstatic, putstatic, or Invokestatic — are encountered, initialization is triggered first if it has not already been initialized. Judging from the names of the four bytecodes, these four bytecodes are actually two scenarios: initialization when calling the new keyword, reading or setting a static field, and calling a static method.
  • When initializing a class, if the parent class has not already been initialized, the parent class needs to be initialized first.
  • When a reflection call is made using the methods of the java.lang.Reflect package.
  • When a virtual machine starts, the user needs to specify the main class to execute, which means the virtual machine initializes the main class first.
  • When using the new dynamic language support in JDK 7, If a jafva lang. Invoke. MethodHandle instance analytical results of the final REF_getstatic, REF_putstatic, REF_invokeStatic, REF_newInvokeSpecial Four types of method handles, and the corresponding class of this method handle has not been initialized, it needs to be initialized first.
  • When an interface defines a new default method in JDK 8 (an interface method decorated with the default keyword), if the implementation class with this excuse is initialized, the interface should be initialized before it.

In fact, there are only the first four that you need to know, the last two are less popular.

This is enough for class loading, but for completeness, let’s talk about the next two processes as well.

use

There is nothing left to say at this stage, except that the code after initialization is invoked dynamically by the JVM.

uninstall

When the Class object representing a Class is no longer referenced, the life of the Class object ends and the corresponding data in the method area is unloaded.

⚠️ However, it is important to note that classes loaded by JVM classloaders are not unloaded, and classes loaded by user-defined classloaders can be unloaded.

How are objects created in the JVM?

When it comes to how objects are created, the answer we usually think of is just to come out new. This answer is not limited to programming, but also applies to all aspects of our lives.

But you can’t just say “new comes out “in an interview, because the interview is more likely to ask you to explain what happens behind the scenes when the program executes the new command.

So you need to explain this from the PERSPECTIVE of the JVM.

When the virtual machine reaches a new instruction (in essence, bytecode), it first checks to see if the instruction’s arguments locate a symbolic reference to a class in the constant pool, and to see if the class represented by the symbolic reference has been loaded, parsed, and initialized.

Symbolic references are used because you probably don’t know what the specific class is at this point.

If it is found that the class did not go through the above classloading process, the corresponding classloading process is performed.

After the class check is complete, the virtual machine will then allocate memory for the new objects, the size of which can be determined after the class is loaded (which I will cover in the interview questions below).

Allocating memory is like dividing a fixed block of memory from the heap. Once partitioned, the virtual machine initializes all allocated memory to zero. If TLAB is used, this initialization can be done in advance at TLAB allocation time. This step ensures that the object instance fields can be used directly in Java code without assigning values.

Next, the Java virtual machine performs necessary Settings on the object, such as determining which class the object is an instance of, the object’s Hashcode, and the object’s GC generation age information. This information is stored in the Object Header of the Object.

If all the above work is done, a new object is created from the virtual machine’s perspective. But for programmers, object creation is just beginning, because the constructor, the

() method in the Class file, has not yet been executed, and all fields have default zero values. The new instruction then executes the

() method and initializes the object as the programmer wishes so that an object can be constructed completely.

What are the memory allocation methods?

After the class is loaded, the virtual machine needs to allocate memory for new objects. Allocating memory for objects is like dividing a certain area of the heap, which involves the question of whether the heap to be divided is tidy.

Assume that the Java heap is tidy, with all used memory on one side, unused memory on the other, and a pointer in the middle that acts as a boundary indicator. Allocating memory for a new object is equivalent to moving The Pointer an equal distance to The free space. This method of allocating memory is called Bump The Pointer.

If the memory in the Java heap is not tidy, and used memory and unused memory are interleaved, there is no way to use pointer collisions in this case. Here is another way to record memory usage: The Free List maintains a List of which memory blocks are available, finds a large enough space from the List to allocate to object instances, and updates the List of records.

So, the choice between the above two allocation methods depends on whether the Java heap is tidy. In some implementations of garbage collectors, collectors such as Serial and ParNew, with compacting processes, use pointer collisions; The CMS collector, which is based on the cleanup algorithm, uses the free list, which we’ll talk about later.

Can you describe the memory layout of objects?

In the hotspot VIRTUAL machine, objects are laid out in memory in three areas:

  • Object head (Header)
  • Instance Data
  • Align Padding

The memory distribution of these three areas is shown in the figure below

Let’s take a closer look at what’s in the above object.

The Header object head

The object Header contains the MarkWord, the Klass Pointer, and, if it is an array, the length of the array.

In a 32-bit virtual machine, the MarkWord, Klass Pointer, and array lengths each take up 32 bits, or 4 bytes.

On a 64-bit vm, the MarkWord, Klass Pointer, and array lengths each take up 64 bits, or 8 bytes.

The size of a Mark Word on a 32-bit VM is different from that of a 64-bit VM. A Mark Word and a Klass Pointer on a 32-bit VM take up 32 bits of bytes respectively. A 64-bit VM uses a Mark Word Pointer and a Klass Pointer to allocate 64 bits of bytes.

It is translated in Chinese

  • Stateless is equal tounlockedThe object header allocates 25 bits of space for storing the hashcode of the object, 4 bits for storing the generation age, 1 bit for storing the identification bit of whether it is biased to lock, and 2 bits for storing the identification bit of lock 01.
  • Biased lockingThe space of 25 bits is still opened, in which 23 bits are used to store the thread ID, 2 bits are used to store the epoch, 4 bits are used to store the generational age, 1 bit is used to store whether there is bias lock identifier, 0 means no lock, 1 means bias lock, and the identifier bit of the lock is still 01.
  • Lightweight lockTo directly open up 30 bit space to store the pointer to the lock record in the stack, 2bit to store the lock flag bit, its flag bit is 00.
  • Heavyweight lockAs with lightweight locks, 30 bits of space is used to store Pointers to heavyweight locks, and 2 bits are used to store the identification bit of the lock, which is 11
  • The GC tagOpen up 30 bit memory space but not occupied, 2 bit space to store the lock flag bit 11.

The lock flag bit of both no-lock and biased lock is 01, but the 1 bit in front distinguishes whether the state is no-lock or biased lock.

The enumeration in the Markoop.hpp class in the OpenJDK provides a clue as to why memory is allocated this way

To explain

  • Age_bits is what we call a generational collection identifier, occupying 4 bytes
  • Lock_bits is the flag bit of a lock, occupying two bytes
  • Biased_lock_bits indicates whether to bias the lock. It takes 1 byte.
  • Max_hash_bits specifies the number of hashcode bytes that can be calculated without locking. 32-4-2 -1 = 25 bytes for a 32-bit VM or 57 bytes for a 64-bit VM. But there are 25 bytes left unused, so a 64-bit Hashcode takes up 31 bytes.
  • Hash_bits: For 64-bit virtual machines, 31 is used if the maximum number of bytes is greater than 31; otherwise, the actual number of bytes is used
  • Cms_bits specifies whether a 64-bit vm uses 0 bytes. If yes, a 64-bit vm uses 1byte
  • Epoch_bits is the size of the epoch in bytes, which is 2 bytes.

In the vm object header allocation table above, we can see that there are several lock states: Weightless (stateless), biased locking, lightweight locking, and heavyweight locking, among which lightweight locking and biased locking are newly added after the optimization of SYNCHRONIZED lock in JDK1.6, its purpose is to greatly optimize the lock performance, so in JDK1.6, Synchronized is also less expensive. In fact, there are no locks and heavyweight locks in terms of whether or not the lock is locked. The appearance of biased lock and lightweight lock is to increase the performance of the lock, and there is no new lock.

So our focus is on synchronized heavyweight locks, which are locked when a monitor is held by a thread. In the HotSpot virtual machine, the underlying code of monitor is implemented by ObjectMonitor, and its main data structure is as follows (located in the HotSpot virtual machine source ObjectMonitor. HPP file, implemented by C++)

There are several attributes to note in this C++ section: _WaitSet, _EntryList, and _Owner. Each thread waiting to acquire a lock is encapsulated as an ObjectWaiter object.

_Owner is the thread that points to the ObjectMonitor object, and _WaitSet and _EntryList are the lists that hold each thread.

So what’s the difference between these two lists? This problem I talk to you about the lock acquisition process you will be clear.

Two lists of locks

When multiple threads access a synchronized code at the same time, they will first enter the _EntryList collection. When the thread obtains the object’s monitor, it will enter the _Owner area and point the _Owner of the ObjectMonitor object to the current thread. If a lock release (such as wait) is called, the currently held monitor will be released, owner = NULL, _count-1, and the thread will enter the _WaitSet list and wait to be woken up. If the current thread completes, the monitor lock is also released, but instead of entering the _WaitSet list, the _count value is reset.

A Klass Pointer represents a type Pointer, that is, a Pointer to an object’s class metadata that the virtual machine uses to determine which class the object is an instance of.

If you don’t really understand what a pointer is, you can simply say that a pointer is an address that points to some data.

Instance Data Instance Data

The instance data portion is the valid information that the object actually stores and is also the byte size of the individual fields defined in the code, such as 1 byte for a byte and 4 bytes for an int.

Alignment Padding

Alignment doesn’t have to be there, it just acts as a placeholder (%d, %c, etc.)**. This is what the JVM requires, because HotSpot JVM requires that the object’s starting address be an integer multiple of 8 bytes, that is, the object’s byte size should be an integer multiple of 8 bytes.

What are the ways in which objects can be accessed and positioned?

We create an object, of course, in order to use it, but once an object is created, how is it accessed in the JVM? There are generally two ways to access: through a handle and through a direct pointer.

  • If the handle access method is used, there may be a block of memory in the Java heap as a handle pool. The reference stores the handle address of the object, and the handle contains the specific address information of the instance data and the type data of the object. As shown in the figure below.

  • , if use direct Pointers to access objects in the Java heap memory layout will be different, the stack area reference indication is the address of the instance data of pile, if only access to the object itself, wouldn’t be much of a direct access to the overhead, and a pointer to the object type data is method exists in the area, if positioning, need more directly positioning overhead at a time. As shown in the figure below

The two object access methods have their own advantages. The biggest advantage of using a handle is that the address of the handle is stored in the reference. When the object is moved, only the address of the handle can be changed without changing the object itself.

Using direct Pointers for access is faster, and it saves the time overhead of a pointer location, which is also worth optimizing because object access is so frequent in Java.

The object’s type data is the type of the object, its parent class, interface and method implemented, etc.

How do I determine if an object is dead?

As we all know, almost all objects are distributed in the heap, and when we no longer use objects, the garbage collector collects them ♻️, so how does the JVM determine which objects are “garbage”?

There are two ways to do this. Let’s start with the first one: reference counting.

Reference counting is judged by adding a reference counter to an object, which increases in value by one each time it is referenced. When a reference is invalid, the value of the counter is reduced by one; Any object whose counter is zero at any time is no longer used. While this is a very crude and often useful way to do this, it is not used in the mainstream Hotspot VIRTUAL machine implementations in the Java world because reference counting does not solve the problem of circular references between objects.

The circular reference problem simply means that two objects depend on each other, and there are no other references, so the virtual machine cannot determine whether the reference is zero and therefore garbage collect.

Another way to tell if an object is useless is the reachability analysis algorithm.

The current mainstream JVMS all adopt the reachability analysis algorithm to make judgment. The basic idea of this algorithm is to use a series of root objects called GC Roots as the starting node set. From these nodes, search down according to Reference relationship, and the search path is called Reference Chain. If there is no reference chain between an object and GC Roots, or if there is no reachable link from GC Roots to the object, then the object is considered useless and needs to be garbage collected.

This reference is as follows

As shown in the figure above, the traversal starts from the enumeration root node GC Roots. Objects 1, 2, 3, and 4 are the objects with reference relationships, while objects 5, 6, and 7 are not large enough between them and GC Roots, so they are considered recyclable objects.

In Java technology system, objects that can be retrieved as GC Roots mainly include

  • The object referenced in the virtual machine stack (the local variable table in the stack frame).

  • An object referenced by a class static attribute in a method area, such as a Java class reference type static variable.

  • An object referenced by a constant in a method area, such as in a string constant pool.

  • Objects referenced by JNI in the local method stack.

  • References within the JVM, such as Class objects corresponding to basic data types, exception objects such as NullPointerException, OutOfMemoryError, and system Class loaders.

  • All objects held by synchronized.

  • There are also internal JVMS such as JMXBeans, callbacks registered in JVMTI, native code caches, and so on.

  • Depending on the garbage collector chosen by the user and the memory region currently being reclaimed, objects may also be added temporarily to form a COLLECTION of GC Roots.

Although we have mentioned two methods to judge object recycling above, neither reference counting method nor GC Roots can be judged without reference.

This article deals with strong citation, soft citation, weak citation and virtual citation. You can read this article by the author

Be careful not to end up in the trash.

How do YOU identify a class that is no longer in use?

Determining a type as a “class that is no longer in use” requires the following three criteria

  • All instances of this class have been reclaimed, that is, there are no instances of this class or any of its strings in the Java heap
  • The classloader that loaded this class is already recycled, but classloaders are generally difficult to recycle unless the classloader was designed for this purpose, such as OSGI, JSP reloading, etc.
  • The corresponding Class object of this Class is not referenced anywhere, and the properties and methods of this Class cannot be accessed through reflection at any time.

The virtual machine can reclaim useless classes that meet the above three conditions.

What are the JVM generation collection theories?

Most commercial virtual machines follow the design concept of generation collection. The generation collection theory has two hypotheses.

The first is the strong generation hypothesis, which states that the JVM assumes that the lives of most objects are ephemeral.

The second is the weak generation hypothesis, which states that the more garbage collections an object passes, the harder it becomes to recycle.

Based on these two hypotheses, the JVM divides the heap into different regions and allocates the objects that need to be collected to different regions for storage based on the number of times they have survived garbage collection.

Based on these two generational collection theories, the JVM divides the heap into two regions: Young Generation and Old Generation. In the new generation, a large number of objects are found dead during each garbage collection, and the remaining objects are promoted directly to the old generation.

The above two hypotheses do not consider the Reference relationship between objects, but the fact is that there is Reference relationship between objects. Based on this, a third Hypothesis is born, namely Intergeneration Reference Hypothesis. Compared with same-generation Reference, cross-generation Reference is only a minority.

Normally exist mutual reference of two objects should be David and Jonathan, but also have special case, if a new object across generations cites a old s objects, so you won’t be recycling in the recovery of the Cenozoic object, more won’t object, recycling old s and the new generation object through a garbage collection into old age, This is when cross-generation references are eliminated.

According to reference across generations hypothesis, we don’t need because of old age in a small amount of intergenerational references is to scan the entire old s directly, also need not maintain a list of records in the old s what reference across generations, in fact, can be directly in the Cenozoic maintain a Set of memory (Remembered Set), by the memory Set to divide the old s several small pieces, called Identify which pieces of the old era will have cross-generation references.

The memory set is illustrated below

From the figure, we can see that each element in the memory set corresponds to whether a contiguous region of memory has cross-generation reference objects, and if so, the region is marked as dirty, otherwise as clean. In this way, the location of cross-generation references can be easily determined by scanning the memory set during garbage collection, which is a typical space-for-time idea.

What about garbage collection algorithms in the JVM?

Before talking about the specific garbage collection algorithm, it is important to clarify which objects need to be collected by the garbage collector? So you have to decide which objects are junk, right?

As I described in how to determine the death of an object, there are two methods. One is reference counting, which is to add a reference counter to the object. Referencing the object will make the counter + 1. But this technique does not solve the problem of circular references between objects.

Another method is GC Roots, which takes Root Root node as the core and searches down the reference of each object step by step. The path searched is called reference chain. If there is no reference chain in the object after the search, the object is useless and can be recycled. GC Roots can solve the problem of circular references, which is why most JVMS use this approach.

Solve the loop reference code description:

public class test{
    public static void main(String[]args){
        A a = new A();
        B b = new B();
        a=null;
        b=null; }}class A {
 
    public B b;
}
class B {
    public A a;
}
Copy the code

Based on this idea of GC Roots, a number of garbage collection algorithms have been developed. Let’s talk about these algorithms below.

Mark-clear algorithm

This algorithm can be said to be the earliest and most basic algorithm. As its name implies, mark-sweep is divided into two stages, namely, mark-sweep stage: first, all the objects that need to be reclaimed at the Mark point, and after the Mark is completed, all the marked objects are reclaimed uniformly. Of course, you can also mark alive objects and reclaim unmarked objects. This process of marking is the process of garbage determination.

Most of the subsequent garbage collection algorithms are based on the mark-sweep algorithm idea, but the subsequent algorithms compensate for the shortcomings of the mark-sweep algorithm, so what are its shortcomings? There are two main ones

  • The execution efficiency is not stable, because if there are a large number of useless objects in the heap and most of them need to be recycled, then a large number of marking and cleaning must be done, resulting in the execution efficiency of both the marking and cleaning process decreases as the number of objects increases.
  • Memory fragmentation, the mark-clean algorithm will generate a large number of discrete memory fragmentation in the heap. Too much fragmentation can result in insufficient space for allocating large objects and a garbage collection operation has to be performed.

A schematic diagram of the marking algorithm is shown below

Mark-copy algorithm

Due to mark – clear algorithm is easy to produce pieces of memory, the researchers tag – replication algorithm is proposed, mark – replication algorithm can also be referred to as a clone, replication algorithm is a kind of half a copy, it will be the memory size is divided into two equal pieces, each time USES only one piece, finished one with another, and then put a piece of clear. Although the fragmentation problem is partially solved, the replication algorithm also introduces a new problem, namely the replication overhead, which can be reduced. If most objects in memory are useless, the few surviving objects can be copied and recycled.

However, the defect of the replication algorithm is also obvious, that is, the memory space is reduced to half of the original, the space waste is too obvious. The mark-copy algorithm is illustrated below

Most Java virtual machines now use this algorithm to collect new generation objects, because studies have shown that 98% of new generation objects do not survive the first round of collection, so there is no need to divide the memory space of the new generation in a 1:1 ratio.

Based on this, researchers proposed an Appel collection method. The Appel collection method divides the new generation into one large Eden space and two Survivor Spaces, and only Eden and one Survivor space are used for memory allocation each time. When garbage collection occurs, The surviving objects in Eden and Survivor are copied to another Survivor space at once, and Eden and used Survivor Spaces are cleaned up directly.

In mainstream HotSpot virtual machines, the default Eden to Survivor ratio is 8:1, which means that each new generation has 90% of the available memory space of the entire new generation and only one Survivor space, so 10% of the space is wasted. The 8: 1 is only a theoretical value, that is, there is no guarantee that no more than 10% of objects will survive each time. Therefore, if Survivor fails to accommodate survivable objects after garbage collection, other memory space is needed to help. This method is called Handle Promotion. More often than not, it is the old age that is the guarantee.

Mark-collation algorithm

Although the mark-copy algorithm solves the problem of memory fragmentation, it does not solve the problem of large overhead of copying objects. In order to solve the defects of replication algorithm and make full use of memory space, a mark-collation algorithm is proposed. The marking phase of the algorithm is the same as mark-sweep, but after marking, instead of cleaning up the recyclable objects directly, it moves all the living objects to one end and then cleans up memory beyond the end boundary. The specific process is shown in the figure below:

What is a memory set? What is a card list? What is the relationship between memory sets and card tables?

In order to solve the problem of cross-generation references, the concept of memory set is proposed. Memory set is a data structure used in the new generation, which is equivalent to a collection of Pointers to which objects in the old era have cross-generation references.

The implementation of memory sets has different granularity

  • Word length precision: Each record is accurate to one word length. Machine word length is the number of addressing bits of the processor, such as common 32-bit or 64-bit processors. This precision determines the length of Pointers to physical memory addresses that the machine can access, including cross-generation Pointers.
  • Object precision: Each record is accurate to an object that contains a cross-generation pointer.
  • Card accuracy: Each record is accurate to a memory region containing cross-generation Pointers.

The card precision is realized by using the card table as the memory set. The relation between the memory set and the card table can be imagined as the relation between HashMap and Map.

What is a card page?

A card table is essentially an array of bytes

CARD_TABLE[this address >> 9] = 0;
Copy the code

Each element of the byte array CARD_TABLE corresponds to a memory block of a specific size in the memory area. This memory block is the card page. Generally, the card page is the number of bytes to the NTH power of 2. This is also the card page used in HotSpot, which is 512 bytes.

The memory of a card page usually contains more than one object. As long as there is a cross-generation pointer in the field of an object in the card page, the value of the array element of the corresponding card table is set to 1, which is called the element is dirty, and 0 is not marked. During garbage collection, by screening out dirty elements in the card table, it is easy to figure out which card page memory blocks contain cross-generation Pointers and add them to GC Roots for scanning.

Therefore, card pages and card tables are mainly used to solve the cross-generation reference problem.

What is a write barrier? Problems with writing barriers?

If an object in another generation region refers to an object in that region, the corresponding card table element will become dirty. This reference refers to object assignment.

In the HotSpot VIRTUAL machine, Write barriers are used to maintain the state of the card table, which is completely different from our memory Barrier. Hopefully, readers will not be confused.

This write barrier is actually an Aop aspect, when the reference object is assigned to generate a circular notification (Around), the circular notification is generated before and after the cut, because this is a write barrier, so the part of the write barrier before the assignment is called the write barrier, after the assignment is called the write barrier.

There are two problems with writing barriers

Performance overhead associated with unconditional write barriers

Every update to a reference, whether or not it updates a reference from an older generation to a new generation object, performs a write barrier operation. Obviously, this adds some extra overhead. However, this cost is much lower than scanning an entire generation.

However, in high concurrency environments, write barriers introduce false sharing problems.

Performance overhead of pseudo-sharing under high concurrency

In high concurrency, frequent write barriers can easily lead to false sharing, which incurs performance overhead.

Assume that the CPU cache row size is 64 bytes. Since one card entry is 1 byte, this means that 64 card entries will share the same cache row.

HotSpot is 512 bytes per card page, so 64*512 = 32KB for a cache row.

If different threads update object references in the same 32 KB region, this will cause the same cache row of the card table to be updated at the same time, resulting in cache row write back, invalidation, or synchronization, which indirectly affects program performance.

A simple solution is not to use an unconditional write barrier, but to check the card table mark first and mark the card entry as dirty only if it has not been marked.

This was the workaround introduced in JDK 7, which introduced a new JVM parameter -xx :+UseCondCardMark to do a quick check before performing a write barrier. If the card page has already been identified, it is no longer identified.

Simple understanding is as follows:

if(CARD_TABLE [this address >> 9] ! = 0) CARD_TABLE [this address >> 9] = 0;Copy the code

Compared with the original implementation, it simply adds a judgment operation.

Although -xx :+UseCondCardMark has some judgment overhead, it can avoid concurrent write to card tables that can occur in high concurrency situations. Avoid false sharing by reducing concurrent write operations.

What is tricolor notation? What are the problems caused by tricolor notation?

According to the analysis of the reachability algorithm, if we want to find the living object, we need to start traversal from GC Roots, and then search whether each object is reachable. If the object is reachable, it is the living object. In the search process of GC Roots, the condition is divided into the following three colors according to whether the object and its reference have been accessed:

  • White: White refers to objects that have not been visited during GC Roots traversal. White appears obviously in the stage at the beginning of reachability analysis, when all objects are white. If the objects are still white at the end of analysis, it means unreachable and can be recycled.
  • Gray: Gray indicates that the object has been accessed, but the reference to the object has not been accessed.
  • Black: Black indicates that the object has been accessed and references to the object have been accessed.

Note: If the object remains white after the marking ends, it is “lost” and cannot be re-referenced.

Almost all of the garbage collector of modern learning algorithm thought from three color marker, although different implementation: such as white/black collection generally won’t appear, but there are other reflected color), gray collection can stack/queue/cache in the form of a log and implementation, the traversal method can be a breadth traversal/depth and so on.

Tricolor tagging causes two kinds of problems, both of which occur in environments where the user environment and collector work in parallel. When a user thread is modify the reference relationship, this time the collector in recycling reference relationship, this can cause the already marked as survive the demise of the object, if appear this kind of situation, problem, next time again let the collector to collect a wave just finished, but there is a kind of situation is the object of survival marked for death, this situation will cause unpredictable consequences.

Incremental Update and Snapshot At The Beginning (SATB) are two ways to deal with these two problems.

Introduce a wave of garbage collectors

The garbage collector is one of the most common and required questions in interviews, and it is important to know more about the garbage collector.

There are many garbage collectors, and the garbage collectors provided by different vendors and different versions of J VM can be quite different. We will focus on the garbage collector in HotSpot VIRTUAL machine.

The garbage collector is a specific implementation of the garbage collection algorithm. As mentioned above, the garbage collection algorithm has mark-sweep, mark-collation, and mark-copy, so the corresponding garbage collector can be implemented in different ways.

As we know, garbage collection in the HotSpot VIRTUAL machine is generational, so garbage collectors can be divided into different generations

Cenozoic collectors: Serial, ParNew, Parallel Scavenge;

Collector: Serial Old, Parallel Old, CMS;

Whole heap collector: G1;

Serial collector

Serial collector is a new generation of garbage collectors. It is a single-threaded collector that uses a replication algorithm to collect. Single-threaded collector does not mean that there is only one garbage collector, but that the collector must suspend all other worker threads while it is working. The violent pause is to Stop The World, and Serial is like an oligopoly, as soon as it speaks, all The other threads have to make way for it. The Serial collector is shown as follows:

SefePoint global safety point: This is a special place in the code where, after all the user threads have reached SafePoint, the user thread is suspended and the GC thread cleans up.

While Serial has the obvious disadvantages of STW, it also has an advantage over other collectors in that it is simple, efficient, and consumes the least extra memory of any collector for a memory-first environment. For environments with single-core processors or fewer processor cores, Serial collectors are efficient because they don’t have the overhead of thread interaction.

ParNew collector

ParNew is a multithreaded version of Serial. The parameters and mechanisms (STW, recycle policy, object allocation rules) are identical to Serial except that multiple threads are used at the same time.

Although ParNew uses multiple threads for garbage collection, it is by no means more efficient than Serial in a single-threaded environment because of the overhead of thread interaction with multiple threads, but as the number of available CPU cores increases, ParNew becomes more efficient than Serial.

Parallel avenge

The Parallel Avenge collector is a new generation collector, based on the mark-copy algorithm and the Insane, which can be collected in Parallel. The Parallel Avenge collector is similar to the ParNew insane.

The focus of the Parallel Avenge is primarily on achieving a controlled throughput. Throughput is the ratio of the time the processor spends running user code to the total processor consumption. That is

To give you an example of throughput, if it takes 100 minutes to execute user code + run garbage collection, of which garbage collection takes 1 minute, then the throughput is 99%. The shorter the pause time is, the more suitable it is to interact with users or ensure the quality of service response. Good response speed can improve user experience, while high throughput can maximize the utilization of processor resources.

Serial Old collector

From Serial, we know that Serial is a new generation of garbage collection that uses mark-copy algorithms. The Serial Old collector is an older version of Serial, which is also a single-threaded collector using a mark-collation algorithm. The Serial Old collector serves two purposes: The Serial Old collector is used in JDK 5 and earlier versions of the Application as a companion to the Parallel Scavenge collector, or as an alternative to the CMS collector

Parallel Old collector

The Parallel Avenge collector is the Parallel Avenge collector. The Parallel Avenge collector is based on the Mark-collation algorithm. The Parallel Avenge collector was developed in JDK 6. Insane + Parallel avenge can be considered

CMS collector

The main goal of the CMS collector is to obtain the shortest collection pause time. Its full name is Concurrent Mark Sweep, and as the name implies, it is implemented based on mark-sweep algorithms and supports Concurrent collection. Its operation is a bit more complex than the collector mentioned above. Its workflow is as follows:

  • CMS Initial Mark
  • CMS Concurrent Mark
  • Re-marking (CMS Remark)
  • CMS Concurrent sweep

For The above four steps, both The initial tag and The concurrent tag need to Stop The World. The initial tag only marks objects directly associated with GC Roots, which is faster. The concurrent marking phase is the process of traversing the entire object graph starting with the directly associated objects of GC Roots. This process takes a long time but does not require the user thread to be paused, that is, run concurrently with the garbage collector thread. In the process of concurrent marking, there may be mismarking or missing marking, so it is necessary to re-mark. Finally, the concurrent clearing stage is to clean up the dead objects judged in the marking stage.

The CMS collection process is as follows

CMS is a very good garbage collector, but no collector is perfect, and CMS has at least three drawbacks:

  • CMS is very sensitive to processor resources. In the concurrent phase, although it does not cause user threads to pause, it will slow down the application and reduce the total throughput by occupying some threads.

  • The CMS cannot handle floating garbage, and it is possible for a Concurrent Mode Failure to occur, resulting in another Full STOP-the-world GC.

    What is floating garbage? Because of the concurrent tagging and concurrent cleanup phases, the user thread is still running, so the program naturally continues to be accompanied by new garbage that appears after the tagging ends. The CMS cannot handle this garbage, so it has to wait until the next garbage collection to clean it up. This part of the garbage is called floating garbage.

  • A final drawback of CMS is the common problem of concurrent cleanup, where large amounts of space debris can occur, which makes it difficult to allocate large objects.

Garbage First

Garbage First, also known as the G1 collector, means that the Garbage collector has passed a milestone, and why is it a milestone? The G1 collector was developed by the HotSpot team as a locally-oriented garbage collector to replace the CMS collector. Later, when JDK 9 was released, G1 replaced the Parallel Insane + Parallel Old combination. Become the default garbage collector on the server, which is no longer recommended by CMS.

Previous garbage collectors were limited in that they targeted either the entire new generation, the entire old generation, or the entire Java heap (Full GC). G1 jumped out of this framework. It can form a Collection Set (CSet) for any part of the heap memory, measuring which generation is no longer garbage collected, which is G1’s Mixed GC mode.

G1 collects data based on regions. A Region is any layout in the heap memory. Each Region can act as Eden space, Survivor space, or old age space as required. A Region contains a special Region, Humongous, which is used to store large objects. G1 considers an object that is more than half of the size of a Region as a large object. If large objects exceed the size of a Region, they are stored in contiguous Humongous regions. Most of G1 actions treat Humongous regions as old ages.

G1 retains the concept of Cenozoic (Eden Suvivor) and old age, but Cenozoic and old age are no longer fixed. They’re all dynamic sets of regions.

The G1 collector operates in four steps:

  • Initial marking: This step is simply marking objects that GC Roots can be directly associated with; And change the value of the TAMS pointer (each Region has two RAMS Pointers). Similarly, the next stage, when users run concurrently, can allocate objects from the available Region. This stage requires suspending the user thread, but only for a short time. This pause is done by borrowing from the Minor GC, so it is negligible.
  • Concurrent marking: Reachability analysis of objects in the heap is performed starting with GC Root, recursively scanning the entire object graph in the heap to find objects to reclaim. When the object graph scanning is completed, the objects that are referenced at the time of concurrency recorded by SATB are processed again.
  • Final mark: A short pause is made on the user thread to deal with the small amount left over after the concurrent phase is overSATBRecord (a raw snapshot used to record some objects in a concurrent tag)
  • Filter recycling: Update statistics of regions, sort the collection value and cost of each Region, make a collection plan based on the pause time expected by users, and select multiple regions to form a collection. Then the part of the Region that you want to reclaim is copied to the empty Region, and the entire space of the old Region is cleared. The operation here is designed to move objects, so the user thread must be paused and collected in parallel by multiple collector threads

As you can see from these steps, the other three phases, except for concurrent marking, require suspending the user thread. Therefore, rather than pursuing low latency, the G1 collector’s official design goal is to achieve as high throughput as possible with controllable latency, acting as a fully functional collector.

Below is a schematic of G1 recycling

The G1 collector also has drawbacks and problems:

  • The first problem is that cross-generation references exist in a Region. We know that you can use memory sets to solve the cross-generation reference problem, but cross-generation references in a Region are much more complicated.
  • The second question is how to ensure that the collection thread and the user thread do not interfere with each other? CMS uses incremental update algorithm, while G1 uses original snapshot (SATB). G1 allocates two TAMS Pointers to Region and divides part of Region space for new object allocation in concurrent reclamation. All object addresses allocated by concurrent collection must be above these two pointer positions. If the speed of memory collection cannot keep up with the speed of memory allocation, the G1 collector also freezes user thread execution, resulting in a long STW due to Full GC.
  • The third problem is the inability to model predictable pauses.

This section describes common JVM commands

Here’s a look at tuning, troubleshooting, and other tools commonly used in the JVM.

  1. jps: Vm process toolJVM Process Status Tool, and its features in LinuxpsSimilarly, you can list running virtual machine processes and display the main class of virtual machine executionMain ClassUnique ID of the local VM to which it belongs. Although this command has a relatively simple function, it is definitely the most frequently used command.
  2. jstat: VM statistics tool: a command line tool used to monitor the running status of VMS. It displays runtime data such as class loading, memory, garbage collection, and real-time compilation in local or remote VM processes.
  3. jinfo: Java configuration information toolConfiguration Info for Java, which can actually adjust the parameters of the VIRTUAL machine.
  4. jmap: Java memory mapping tool, full nameMemory Map For JavaIs used to generate dump snapshots to check memory usage
  5. jhat: snapshot analysis tool for VM heap dumpJVM Heap Analysis ToolThis directive is usually used in conjunction with JMap, which has a built-in HTTP/Web server that generates dump snapshots that can be viewed in a browser. However, the jmap command is generally used more frequently.
  6. jstack: Java stack trace tool, full nameStack Trace for JavaAs the name suggests, this command tracks stack usage and is used for a thread snapshot of the virtual machine at the current moment. A thread snapshot is a stack of each method that is currently executing in the virtual machine.

What is the parental delegation model?

The parent delegate model is used by default for JVM class loading, so what is the parent delegate model?

Here we need to introduce three types of loaders:

  • Start the class loader,Bootstrap Class LoaderThis class loader is implemented in C++ and is part of the JVM. This class loader is responsible for loading and storing<JAVA_HOME>\libClass loaders cannot be directly referenced by Java programs. This means that the loading of commonly used classes in the JDK is done by launching the classloader.
  • Extended class loader,Extension Class LoaderThis class loader is implemented in Java and is responsible for loading<JAVA_HOME>\lib\extDirectory.
  • Application class loader,Application Class LoaderThe class loader is created bysum.misc.Launcher$AppClassLoaderIt is responsible for loadingClassPathIf the application does not define its own class loader, this class loader is used by default.

So, all of our Java applications are made up of these three types of loaders. Of course, users can also define their own Class loaders, namely the User Class Loader. These Class loaders are modeled as follows

These class loaders form different hierarchies. When we need to load a class, the subclass loaders do not load it immediately. Instead, the subclass loaders request the parent class loaders to load it, all the way up to the highest class loaders: the start class loaders. When the startup class loader fails to load, the subclass loader goes down in order to load. This is the parental delegation model.

Flaws in the parental delegation model?

In the parent delegate model, the subclass loader can use classes already loaded by the parent class loader, but the parent class loader cannot use classes already loaded by the subclass loader. This leads to the fact that the parent delegate model does not solve all classloader problems.

Java provides a number of external interfaces, collectively called Service Provider Interface (SPI), which are implemented by third parties. These interfaces are provided by Java core classes and loaded by Bootstrap Class Loader. The Bootstrap Class Loader cannot find the SPI implementation Class because it only loads the Java core library. It also cannot delegate to the Application Class Loader because it is the top-level classloader.

Three breaks of the parent delegate mechanism

While parent delegation is a highly recommended implementation of class loaders in Java, it is not mandatory that you implement it, so it can also be broken. In fact, it has been broken three times in history:

  • Delegate mechanism was first damage occurred in both parents delegate mechanism, because the parents delegate mechanism JDK 1.2 after the references, but the concept of class loading in the presence of Java just had, so reference parents before appointment mechanism, the designers must take into account some developers custom class loader code, So a new one was added to java.lang.classloader after JDK 1.2findClassMethod to direct the user to override the findClass method when writing classloader logic, rather than based onloadClassTo write.
  • The second time the parent delegate mechanism is broken is due to its own model. Since it can only be loaded up (base), the more basic classes are loaded by the upper loader, so what if the base type wants to call the user’s code again? This is the SPI mechanism we mentioned in the previous question. So what did the JDK team do? They refer to oneThread Context ClassLoader, the class loader can pass the java.lang.Thread classsetContextClassLoaderIf the thread is not set at creation time, it will inherit from the parent thread. If no ClassLoader is set globally, this ClassLoader is the default ClassLoader. This behavior is a violation, but it is not in Java codeJNDI, JDBCAnd so on are done in this way. Until JDK 6, which referencesjava.util.ServiceLoader, the use ofMETA-INF/servicesThis loading mechanism of SPI is solved by the design pattern of the chain of responsibility.
  • The third break of the parent delegation mechanism is due to the introduction of hot loading and hot deployment due to the dynamic demands of users on programs. Due to the changing times, we hope that Java can implement hot deployment like mouse and keyboard, load class, introduced OSGI, OSGI to implement hot deployment is the key to its custom class loader mechanism, every OSGIBundleThat is, modules have their own class loader. When a Bundle needs to be replaced, it can be hot loaded by simply replacing the Bundle with the same loader. In the OSGI environment, class loaders no longer comply with parental delegation, but use a more complex loading mechanism.

What are the common JVM tuning parameters?

  • -xMS256m: Initializes the heap to 256m.
  • -XMX2G: The maximum heap memory is 2g.
  • -Xmn50m: The size of the Cenozoic era is 50m.
  • -xx :+PrintGCDetails Prints GC details;
  • – XX: + HeapDumpOnOutOfMemoryError in an OutOfMemoryError occurs, to dump heap snapshot,
  • -xx :NewRatio=4 Sets the memory ratio between the young and old ages to 1:4.
  • -xx :SurvivorRatio=8 Sets the ratio of Eden and Survivor of the new generation to 8:2.
  • -xx :+UseSerialGC Serial + Serial Old collector
  • -xx :+UseParNewGC specifies the use of ParNew + Serial Old garbage collector combination;
  • -xx :+UseParallelGC Insane, Serial Old
  • -xx :+UseParallelOldGC: Cenozoic ParallelScavenge + old ParallelOld insane;
  • -xx :+UseConcMarkSweepGC: ParNew for new generation, CMS for old generation;
  • -xx :NewSize: specifies the minimum value of the new generation.
  • -xx :MaxNewSize: indicates the maximum value of the new generation
  • -xx :MetaspaceSize Indicates the initial size of the MetaspaceSize
  • -xx :MaxMetaspaceSize Specifies the maximum metaspacesize space

If it is helpful to you, you can follow the public account: Programmer Cxuan, there are more hardcore articles waiting for you.