preface

Through the analysis of the previous several articles, I believe you have a clear understanding of class loading and its tightly bound method area, method area let’s end, we focus on the next storage area: heap

I’m sure you’ve all heard of this area before, so I’m going to start this article by looking at how objects are created and how they get into the heap

Object creation

The flow chart

The flow chart of an object is shown in the figure above, which can be roughly divided into the following five steps:

  1. Class checking mechanism
  2. Allocate memory
  3. Initialize the
  4. Set the object header
  5. Execute the init method

We first leave a general impression according to the flow chart, and then I will take you through HotSpot source code to strengthen the impression. Next, we will start from the class inspection mechanism, step by step analysis

Class checking mechanism

When a virtual machine receives 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 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

The new instruction corresponds to a variety of situations at the language level: new keywords, object cloning, object serialization, etc

example

Let’s take this code as an example:

public static void main(String[] args) {
        Math math = new Math();
        math.compute();
        System.out.println("Hello World");
}
Copy the code

Take a look at the output compiled by javAP instructions

#2 corresponding symbol reference

The byte dock line corresponding to the main method is the new instruction, and the #2 next to it is used to refer to the symbol reference in the static constant pool

The next check is to see if the Math class has been loaded, parsed, and initialized. If not, the appropriate class loading process must be performed first. Once the class checking mechanism is complete, the next step is to allocate memory.

Allocate memory

Next comes the phase of allocating memory. The JVM allocates memory for new objects. The size of the object’s memory needs can be fully determined after the class is loaded

As for this content, I will talk about it from the following three parts:

  1. Object memory allocationThe way of
  2. When allocating memoryConcurrency issues
  3. objectStorage placeWhere is the

The first content:Object memory allocationThe way of

We know that the heap is a whole block of memory, but an object only needs to occupy a very small part of it. But how does the JVM allocate a small chunk of memory to the object within the whole block

At present, there are two main ways to divide the memory in Java. According to whether the memory space in the heap is orderly, the allocation can be divided into the following two ways:

  • Pointer collisions – Assume that memory in the Java heap is perfectly tidy
  • Free list – Assume that the memory in the Java heap is not tidy

The first way: pointer collision

The Java heap is structured, with used memory on one side, free memory on the other, and a pointer in the middle as the dividing line. When a new object needs to allocate memory, it moves the pointer to the free space by the same size as the object

As shown in the figure above, a pointer divides used memory from free memory. When a new object needs to allocate memory, the pointer moves to the free area by the size of the new object

The second way: the free list

The memory in the Java heap is not neat, and used memory and free memory cross each other, so there is no way to simply collide because the area where the pointer moves is likely already in use

For this scenario, the virtual machine must maintain a list of memory blocks that are available, find a chunk of the list that is large enough to be allocated to the object instance, and update the list, which is the free list

How to choose the memory allocation mode

The choice of allocation method is determined by whether the Java heap is clean, which in turn is determined by whether the garbage collector’s collection algorithm adopted has spatial collation capabilities. Therefore, when using Serial, ParNew and other collectors with collation process, the allocation algorithm is pointer collision, simple and efficient, while when using CMS collectors based on the clearing algorithm, in theory, only a relatively complex free list can be used to allocate memory

I believe you in this passage

The second piece of content: when allocating memoryConcurrency issues

As mentioned in the first article in this series, the heap is shared by all threads. In A multi-threaded environment, concurrency issues are certainly involved. If the JVM is allocating memory to object A, thread A has not changed its pointer, and object B uses the original pointer to allocate memory at the same time, thread B, That is, different objects in different threads need to apply for the same memory space

Faced with this situation, the JVM provides two solutions, the first is CAS and the second is local Thread allocation caching (TLAB)

First solution: CAS (Compare and Swap)

The CENTRAL Authentication Service (CAS) configuration fails to retry to ensure atomicity of update operations

As an example, memory allocation for pointer collisions is a simulation of the CAS idea, not an implementation:

  • Step 1: Thread A arrives and needs to allocate memory to object A. Thread A gets the memory address pointed to by the current pointer
  • Step 2: Thread B also comes, needs to allocate memory to object B, thread B gets the same memory address
  • Step 3: Then thread A will update and find that the current pointer to the memory address is the same as the original memory address, so update the memory, change the current pointer to the memory address
  • Step 4: Then thread B will update and find that the current pointer to the memory address is not the same as the original memory address, so it will retry the step of failure

Second solution: Thread Local Allocation Buffer (TLAB)

The action of allocating memory is divided into different Spaces according to threads. That is, each thread pre-allocates a small block of memory in the Java heap, a unique block of memory for each thread. By default, this block is Eden %1

Note: TLAB only allocates objects when the operation is private to the thread; the allocated object is still readable to other threads.

Advantages: The advantage is that when allocating memory, there is no need to lock a whole block of memory, which is a way to exchange space for time

Set the TLAB
  • -XX:+/-UseTLABParameter to set whether the virtual machine uses TLAB,The default open
  • -XXTLABSizeSpecifies the TLAB size

Third piece of content: objectsStorage placeWhere is the

The following flow chart clearly describes the mental process of object allocation. We analyze it node by node, or judgment by judgment

First node: Stack allocation?

Not all objects are allocated directly to the heap in Java, because when you do, GC is very, very frequent, especially when creating objects in looping methods

