Follow your wechat account: BaronTalk for more excellent articles!

Books are really often read often new, the ancients said that “read a book a hundred times its sense of self” or quite reasonable. I have read this book “In-depth Understanding of Java Virtual Machine” by Zhou Zhiming for more than three times. Each time I read it, I have gained something new, and each time I read it, I have further understanding of Java virtual machine. Therefore, THE initiation of reading notes into a written idea, one is to test their own learning results, a systematic review of the learning content; Second, I would like to recommend this excellent work to those who have not been exposed to it, so that they can have a glimpse of the essence of the book through my article before reading it.

The original thought of an article is enough, but writing writing found that the length greatly exceeded expectations. It seems that the skills are not enough, so we simply divided into six articles, respectively from automatic memory management mechanism, class file structure, class loading mechanism, bytecode execution engine, program compilation and code optimization, efficient concurrency six aspects to do a more detailed introduction. This article starts with automatic memory management for the Java virtual machine.

Run time data area

During the execution of Java programs, the Java VIRTUAL machine divides the memory area managed by the virtual machine into several data areas. Each of these areas has its own purpose and time of creation and destruction. Some areas exist with the start of a virtual machine process, while others are created and destroyed depending on the start and end of a thread. The memory managed by the Java VIRTUAL machine is divided into the following areas:

Program counter

A program counter is a small area of memory that can be thought of as a line number indicator of the bytecode being executed by the current thread. In the conceptual model of virtual machine, bytecode interpreter works by changing the value of this counter to select the next bytecode instruction to be executed, and basic functions such as branch, loop, jump, exception handling, thread recovery and so on need to be completed by this counter. “Thread private memory area”

Java virtual machine stack

A Stack Frame is created when each method is executed to store information about local variables, operation Stack, dynamic links, method exits and so on. The process of each method from being called to completion of execution corresponds to the process of a stack frame in the virtual machine stack from the stack to the stack. “Thread private memory area”