For this scenario, the JVM takes advantage of the properties of the method to end the stack frame, freeing up stack frame space, and assigning eligible objects to the stack

Since to meet the conditions, then this condition has the following two points:

  1. First of all, this object should not take up a lot of space, the total stack frame size is only a little bit, beyond certainly not
  2. This object satisfies the requirements of object escape analysis

Object escape analysis

On the first point, which we’re sure you’ll understand quickly, let’s talk about object escape analysis

What is object escape analysis

Analyze the dynamic scope of an object. When an object is defined in a method, if it is referenced by an external method, the dynamic scope of the object is not only the method, then it is said to have escaped

An example of object escape analysis
public class ObjectEscapeTest {
    public User tes1(a) {
        User user = new User();
        user.setAge(1);
        user.setName("test1");
        / /... The subsequent operation
        return user;
    }
    public void test2(a) {
        User user = new User();
        user.setAge(2);
        user.setName("test2");
        / /... The subsequent operation}}public class User {
    private int age;
    private String name;
    // omit the get and set methods
}
Copy the code
  • First look at test1 method, in this method we have new User class object, at the end of the method, the object to return, used to call this method, according to the User object, we can say this object to escape its dynamic scope, it is the scope of this method, but due to finally returned to the object, So the scope of this object is no longer limited to this method, it has escaped.

  • In test2, we create an object of class User, but we don’t end up returning it as a value. That is, the object’s scope is always in the method, so it doesn’t “escape”.

The JVM uses escape analysis to determine that the object will not be accessed externally. If it does not escape, the object can be allocated on the stack so that the memory space occupied by the object can be released as the stack frame goes off, reducing the stress of GC

Setup escape analysis

JVM can optimize object memory location allocation by enabling escape analysis parameter -xx :+DoEscapeAnalysis so that it is allocated on the stack first by scalar substitution. After JDK7, escape analysis is enabled by default. To disable escape analysis, use -xx: -doEscapeAnalysis

So this brings up a new concept: a scalar substitution, and then what is this scalar substitution operation

Scalar replacement

When escape analysis first determines that the object cannot be accessed externally and that the object can be further decomposed, the JVM does not create the object, but instead decomposes the object’s member variables into a number of member variables used by this method, which allocate space on stack frames or registers. In this way, objects are not overallocated because there is no contiguous chunk of memory

Scalar substitution

Mainly considering the memory, we know that a stack frames have local variables, such as the operand stack some fixed memory area, so the rest of the memory space is not very likely a large continuous, but a small piece, a small piece of fragments of the memory, if you want to allocate objects in the stack, then consider how to put it in.

So the JVM comes up with A solution: if the object has A, B, and C member variables, and only A and B are used in this method, it will not create the object. Instead, it will store the A and B member variables used in this method in A stack frame or register. You then need to identify the member variables as belonging to the object.

Scalars and aggregates

Scalar is the quantity that cannot be further divided, Java basic data type is scalar, the opposite of scalar is aggregate quantity, indicating that the quantity can be further divided, and this quantity is called aggregate quantity, object is the aggregate quantity that can be further decomposed

How do I set scalar substitution

First of all, it’s important to make it clear that scalar substitution only works when escape analysis is turned on. If escape analysis is not turned on, scalar substitution is not turned on

Turn on the scalar substitution parameter: -xx :+EliminateAllocations, turned on by default after JDK 7

Escape analysis and scalar case analysis

public class AllotOnStack {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            alloc();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
    private static void alloc(a) {
        User user = new User();
        user.setAge(3);
        user.setName("alloc"); }}Copy the code

Case study: In main, loop 100 million times, call alloc. In alloc, we create the User object, but the User object does not escape the scope of the method, because loop 100 million times, create 100 million User objects

Verify the first point: Not all objects are immediately created and stored in the heap

Adjust JVM parameters (reduce memory and PrintGC logs) to run: -xmx15m -xms15m -xx :+PrintGC

If all objects were created on the heap, there would be a lot of GC going on, but we found that there was no GC going on at all, so it turns out that not all objects are put directly into the heap as soon as they are created

Verify the second point: Do not turn on escape analysis, but turn on scalar substitution

Adjust JVM parameters to run: -XMx15m -xMS15m -xx: -doescapeAnalysis -xx :+PrintGC -xx :+EliminateAllocations

Here I truncated the section and found that a large number of GCS occurred, indicating that single scalar substitution is useless, scalar substitution can only work if escape analysis is enabled

Verify point 3: Turn on escape analysis, but not scalar substitution

Adjust JVM parameters to run: -XMx15m -xMS15m -xx :+DoEscapeAnalysis -xx :+PrintGC -xx: -ELIMinateAllocations

Here, I also truncated part and found that a large number of GC occurred, indicating that only opening escape analysis can not achieve great optimization efficiency. Scalar replacement must also be enabled to achieve optimization effect

Second node: large object?

When the stack fails, I go to the heap to allocate memory, and on the way to the heap to allocate memory, I encounter a second fork to determine whether it is a large object

According to the flow chart of object creation, judge if it is a large object, then go straight to the old age, if it is not a large object, then perform local thread allocation buffer (TLAB).

What TLAB does is allocate a block of memory in Eden for the thread to use, so whether the thread allocates memory through TLAB or not, the object will end up in Eden

Create large objects directly into old age cases

The JVM has this parameter by default: -xx :+UseAdaptiveSizePolicy This parameter is enabled by default, which automatically changes the 8:1:1 ratio. If you do not want this ratio to change, you can use -xx: -useadaptivesizePolicy

Let’s take a look at the memory footprint of each part of the heap after creation

/** * Add JVM running parameters: -xx :+PrintGCDetails Prints GC log details */
public class GCTest {
    public static void main(String[] args) {
        byte[] allocation = new byte[29500 * 1024]; }}Copy the code

Add -xx :+PrintGCDetails and run it

Analyze the output

  1. An object of about 29500K was created in the main method, to be sure, this objectIt won't fit in the stack frame spaceSo it’s stored in the heap
  2. The whole young generation occupies 38400K space, among which Eden district occupies 33280K space, FROM (survivor1) occupies 5120K space, to (survivor2) occupies 5120K space, about 8:1:1 ratio
  3. The usage of the Eden area has reached 100%, and the usage of the FROM (Survivor1) and to (Survivor2) zones is 0

Obviously, Eden is already full. Let’s put another big object and see what happens

public class GCTest {
    public static void main(String[] args) {
        byte[] allocation = new byte[29500 * 1024];
        byte[] allocation1 = new byte[8000 * 1024]; }}Copy the code

Go ahead and see the output

Heap
 PSYoungGen      total 38400K, used 9125K [0x00000000d5b80000.0x00000000da680000.0x0000000100000000)
  eden space 33280K, 25% used [0x00000000d5b80000.0x00000000d63a34b8.0x00000000d7c00000)
  from space 5120K, 15% used [0x00000000d7c00000.0x00000000d7cc6030.0x00000000d8100000)
  to   space 5120K, 0% used [0x00000000da180000.0x00000000da180000.0x00000000da680000)
 ParOldGen       total 87552K, used 29508K [0x0000000081200000.0x0000000086780000.0x00000000d5b80000)
  object space 87552K, 33% used [0x0000000081200000.0x0000000082ed1010.0x0000000086780000)
 Metaspace       used 3237K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 352K.capacity 388K.committed 512K.reserved 1048576K
Copy the code

After analyzing the output, the following analysis results can be obtained

  • Step 1: Trigger when Eden has no more spaceMinor GC, so it will send the object at 29500K toSurvivor areaput
  • Step 2: Since the survivor area is only 5120K, it will definitely not fit, so it will be put in the old age, so it will be foundIn the old days it was 33%And the restThe from (survivor1)There are someObjects within the JVMWe don’t have to put these objects in the old age yet, so we see thatThe from (survivor1)At the same time, the newly created object is placed in Eden, and Eden has a 25% utilization rate

Let’s create four more objects, let’s run them and see

public class GCTest {
    public static void main(String[] args) {
        byte[] allocation =  new byte[29500 * 1024];
        byte[] allocation1 = new byte[8000 * 1024];
        byte[] allocation2 = new byte[1000 * 1024];
        byte[] allocation3 = new byte[1000 * 1024];
        byte[] allocation4 = new byte[1000 * 1024];
        byte[] allocation5 = new byte[1000 * 1024]; }}Copy the code

Output result:

Heap
 PSYoungGen      total 38400K, used 13786K [0x00000000d5b80000.0x00000000da680000.0x0000000100000000)
  eden space 33280K, 38% used [0x00000000d5b80000.0x00000000d682ca10.0x00000000d7c00000)
  from space 5120K, 15% used [0x00000000d7c00000.0x00000000d7cca020.0x00000000d8100000)
  to   space 5120K, 0% used [0x00000000da180000.0x00000000da180000.0x00000000da680000)
 ParOldGen       total 87552K, used 29508K [0x0000000081200000.0x0000000086780000.0x00000000d5b80000)
  object space 87552K, 33% used [0x0000000081200000.0x0000000082ed1010.0x0000000086780000)
 Metaspace       used 3239K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 352K.capacity 388K.committed 512K.reserved 1048576K
Copy the code

Analyze the output:

It is found that newly created objects continue to be put into Eden area, and the utilization rate increases from 25% to 38%. The utilization rate of from area and old age remains unchanged, indicating that GC has not occurred and Eden area has enough memory space

How to determine whether it is a large object

According to the case, if not set, the object that exceeds the size of the remaining memory in Eden area is a large object

How to set the size configuration of large objects

JVM parameters: – XX: PretenureSizeThreshold can set the size of large object

If an object exceeds the set size, it goes directly to the old generation, not to the young generation. Note: This parameter is valid only for Serial and ParNew collectors

Example of setting large object size

public class GCTest {
    public static void main(String[] args) {
        byte[] allocation1 = new byte[8000 * 1024]; }}Copy the code

Use – XX: + PrintGCDetails – XX: PretenureSizeThreshold = 1000 – XX: UseSerialGC this parameter, found 8000 k object directly into the old s parameters are proved effective

Large objects directly into the old age advantage

In order to avoid the efficiency of copying operations when allocating memory for large objects, the young generation GC uses the tag copying algorithm. If large objects are stored in the young generation, the replication time will be very expensive. And since Minor GC is a more frequent GC, the throughput needs to be guaranteed

Third node: The object moves from the young generation to the old generation