Local variation scale: The local variable table is the part of the Java virtual machine stack that stores the basic data types known by the compiler (Boolean, byte, CHAR, short, int, float, long, double), object references (reference types, not the same as the object itself, Depending on the virtual machine implementation, this may be a reference pointer to the object’s starting address, a handle representing the object, or some other location associated with the object, and the returnAddress type (which points to the address of a bytecode instruction).

Local method stack

This is similar to the virtual machine stack, except that the virtual machine stack serves the Java methods executed by the virtual machine, and the Native method stack serves the Native methods used by the virtual machine. “Thread private memory area”

The Java heap

For most applications, the Java heap is the largest chunk of memory managed by the virtual machine, an area of memory that is shared by all threads and created when the virtual machine is started. The only purpose of this memory area is to hold object instances, and almost all object instances are allocated here (not always, but in the case of virtual machine optimization, stack allocations and scalar substitutions can also occur, as described in a later section). The Java heap is the primary area for GC collection and is often referred to as the GC heap. From a memory reclamation perspective, the Java heap can also be subdivided into new generation and old generation; The next generation can be divided into Eden Space, From Survivor Space, and To Survivor Space. From the point of view of memory reclamation, a Java heap shared by threads can be partitioned into Thread Local Allocation Buffers (TLabs) that are private to multiple threads. “Memory region that belongs to threads shared”

Methods area

Used to store information about classes that have been loaded by the virtual machine, constants, static variables, code compiled by the just-in-time compiler, and so on. “Memory region that belongs to threads shared”

Runtime Constant Pool: The runtime Constant Pool is a part of the method area. The Constant Pool Table is used to store various literal and symbolic references generated at compile time.

Direct Memory: Direct Memory is not part of the virtual machine’s run-time data region, nor is it defined in the Java Virtual Machine specification. NIO in Java can use Native functions to allocate out-of-heap memory directly, and then operate as a reference to that memory via a DiectByteBuffer object stored in the Java heap. This can significantly improve performance in some scenarios because it avoids copying data back and forth between the Java heap and Native heap. Direct memory is not limited by the Size of the Java heap.

Object creation, memory layout and access location

Earlier, we introduced the Runtime data area of the Java Virtual machine and looked at the virtual machine memory. Let’s look at how objects are created, what their memory layout is, and how objects are positioned in memory.

2.1 Object Creation

To first create an object in the Java heap (not absolute, introduced virtual machine optimization strategy behind what do you do in detail) for the allocated memory to create objects, the process of allocating memory to ensure the safety of concurrent, and then to the corresponding memory initialization, a series of process, after the completion of a real object is created.

Memory allocation

When the virtual machine gets a new instruction, it first checks to see if the instruction’s arguments can locate a symbolic reference to a class in the constant pool, and whether the symbolic reference represents a class that has been loaded, parsed, and initialized. If not, the corresponding class loading process must be performed first. After the class load check passes, the virtual machine allocates memory for the new objects. The size of memory required by an object is fully determined after the class is loaded, and the task of allocating memory for an object is equivalent to dividing a certain size of memory from the Java heap.

Partitioning memory in the Java heap involves two concepts: Bump the Pointer and Free List.

  • If the Java heap memory is absolutely neat, all used memory aside and free memory on the other side, there is a middle pointer as a cut-off point indicator, the allocated memory is tightly is the pointer to the free space there move a and the object is equal to the size of the distance, this way of distribution is called the “pointer collision” * * * *.

  • If the memory in the Java heap is not tidy, and used memory and free memory cross each other, there is no simple way to do pointer collisions. The virtual machine must maintain a list of what memory is available, find a block of space from the list that is large enough to be allocated to object instances, and update the records on the list. This allocation is called ** “free list” **.

The choice of allocation method is determined by the cleanliness of the Java heap, which in turn is determined by the use of a garbage collector with collation capabilities.

Ensure concurrency security

Object creation is A very frequent activity in virtual machines. Even changing the location of A pointer is not safe in concurrent situations. Object B may use the original pointer to allocate memory while object A is being allocated. There are two solutions to this problem:

  • Synchronizes the action of allocating memory space (CAS + retry to ensure atomicity of update operation);

  • The Allocation of memory is divided into different Spaces by Thread. In other words, each Thread allocates a small chunk of memory in the Java heap, called Thread Local Allocation Buffer (TLAB). Whichever thread wants to allocate memory is allocated on the TLAB of that thread. Synchronization locks are required only when the TLAB runs out and new TLabs are allocated.

Initialize the

After the memory allocation is complete, the virtual machine initializes the allocated memory space to zero (excluding the object header). If TLAB is used, this step is advanced to TLAB allocation. This step ensures that the instance fields of the object can be used directly in Java code without assigning initial values.

Next, you set the Object Header information, including which class the Object is an instance of, how to find the metadata of the class, the Hash of the Object, and the GC generation age of the Object.

This sequence of actions is followed by the execution of a method that initializes the object as intended by the programmer, and a true object is created.

The process for creating objects in the JVM looks like this:

2.2 Memory Layout of objects

In the HotSpot virtual machine, the layout of objects in memory can be divided into three blocks: object headers, Instance Data, and Padding.

Object head

The object header contains two parts of information. The first part is used to store the runtime data of the object itself, such as HashCode, GC generation age, lock status flags, locks held by the thread, bias thread ID, bias timestamp, and so on. This part of the data is called the Mark Word. The other part of the object header is the type pointer, the class metadata pointer to which the object points, which the VIRTUAL machine uses to determine which class instance the object is. In the case of an array, there must also be a block of data in the object header that records the length of the array. (Not all virtual machine implementations are required to keep type Pointers on object data, as explained in the next section on “Object access location.”)

The instance data

Object actually stores valid data, which is also the content of various fields defined in the program code.

Alignment filling

It has no special meaning and is not required to exist. It serves only as a placeholder.

2.3 Object Access positioning

Java programs need reference information on the stack to manipulate specific objects on the heap. According to different VIRTUAL machine implementations, the main methods of accessing objects are handle access and direct pointer access.

Handle access

The Java heap is divided into a block of memory as a handle pool. Reference stores the handle address of the object, and the handle contains the specific address information of the instance data and type data of the object.

The advantage of using handle access is that reference stores a stable handle address. When the object is moved, only the pointer to the instance data in the handle needs to be changed, and reference itself does not need to be modified.

Direct Pointers

Type data is stored in the object header, and the object address is stored in reference.

The advantage of using direct pointer access is that it is faster and it saves the overhead of a pointer location. Because object access is so frequent in Java, this kind of overhead can add up to a very significant execution cost. This is the approach used in HotSpot.

Garbage collector and memory allocation strategy

As we said earlier when we introduced the JVM runtime data area, the program counter, virtual machine stack, and local method stack live and die with the thread; The stack frames in the stack methodically perform loading and unloading operations as methods enter and exit. How much memory is allocated in each stack frame is basically known when the data structure is determined. Therefore, the allocation and reclamation of memory in these areas are deterministic, so there is no need to worry too much about the problem of memory reclamation, because at the end of the method or the end of the thread, memory will be reclaimed.

The Java heap and methods area is different, an interface of multiple implementation classes need memory may not be the same, a method of multiple branch need memory also may not be the same, we can only know in program running period is created which objects, this part of the memory allocation and recovery is dynamic, the garbage collector to focus on is this part of the memory.

3.1 Determination rules for object reclamation

Garbage collector in the garbage collection, the first need to determine which memory is to be reclaimed, which objects are “alive”, can not be reclaimed; Which objects are “dead” and need to be recycled.

Reference counting method

One way to determine whether an object is alive or not is “reference counting,” where an object is referenced once and the counter is incremented by one, and if the counter is zero, the object can be reclaimed. However, reference counting has a fatal flaw that it can’t solve the problem of loop dependency, so it is rarely adopted by mainstream virtual machines today.

Accessibility analysis algorithm

Reachability analysis algorithm is also called root search algorithm. The basic idea of the algorithm is to search down from a series of objects called “GC Roots” as the starting point, and the search path is called reference chain. When there is no reference chain between an object and GC Roots (unreachable), Prove that the object is not available, and it is judged to be recyclable.

Objects that can be used as GC Roots in Java include the following:

  • Objects referenced in the virtual machine stack (the local variable table in the stack frame);
  • The object referenced by the class static attribute in the method area;
  • The object referenced by the constant in the method area;
  • Objects referenced by JNI (Native methods) in the Native method stack.

There are four reference types in Java

Whether it is through reference counters or through reachable analysis to determine whether an object can be reclaimed or not is designed into the concept of “reference”. In Java, Reference types are divided into Strong Reference, Soft Reference, Weak Reference and Phantom Reference according to the strength of Reference relationship.

Strong references: Object obj = new Object() This approach is a strong reference, and as long as this strong reference exists, the garbage collector will never reclaim the referenced Object.

Soft references: Used to describe objects that are useful but not necessary. Before OOM, the garbage collector will include these soft-referenced objects in the collection scope for secondary collection. The OOM will be triggered if the memory is still out after this collection. Soft references are implemented in Java using the SoftReference class.

Weak references: Like soft references, they are used to describe non-essential objects, but they are weaker than soft references. Objects associated with weak references only survive until the next garbage collection occurs. When the garbage collector works, objects associated only with weak references are reclaimed regardless of whether there is currently enough memory. WeakReference class is used to achieve this in Java.

Virtual reference: is the weakest kind of reference relationship, whether an object has a virtual reference does not affect the lifetime of the object, also cannot obtain an object instance through virtual reference. The only reason an object uses a virtual reference is to receive a system notification when it is collected by the garbage collector. Use the PhantomReference class in Java to do this.

To be or not to be, that’s a question

The objects that are determined to be unreachable in the accessibility analysis are not necessarily “inevitable”. At this point, they are in a “reprieve” phase, and it takes at least two tagging sessions to actually declare an object dead:

First mark: If an object is determined to be unreachable after a reachability analysis, it is marked for the first time and filtered once. The filter is whether it is necessary for this object to execute the Finalize () method. If the object does not override the Finalize () method or the Finalize () method of the object has been called by the virtual machine, then it is deemed not necessary to execute.

Second flag: If it is determined that a Finalize () method is necessary, the object will be put into an F-queue, and a low-priority Finalizer thread automatically created by the virtual machine will execute the Finalize () method of the object later. However, the virtual machine does not promise to wait for the method to end, because if finalize() method of an object is time-consuming or an infinite loop occurs, other objects in the F-queue may be waiting forever, and even the whole memory reclamation system may crash. The Finalize () method is the last chance for objects to escape death. If objects want to save themselves in Finalize (), they just need to reconnect with the GC Roots reference chain. This will remove the “collection to be collected” on the second tag, and if the object has not escaped by this point, it will basically be collected.

Method area recovery

As described earlier, the method areas are divided into permanent generations in the HotSpot VIRTUAL machine. The Java Virtual Machine specification does not require method areas to implement garbage collection, and method area garbage collection is not cost-effective.

The garbage collection of the method area (permanent generation) mainly recycles two parts: discarded constants and useless classes.

The collection of deprecated constants is very similar to the collection of objects in the Java heap, which won’t be explained too much here.

Class collection conditions are more stringent. To determine if a class can be reclaimed, the following three criteria must be met:

  1. All instances of the class have been reclaimed;
  2. The ClassLoader that loaded the class has been reclaimed;
  3. The Class object of this Class is not referenced, and its methods cannot be accessed anywhere by reflection.

3.2 Garbage collection algorithm

Mark-clear algorithm

Like the name of the mark-clear algorithm, the algorithm is divided into two phases: mark and clear:

All marked objects are marked first, and all marked objects are reclaimed after marking is complete. Mark-clear algorithm is one of the most basic algorithms, and other subsequent algorithms are based on it based on the shortcomings of improvement. Its deficiency is reflected in two aspects: one is the efficiency problem, the efficiency of marking and clearing is not high; Second, space problem. After mark clearing, a large number of discontinuous memory fragments will be generated. Too much space fragment may lead to the allocation of large objects in the running process of the program in the future.

Replication algorithm

To solve the efficiency problem, a copy algorithm is developed, which splits memory into two equally sized memory regions. Use only one piece at a time. When this block is used up, the surviving objects are copied onto the other block, and the used memory space is cleaned up again. The advantage of this is that you don’t have to worry about memory fragmentation, it’s simple and efficient. However, this algorithm is also expensive, so the memory is reduced by half.

Nowadays, commercial virtual machines all use this algorithm to recover the new generation. In IBM’s research, 98% of the objects of the new generation are “live and die”, so it is not necessary to divide the space according to the 1:1 ratio, but to divide the memory into a large Eden space and two small Survivor Spaces. Use Eden and one of the pieces Survivor each time. When reclaiming, the surviving objects in Eden and Survivor are copied to another Survivor space at once, and Eden and the Survivor space that was just used are cleaned up. HotSpot defaults to an 8:1 ratio of Eden to Survivor, meaning that each new generation has 90% (80%+10%) of the memory available for the entire new generation, and only 10% is wasted. Of course, 98% of the recoverable objects are only the data in general scenarios. There is no way to ensure that no more than 10% of the objects survive after each collection. When Survivor space is insufficient, we need to rely on other memory (here refers to the old age) for allocation guarantee. If there is not enough space in another Survivor space to hold objects that survived the previous generation, these objects will enter the old age directly through the allocation guarantee mechanism.

Mark-collation algorithm

From the previous introduction of copy-collect algorithm, we know that it is not suitable for the memory area of the old age, where objects live for a long time, while the tag collation algorithm is more suitable for this scenario.

The mark-declutter algorithm uses the same marking process as the Mark-clean algorithm, but instead of cleaning up the recyclable objects directly, the next step is to move all surviving objects towards one end and then clean up memory directly beyond the end boundary.

Generation collection algorithm

Currently, garbage collection of commercial virtual machines adopts the “generational collection” algorithm. This algorithm has no new idea, but divides the memory into several blocks according to the different life cycle of objects. Typically, the Java heap is divided into the new generation and the old generation, so that the most appropriate collection algorithm can be adopted based on the characteristics of each generation.

In the new generation, a large number of objects are found dead and only a few survive in garbage collection, so the replication algorithm is selected, and only a small amount of the replication cost of the surviving objects can be collected.

In the old days, because the object has a high survival rate and there is no extra space to allocate it, it has to use the “mark-clean” or “mark-tidy” algorithm for recycling.

3.3 Memory Allocation and Reclaiming Policies

The so-called automatic memory management, ultimately to solve the two problems of memory allocation and memory reclamation. Earlier we talked about memory reclamation, so let’s talk about memory allocation.

Objects are usually allocated on the Java heap (and in some cases on the stack with the advent of virtual machine optimization, more on this later). Objects are mainly allocated in the Eden region of the new generation. If local thread caching is enabled, it is allocated on the TLAB according to thread priority. In rare cases, it will be allocated directly over the old age. In general, allocation rules are not 100% fixed, and the details depend on the combination of garbage collectors and the parameters of the virtual machine, but there are several “universal” rules for allocating memory for virtual machines:

Objects are allocated in Eden area first

In most cases, objects are allocated in the New Generation Eden area. When the Eden allocation does not have enough space to allocate, the virtual machine will issue a Minor GC. If there is still not enough space after this GC, the allocation guarantee mechanism is enabled to allocate memory in the old years.

Here we mention Minor GC, and if you look closely at the GC routine, we can usually see Major GC/Full GC in the log.

  • Minor GC refers to GC that occurs in the new generation, because Java objects are mostly ephemeral, and all Minor GC is very frequent and generally very fast;

  • Major /Full GC is a GC that occurs in an older era, and a Major GC is usually accompanied by at least one Minor GC. A Major GC is typically 10 times slower than a Minor GC.

Big object goes straight to the old age

Large objects are objects that require a large amount of contiguous memory space, and frequent occurrences of large objects can be fatal, causing GC to be triggered prematurely to obtain contiguous enough space to place new objects when there is not much left in memory.

As we mentioned earlier, the new generation uses a mark-sweep algorithm to handle garbage collection. If large objects are allocated directly in the new generation, a large amount of memory replication will occur between Eden and two Survivor regions. So large objects are allocated directly in the old age.

Long-lived objects will enter the old age

Virtual machines use the idea of generational collection to manage memory, so the memory collection must determine which objects should be placed in the new generation and which objects should be placed in the old age. Therefore, the virtual machine defines an object age counter for each object. If the object is born in Eden and can be accommodated by Survivor, it will be moved to Survivor, and the object age is set to 1. Each time an object “survives” a Minor GC in a Survivor zone, the age increases by 1, and when it reaches a certain age (15 by default), it is promoted to the old age.

Dynamic object age determination

In order to better adapt to the memory conditions of different applications, the virtual machine does not always require the age of objects to reach a fixed value (such as 15) to be advanced to the old age. Instead, it dynamically determines the age of objects. If the sum of all object sizes of the same age in the Survivor zone is greater than half of the Survivor space, objects older than or equal to that age can go straight to the old age.

Space allocation guarantee

After the minorGC is triggered by the new generation, if a large number of objects are still alive in Survivor, the older team is required to allocate guarantees, allowing the objects that cannot be held in Survivor to go straight to the old age.

Write in the last

For us Java programmers, the automatic memory management mechanism of virtual machines has brought great convenience to us in the process of coding, and we do not have to carefully manage the life cycle of each object like THE developers of C/C++ and other languages. But at the same time we also lost the management rights of memory control, once memory leakage occurs if we do not understand the principle of virtual machine memory management, it is very difficult to troubleshoot the problem. Hopefully this article has helped you understand the memory management mechanism of the Java virtual machine. If you want to learn more about Java Virtual machines, you are recommended to read Zhou Zhiming’s in-depth Understanding of Java Virtual Machines: Advanced JVM Features and Best Practices.

Ok, so much for automatic memory management for the Java virtual machine. In the next article, we will talk about “class file structure”.

References:

  • Understanding the Java Virtual Machine in Depth: Advanced JVM Features and Best Practices (version 2)

If you like my articles, follow my public account BaronTalk, my zhihu column or add a Star on GitHub.

  • Wechat official account: BaronTalk
  • Zhihu column: zhuanlan.zhihu.com/baron
  • GitHub:github.com/BaronZ88
  • Personal blog: Baronzhang.com