If an object is not a large object, it must enter the old age from the young generation. There are many ways for an object to enter the old age from the young generation, including generational age judgment, dynamic age judgment and the guarantee mechanism of old age spatial allocation. Let’s look at the most common generational age judgment:

The first way: the long-lived objects will enter the old age

The JVM uses the idea of generational collection to manage memory, so memory collection must be able to identify which objects should be placed in the new generation and which objects should be placed in the old generation. To do this, the JVM assigns a generational age to each object.

If an object is born in Eden and survives the first Minor GC and is accommodated by survivor, it is moved to survivor and its generation age is set to 1

Every time an object survives a Minor GC in a survivor zone, the generational age is +1, and when it reaches a point where the default age is 15, the CMS collector is 6 years old, and different garbage collectors are slightly different, the object goes into the old age

Set the generation age threshold

– XX: MaxTenuringThreshold to set

The second way: object dynamic age judgment

If the total size of a batch of objects in the survivor region is greater than 50% of the memory size of the survivor region (configurable), the objects that are greater than or equal to the maximum generation age of the batch of objects can enter the old age directly

For example: if the sum of a group of objects (generational age 1+ age 2+ age N) exceeds 50% of the survivor zone, all objects with generational age n or older are placed in the old age

Examples of real-world scenarios

According to the traffic peak, the interface with the most frequent call is evaluated, and it is estimated that about 60M objects are generated per second. The process is shown as follows:

Assuming that the physical machine has 8GB of memory, we allocate memory for each region of the heap based on the physical machine size and the following JVM parameters

java -Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -jar xx.jar
Copy the code

  • In practice, objects generated in the first 13 seconds can be placed in Eden. However, at the 14th second, the JVM finds that Eden is already full, so it STW (Stop the world) and MinorGC
  • Assume that the object created at 14 seconds has not yet exited the stack, so it cannot be removedMinor GC recycling, so the 60M objects generated in the 14th second will be placed in the survivor zone. The generation age is set to 1, but this batch of 60M objects has exceeded50% of the memory space in survivor areaAny object whose age is greater than or equal to 1 will be moved to the old age

Note: The Minor GC has already executed when the survivor object is moved to the old age

Case summary

In this case, 60 megabytes of objects are put into the old age every 14 seconds, and within a few minutes, the memory space of the old age will be filled, and a full GC will be performed in a few minutes

This type of garbage collection frequently generates full GC because of invalid garbage objects.

The symptom of the problem is that the survivor area memory space is too small, resulting in the object dynamic age judgment mechanism directly into the old age, so the most direct method is to expand the memory space of the new generation

We allocate memory space according to the following JVM parameters and increase the space for the new generation, so that in a 8:1:1 ratio, survivor is over 200 M, and 60M is not put directly into the old age. After a MinorGC, these garbage objects are collected and not put into the old age, which greatly reduces the Full GC

java -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar xx.jar
Copy the code
Set dynamic age judgment size

-xx :TargetSurvivorRatio Indicates the dynamic age determination size

Object the purpose of dynamic age determination mechanism

Hopefully those objects that may be long-term survival, as soon as possible into the old age.

Note: The dynamic age determination mechanism for objects is usually triggered after the Minor GC

Third way: old chronospatial allocation guarantee mechanism

Before each Minor GC, the JVM calculates the free space of the old generation. If the free space is less than the sum of all the existing objects in the young generation, it looks at a -xx: -handlePromotionFailure JDK 1.8 default. This parameter is a guarantee that if it is less than the historical average, Full GC will not occur

If this parameter is present, it looks to see if the available memory size of the old age is greater than the average size of objects that entered the old age after each previous Minor GC

  • If it is smaller than or the parameter is not set, a Full GC will be triggered, which will recycle both old and new generations. If there is not enough space to store new objects, an OOM will be generated

  • If the value is larger than the value previously set, a Minor GC is performed. Of course, if the size of the remaining objects that need to be moved to the old age after the Minor GC is still larger than the available space, then a Full GC is also triggered. If there is still no space left after the Full GC, OOM will also occur

The complete flow chart is shown below:

The purpose of the design of the old era space allocation guarantee mechanism:

Before each MinorGC, determine if there is a high probability that a Full GC will occur, and then execute Full GC without MinorGC to avoid the first MinorGC in the flowchart, even though a MinorGC will be performed after Full GC. But the Minor GC will be significantly less stressful this time around

Initialize the

After memory allocation is complete, the virtual machine needs to initialize the allocated memory space to zero, excluding the object header. If TLAB is used, this process can also be advanced to TLAB allocation time

The effect of this step is to ensure that the instance fields of the object can be used in Java code without assigning initial values, and that the program can access the zero values corresponding to the data types of these fields

public class Student {
   public int age = 100;
}
Copy the code

At the initialization step, the age variable is set to 0, the base type is set to default, and the reference type is set to NULL

Set the object header

In the HotSpot VIRTUAL machine, the layout that objects can store in memory can be divided into three areas: Object Header, Instance Data, and alignment Padding, as shown in the figure below

After the zero value is initialized, the JVM performs the necessary Settings on the object, such as which class instance the object is, if any, to find the metadata information of the class, the hash code of the object, and the GC generation age of the object. This information is stored in the object header of the object, which we’ll talk more about next

Talk about object headers

The object can be subdivided into three regions: Mark Word, type pointer, and array length

  1. The first area is the Mark Word, which is used to store the runtime data of the object itself, hashCode, GC generation age, lock status flags, thread held locks, bias thread IDS, bias timestamps, and so on

  2. The second area is a type pointer, a pointer to its class metadata, that the virtual machine uses to determine which class the object is an instance of.

  3. The third area is the length of the array. If the object is of array type, there is also an array length in the object header, which takes up 4 bytes

Area 1: Mark Word (32-bit as an example)

Recorded under different lock state own runtime data, for example, we just came out of the new is unlocked state, so in a 32-bit operating system, no lock state of the object in the top 25 record is the object’s hashCode, object is the four records in the middle of a generational age, after one records are biased locking, the last two records is lock symbol

Lock status and lock flag bit

I’d like to cover lock states and lock flag bits in detail in a series of articles on multithreading, focusing first on generational age

Generational age

The generation age can take up only 4 bits in the object header. 4 bits in decimal is 2 ^ 4 minus 1=15, so the maximum generation age can be 15

The state of the object lock changed from no lock -> biased lock -> lightweight lock, and the generation age was lost.

In fact, generational age does not disappear, but is copied into other objects, please click here

Block 2: Pointer of type Klass Pointer

The JVM loads the bytecode of a class into the method area and stores it in the method area as the class meta information. This type pointer is used to point to the class meta information

Examples of real-world scenarios

Using the following Math class as an example, we create an object of the Math class in the main method

public static void main(String[] args) {
    Math math = new Math();
    math.compute();
    System.out.println("Hello World");
}
Copy the code

As shown in the figure below, the Math object has type Pointers to the Math class meta-information in the method area in its object header

For those of you who have learned reflection, you probably know the mathClass object in the figure below. What is the difference between this class object and the class metacomponent?

The class object is stored in a heap, because some static variables and methods in the class such as meta information are stored in the method, in order to let developers access to the method in the information below, the JVM provides such a class object, convenient access to information, the JVM’s own internal use the pointer on the surface of the head or object type

The length of the array

If the object is of array type, there is also an array length in the object header, which takes up 4 bytes

View object header

Now that we’re done with the conceptual components of an object header, let’s take a practical example. What does an object header look like

  1. Let’s start by importing the following package in our project
<! -- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
Copy the code
  1. Write the test code and look at the object header
public class ObjectTest {
    public static void main(String[] args) {
        ClassLayout classLayout = ClassLayout.parseInstance(new Object());
        System.out.println(classLayout.toPrintable());

        System.out.println();
        ClassLayout classLayout1 = ClassLayout.parseInstance(new int[] {}); System.out.println(classLayout1.toPrintable()); System.out.println(); ClassLayout classLayout2 = ClassLayout.parseInstance(new A());
        System.out.println(classLayout2.toPrintable());
    }
    public static class A {
        int id;
        String name;
        byteb; Object object; }}Copy the code
  1. Execute main to view the output. My computer is 64-bit
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           6d 01 00 20 (01101101 00000001 00000000 00100000) (536871277)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
     16     0    int [I.<elements>                             N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total


com.test.ObjectTest$A object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           6a ce 00 20 (01101010 11001110 00000000 00100000) (536923754)
     12     4                int A.id                                      0
     16     1               byte A.b                                       0
     17     3                    (alignment/padding gap)                  
     20     4   java.lang.String A.name                                    null
     24     4   java.lang.Object A.object                                  null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
Copy the code

Test example: Print the headers of three objects: Object, array of type int, and A with member variables.

Object

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Copy the code
  • OFFSET: indicates the start OFFSET position
  • SIZE: indicates the offset, in bytes
  • DESCRIPTION: The object header is the object header
  • Value: the Value

As mentioned above, on 64-bit machines, Mark Word takes up 8 bytes in the entire object header, with the first 2 lines being Mark Word

On a 64-bit machine, four bytes are allocated, so the third line represents the Pointer to the type. But there is an important point here. The Pointer to Object is eight bytes, so why is it showing four bytes? This will be discussed in more detail below

Object is not an array, so there is no array length, but what does the last line mean? Let’s focus on the last word alignment, which means complement

Remember the figure head of this object, the object in the last one piece of content is aligned to fill, the purpose is to ensure that the object is 8 bytes integer times, it is because after a lot of validation, when the head is 8 integer times the size of the object, the object of addressing and access efficiency is the highest, so in order to supplement the object, the JVM automatically fill the size of 4 bytes

The final total is 16 bytes, matching multiples of 8

Int array object

[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           6d 01 00 20 (01101101 00000001 00000000 00100000) (536871277)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
     16     0    int [I.<elements>                             N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Copy the code

The first three lines are the same as the Object. The first eight bytes are Mark words and the next four bytes are type Pointers, but since they are array types, four bytes are allocated to the length of the array

That adds up to 16 bytes, which is a multiple of 8, so you don’t need objects to complete it

A object

com.test.ObjectTest$A object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           6a ce 00 20 (01101010 11001110 00000000 00100000) (536923754)
     12     4                int A.id                                      0
     16     1               byte A.b                                       0
     17     3                    (alignment/padding gap)                  
     20     4   java.lang.String A.name                                    null
     24     4   java.lang.Object A.object                                  null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
Copy the code

In the A object, the first three rows are still the familiar Mark Word and type pointer, but from the fourth row on, it is the part of our instance data

  • Int id takes 4 bytes and byte B takes 1 byte. Note that there is also auto-completion inside the variable for the same purpose: to address and access objects

  • The next type String of 4 bytes, Object type is a reference type, according to the definition of 8 bytes, why there is 4 bytes or involved pointer to compress the content, will introduce a pointer compression, a total of all the above to add up to 28 bytes, do not conform to the multiples of 8, so finally completion 4 bytes, That’s 32 bytes

Pointer to the compressed

When we look at the Object header, we find that the size of the pointer to String should be 8 bytes, which prints out to 4 bytes, and the size of the pointer to Object is 8 bytes, which eventually becomes 4 bytes. This is involved in pointer compression. The JVM supports pointer compression

The JVM configures pointer compression parameters
  • Enable the compression pointer: -xx :+UseCompressedOops
  • Disable pointer compression: -xx: -usecompressedoops, compress all Pointers, type Pointers and object Pointers
  • Only compression head: the type of the pointer object – XX: + UseCompressedClassPointers
Disable the pointer compression case

With pointer compression disabled, look at the Object header of the Object:

  • The type pointer, which used to be four bytes, or one line, now has two lines, or eight bytes
  • The original String variable is 8 bytes, and the original Object variable is 8 bytes, indicating that pointer compression is disabled
Purpose of pointer compression
  1. In a 64 – bit operating system, the HotSpot using 32-bit pointer (the actual storage with 64 – bit), memory usage will be more than 1.5 times the left and right sides, using large pointer to move data between main memory and cache, large bandwidth, and GC can endure great pressure, in order to reduce the consumption of memory, a 64 – bit platform enable pointer compression
  2. The JVM can be optimized to support more memory configurations using only 32-bit addresses (heap memory less than or equal to 32GB) by compressing and encoding object Pointers into heap memory and decoding them out of CPU registers.
  3. When the heap is less than 4G, pointer compression does not need to be enabled, and the JVM automatically removes the high 32-bit addresses, that is, the low virtual address space
  4. When the heap is larger than 32GB, pointer compression fails and 64-bit (8 bytes) is forced to address Java objects, which leads to the first problem, so it is best not to be larger than 32GB

On the first point, here’s an explanation:

Studied the principle of computer students all know, in a 32-bit operating system, memory is the largest 2 32, 4 g, 64, then the corresponding theoretical maximum is 2 to 64, this calculates down is a T a unit of memory space, but we are on the market popular is 8 g, 16 g, that is to say, in general, So if you’re on a 64-bit operating system, an object pointer is 35 bits long, and the remaining 29 bits might not fit into an object. So the JVM uses pointer compression. For more than 32 bits of the object pointer into the heap memory compression coding, compression to 32 bits, out of the CPU register after decoding mode for recovery, because the register is still running on the address of 35 bits, after doing this, 64 bits can put down two object Pointers, greatly saving memory space

Execute the init method

After performing the above steps, a new object has been created from the virtual machine’s perspective. But from a Java program’s point of view, object creation has only just begun — the constructor, the init method in the Class file, has not yet been executed, and all fields have default zero values.

In general, the new instruction is followed by the Invokespecial instruction. The Java compiler generates both bytecode instructions, new and Invokespecial, where the new keyword is encountered, but not necessarily if it is generated directly by other means

The init method initializes the object as the programmer wishes, so that a usable object is fully constructed

See the Java object creation process through HotSpot source code

To review the whole process of object creation from source code, the following code is used as an example

public class Math {
    public static void main(String[] args) {
        Math math = new Math();
        math.say();
    }
    private void say(a) {
        System.out.println("Hello World!"); }}Copy the code

The javap -v math. class command decompiles the bytecode to see the structure, looking directly at the bytecode where the main method is located, and omits all unnecessary code. The result is as follows:

  public static void main(java.lang.String[]);
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class com/project/mall/jvm/Math
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method say:()V
        12: return

Copy the code

HotSpot uses the new instruction to create Math objects. The next section describes how HotSpot handles the new instruction

Java bytecode is interpretive execution, if the current is to explain, implement new execution is actually going to/hotspot/SRC/CPU/x86 / vm/templateTable_x86_64 TemplateTable: in the CPP file: _new method to generate a machine code, Is to find the corresponding method template to execute, let’s analyze this template step by step

TemplateTable::_new


void TemplateTable::_new() {
  // Top of stack cache validation
  transition(vtos, atos);
  / / call InterpreterMacroAssembler: : get_unsigned_2_byte_index_at_bcp () method to load the new instruction operand, for example above, the value is a constant pool of subscript 2 index
  __ get_unsigned_2_byte_index_at_bcp(rdx, 1);
  Label slow_case;
  Label done;
  Label initialize_header;
  Label initialize_object;
  Label allocate_shared;
  // call get_cpool_and_tags() to get the first address of the constant pool and put it in the RCX register
  __ get_cpool_and_tags(rsi, rax);
  // Make sure the class is in the constant pool
  const int tags_offset = Array<u1>::base_offset_in_bytes();
  __ cmpb(Address(rax, rdx, Address::times_1, tags_offset),
          JVM_CONSTANT_Class);
  __ jcc(Assembler::notEqual, slow_case);

  // Get the address of the class to which the created object belongs, place it in the RCX register, the runtime data structure of the class InstanceKlass, and push it into the RCX register. This InstanceKlass is the type pointer mentioned above
  __ movptr(rsi, Address(rsi, rdx, Address::times_8, sizeof(ConstantPool)));

  // Check whether the class has been initialized. If not, jump to slow_close for slow allocation.
  // If an object belongs to a class that has already been initialized, quick allocation is entered
  __ cmpb(Address(rsi, InstanceKlass::init_state_offset()),
          InstanceKlass::fully_initialized);
  __ jcc(Assembler::notEqual, slow_case);

  // The RCX register stores the memory address of class InstanceKlass, and uses the offset to obtain the size of class object and store it in RDX register
  __ movl(rdx, Address(rsi, Klass::layout_helper_offset()));
  __ testl(rdx, Klass::_lh_instance_slow_path_bit);
  __ jcc(Assembler::notZero, slow_case);

  constbool allow_shared_alloc = Universe::heap()->supports_inline_contig_alloc() && ! CMSIncrementalMode;// When the size of the created object is calculated, the memory allocation can be performed. By default, UseTLAB is true, that is, TLAB is used
  if (UseTLAB) {
    // Get the first address of the remaining TLAB space and place it in %rax
    __ movptr(rax, Address(r15_thread, in_bytes(JavaThread::tlab_top_offset())));
    
    // % RDX save the size of the object, according to the TLAB free area address can be calculated after the end of the object allocation, and then put in % RBX
    __ lea(rbx, Address(rax, rdx, Address::times_1));
    
    // Compare the end address of the object in % RBX with the end address of the TLAB free area
    __ cmpptr(rbx, Address(r15_thread, in_bytes(JavaThread::tlab_end_offset())));
    
    // If % RBX is the end address of the TLAB free area, it indicates that the TLAB free area is not large enough to allocate the object.
    // Jump to allocate_shared in allow_shareD_alloc, otherwise jump to slow_case
    __ jcc(Assembler::above, allow_shared_alloc ? allocate_shared : slow_case);
    
    // There is enough space in TLAB to allocate objects
    // After the object is allocated, the first address of the TLAB free area is updated to the last address after the object is allocated
    __ movptr(Address(r15_thread, in_bytes(JavaThread::tlab_top_offset())), rbx);
    
    // If TLAB defaults to clearing free space, then there is no need to clear object variables.
    // Jump directly to the initialization of the object header
    if (ZeroTLAB) {
      // the fields have been already cleared
      __ jmp(initialize_header);
    } else {
      // initialize both the header and fields
      __ jmp(initialize_object); }}// If the allocation fails in TLAB, it will be allocated in Eden directly
  if (allow_shared_alloc) {
  
    // TLAB allocation failure will jump here
    __ bind(allocate_shared);

    // Obtain the start and end addresses of the remaining space in Eden
    ExternalAddress top((address)Universe::heap(a)->top_addr(a));
    ExternalAddress end((address)Universe::heap(a)->end_addr(a));

    const Register RtopAddr = rscratch1;
    const Register RendAddr = rscratch2;

    __ lea(RtopAddr, top);
    __ lea(RendAddr, end);
    
    // Place the first address of the Eden free area in the RAX register
    __ movptr(rax, Address(RtopAddr, 0));

    Label retry;
    __ bind(retry);
    
    // Calculate the end address of the object and compare it with the end address of the free area.
    __ lea(rbx, Address(rax, rdx, Address::times_1));
    __ cmpptr(rbx, Address(RendAddr, 0));
    __ jcc(Assembler::above, slow_case);

    // rax: object begin RAx The first address of the memory allocated by the object is recorded
    // RBX: object end RBX
    // RDX: instance size in bytes RDX records the object size
    if (os::is_MP()) {
      __ lock(a);
    }
    // Use CAS to update the start address of Eden's free space to the end address of the object.
    __ cmpxchgptr(rbx, Address(RtopAddr, 0));

    __ jcc(Assembler::notEqual, retry);

    __ incr_allocated_bytes(r15_thread, rdx, 0);
  }
  
  // After the memory required for the object is allocated, the object is initialized
  if (UseTLAB || Universe::heap()->supports_inline_contig_alloc()) {
    // The object is initialized before the header. If the object size is
    // zero, go directly to the header initialization.
    __ bind(initialize_object);
    
    // if RDX is the same size as sizeof(oopDesc), that is, the sizeof the object is the same as the sizeof the object header,
    // The memory of the real instance data of the object is 0, and there is no need to initialize the instance data of the object.
    // Jump directly to the initialization of the object header. In Hotspot, although object headers are placed in memory before object instance data,
    // However, the object instance data is initialized first and then the object header is initialized.
    __ decrementl(rdx, sizeof(oopDesc));
    __ jcc(Assembler::zero, initialize_header);

    // Initialize object fields
    // Perform xor to set RCX to 0 in preparation for assigning the object variable a value of zero later
    __ xorl(rcx, rcx); // use zero reg to clear memory (shorter code)
    __ shrl(rdx, LogBytesPerLong);  // divide by oopSize to simplify the loop
    {
      // The size of the object is RDX, and the memory is initialized to zero
      // Rax stores the first address of the object
      Label loop;
      __ bind(loop);
      __ movq(Address(rax, rdx, Address::times_8, sizeof(oopDesc) - oopSize),
              rcx);
      __ decrementl(rdx);
      __ jcc(Assembler::notZero, loop);
    }

    // initialize object header only.
    After initializing the object instance data, initialize the object header (mark and metadata attributes in oop).
    __ bind(initialize_header);
    // Whether to use biased lock, most of the time an object is only accessed by the same thread, so the object header records the id of the thread that acquired the lock.
    // The next time the thread acquires the lock, it does not need to be locked.
    if (UseBiasedLocking) {
       // Move the biased locking data of the class to the object header
      // Rax stores the first address of the object
      __ movptr(rscratch1, Address(rsi, Klass::prototype_header_offset()));
      __ movptr(Address(rax, oopDesc::mark_offset_in_bytes()), rscratch1);
    } else {
      __ movptr(Address(rax, oopDesc::mark_offset_in_bytes()),
               (intptr_t) markOopDesc::prototype(a)); // header (address 0x1)
    }
    // RCX saves InstanceKlass, rax saves the first address of the object, where the data of the class to which the object belongs is stored.
    The _metadata property in object OOP stores a pointer to the class InstanceKlass that the object belongs to
    __ xorl(rcx, rcx); // use zero reg to clear memory (shorter code)
    __ store_klass_gap(rax, rcx);  // zero klass gap for compressed oops
    __ store_klass(rax, rsi);      // store klass last

    {
      SkipIfEqual skip(_masm, &DTraceAllocProbes, false);
      // Trigger dtrace event for fastpath
      __ push(atos); // save the return value
      __ call_VM_leaf( CAST_FROM_FN_PTR(address, SharedRuntime::dtrace_object_alloc), rax);
      __ pop(atos); // restore the return value

    }
    __ jmp(done);
  }

  // If allocation cannot be made in TLAB and Eden, proceed with the following steps

  // Slow allocation, if the class has not been initialized, will skip to this point
  __ bind(slow_case);
  // Get the first address of the constant pool and store it in register rarg1
  __ get_constant_pool(c_rarg1);
  // Get the operand after the new instruction, that is, the index of the class in the constant pool, into the rarg2 register
  __ get_unsigned_2_byte_index_at_bcp(c_rarg2, 1);
  // Call the InterpreterRuntime::_new() function to allocate object memory
  call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), c_rarg1, c_rarg2);
  __ verify_oop(rax);

  // continue
  __ bind(done);
}
Copy the code

InterpreterRuntime::_new

If the allocation cannot be made in TLAB and Eden, the allocation is called in the InterpreterRuntime::_new() function

IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))
  Klass* k_oop = pool->klass_at(index, CHECK);
  instanceKlassHandle klass (THREAD, k_oop);

  // Make sure we are not instantiating an abstract klass
  klass->check_valid_for_instantiation(true, CHECK);

  // Make sure klass is initialized
  klass->initialize(CHECK);

  // Load the class and allocate the object, and return the address of the allocated object
  oop obj = klass->allocate_instance(CHECK);
  thread->set_vm_result(obj);
IRT_END


// Load the class and allocate the object, and return the address of the allocated object
instanceOop instanceKlass::allocate_instance(TRAPS) {
  assert(! oop_is_instanceMirror(),"wrong allocation path");
  // Whether to override the Finalize () method
  bool has_finalizer_flag = has_finalizer(); // Query before possible GC
  // The size of the allocated object
  int size = size_helper();  // Query before forming handle.
  KlassHandle h_k(THREAD, as_klassOop());
  instanceOop i;
  // Assign objects
  i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
  if(has_finalizer_flag && ! RegisterFinalizersAtInit) { i = register_finalizer(i, CHECK_NULL); }return i;
}
Copy the code

The code fragment above is new instructions complete process, according to the source code analysis down and flow chart also corresponding to the at the beginning, but the lack of the initialization steps of the last step class loading, this is because this step is invokespecial instruction should do, there is no longer continue to expand, can anyone interested in the study

This paper summarizes

At this point, the path of object creation has been completed, which is the whole content of this article. The core is around the object creation process. Let’s review again:

  1. The class checking mechanism phase checks whether the class represented by the symbolic reference has been loaded, parsed, and initialized. If not, the corresponding class loading process must be performed first
  2. In the memory allocation stage, escape analysis, scalar replacement, large object, dynamic age judgment mechanism, old age guarantee mechanism and other important knowledge points are involved. This step is also the most complicated step
  3. Assigns a zero value to the memory space during initialization
  4. In the stage of setting the object head, pointer compression, automatic completion and other knowledge points are involved
  5. In the init method stage, the constructor of the class is executed

The birth of the object is finished, from the beginning of the next chapter, began to analyze the object recycling, began to introduce garbage collection algorithm, garbage collector and so on related knowledge points

omg

Finally, if you feel confused about the article, please leave a comment immediately. If you think I have something to ask for a thumbs up 👍 for attention ❤️ for share 👥 is really very useful for me!! If you want to get massive Java resources easy to use IDEA plug-ins, resume templates, design patterns, multi-threading, architecture, programming style, middleware…… , can pay attention to the micro channel public number Java encyclopedia, the last of the last, thank you to see the support of the officer!!