[jvm series-06] In-depth understanding of object instantiation, memory layout and access positioning

JVM series overall column


content link address
[1] Getting to know virtual machines and java virtual machines https://blog.csdn.net/zhenghuishengq/article/details/129544460
[2] The class loading subsystem of jvm and the basic use of jclasslib https://blog.csdn.net/zhenghuishengq/article/details/129610963
[3] The virtual machine stack, program counter, and local method stack of the private area at runtime https://blog.csdn.net/zhenghuishengq/article/details/129684076
[4] Heap and escape analysis of the shared area of ​​the data area at runtime https://blog.csdn.net/zhenghuishengq/article/details/129796509
[5] The method area and constant pool of the runtime data area shared area https://blog.csdn.net/zhenghuishengq/article/details/129958466
[6] Object instantiation, memory layout and access positioning https://blog.csdn.net/zhenghuishengq/article/details/130057210

1. Object instantiation, memory layout and access positioning

1. Object instantiation

There are mainly the following ways to create objects and the steps to create objects
insert image description here

1.1, several ways to create objects

In daily development, there are mainly the following ways to create objects:

  • The most common way: new plus constructor, if the constructor is private, it can be accessed statically, such as singleton mode, or loaded through the factory
//new 构造器 创建对象
Object object = new Object();
//构造器静态私有,如典型的单例模式
Object object = Object.getObject()//工厂加载,SpringBean,SqlSessionBean
Object object = ObjectFactory.getObject();
  • Reflection method: newInstance of the class or newInstance of the constructor
public class Invoke {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            Class<?> clazz1 = Class.forName("com.tky.jvm.Invoke");
            //通过类构造器获取对象
            Constructor<?> constructor = clazz1.getConstructor();
            Invoke invoke1 = (Invoke)constructor.newInstance();
            //通过类名获取
            Class<Invoke> clazz2 = Invoke.class;
            Invoke invoke2 = clazz2.newInstance();
            //通过对象获取
            Invoke in = new Invoke();
            Class<? extends Invoke> clazz3 = in.getClass();
            Invoke invoke3 = clazz3.newInstance();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}
  • Cloning method: clone method, without calling any constructor, the current class needs to implement the Cloneable interface and the clone method
/**
 * @author zhenghuisheng
 * @date : 2023/4/10
 */
@Data
public class Clone implements Cloneable {
    
    
    private Long id;
    private String username;
    private String password;

    @Override
    protected Clone clone() throws CloneNotSupportedException {
    
    
        return (Clone)super.clone();
    }
}

class TestClone{
    
    
    public static void main(String[] args) {
    
    
        Clone clone1 = new Clone();
        clone1.setId(1L);
        clone1.setUsername("zhenghuisheng");
        clone1.setUsername("123456");
        try {
    
    
            Clone clone2 = clone1.clone();
            System.out.println(clone2.getId());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}
  • Deserialization method: get binary stream from file or network, convert binary stream into object
//对象序列化
Student s = new Student("1","zhenghuisheng","18");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/a.txt"));
objectOutputStream.writeObject(s);
objectOutputStream.close();

//对象反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
Student student = (Student) inputStream.readObject();
  • Third-party library Objenesis
//构建 Objenesis 对象  Objenesis需要对应的pom依赖对象
Objenesis objenesis = new ObjenesisStd();
ObjectInstantiator<Student> instantiator = objenesis.getInstantiatorOf(Student.class);
Student student = instantiator.newInstance();

1.2, the steps of object creation

Here we mainly analyze the steps of object creation from the perspective of execution. As shown in the figure above, it is mainly divided into six steps to create objects
insert image description here

1.2.1, determine whether the class corresponding to the object is loaded, verified, prepared, parsed and initialized

When the virtual machine encounters a new instruction, it will first check whether the parameters of this instruction can locate a class symbol in the constant pool of the metaspace, and check whether the class has undergone loading, verification, preparation, and parsing and initialize this few steps. If not, then the class loader is still in the mode of parental delegation, using the key of the current class loader ClassLoader + package + classto search for the corresponding .class file. If the corresponding file is not found, ClassNotFoundExceptionan exception will be thrown. If found, the Class loading, and generate the corresponding Class object

1.2.2, Open up space for objects and allocate memory

First, you need to calculate the size of the space occupied by the object, and then divide a piece of memory in the heap for the new object. If the instance member variable is a reference variable, then only allocate the reference variable space, which is four bytes in size. For example, according to the number of bytes occupied by different basic data types, we can know how much space each variable occupies, and finally add all the space required by these variables together to get the number of bytes in this total space.

And if the memory is regular, then the virtual machine will use pointer collision to allocate memory for the object. As shown in the figure below, the used memory is placed on one side, and the free memory is placed on the other side. There is an indicator of the dividing point in the middle. Memory allocation is to move the pointer to the free side. The moving distance is what the object needs. The size , and the pointer collision method depends on whether the garbage collection algorithm of the virtual machine has a compression function.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-mPmoI7G5-1681101135139)(img/1680846919641.png)]

If the internal memory is not regular, a list must be maintained inside the virtual machine to manage used memory and unused memory, which is called a free list . As shown in the figure below, a table is maintained inside the virtual machine, which records which memory is available and which memory is not available, and then when allocating, it finds a large enough space from the list to allocate to the object instance , and update the content on the table.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-enJCEOZ8-1681101135140)(img/1680847513344.png)]

1.2.3, dealing with concurrency issues

Since the object is created in the heap, and the heap is a shared area, this concurrency problem cannot be avoided. Inside the heap, two methods are mainly used to ensure the security of the instance.

One is to use the CAS comparison and exchange method, retry if it fails, and lock the area to ensure the atomicity of the update . The other is to pre-allocate a TLAB for each thread . It is mainly through these two methods to solve the problem of concurrency security.

1.2.4, object initial assignment

A default initialization is performed here . In this way, all properties have a default value to ensure that the object instance fields can be used when they are not assigned. Therefore, inside the method, the static variable is initially assigned in the preparation stage, and the instance variable is also initially assigned when the space is allocated, so these two variables can be used directly. If other variables are not explicitly initialized, then they will be A direct compilation failure occurs.

1.2.5, set the object header of the object

Store the class to which the object belongs, the hashCode of the object, GC information, age, lock information, etc. in the object header of the object.

1.2.6, execute the init method to initialize

Here is a display initialization , and the initialization work officially begins. Initialize member variables, execute the instantiation code block, call the constructor of the class, and assign the first address of the heap object to the reference object. Therefore, generally speaking, the new instruction will be followed by the execution method to initialize the object according to the programmer's wishes, so that the truly usable object is completely created.

2. The memory layout of the object

The memory layout of the object mainly includes the object header, instance data and its filling

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-ehB4t1wc-1681101135141)(img/1680853091624.png)]

2.1, object header (Header)

In the object header, it can be divided into two parts, one part is the runtime metadata, and the other part is the type pointer.

Runtime metadata includes hash code, GC age generation, locks held by threads, lock holding flags, thread ids, and thread timestamps .

As can be seen from the figure below, the age of the object is divided into 4 bits, so the maximum is 1111, which is 15, and since it starts from 0, the maximum age is 15, so when setting this age, it can only be set to a smaller value . The byte codes corresponding to the flag bits of the lock are represented by 01, 10, and 11, and their values ​​correspond to 1, 2, and 3 respectively, and the lock upgrade process is irreversible.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-LgRgzEzW-1681101135141)(img/1680854392412.png)]

The type pointer points to the metadata InstanceKlass, which determines the type of the object. If it is an array, you also need to record the length of the array

2.2, instance data (Instance Data)

The effective information actually stored by the object includes various types of fields defined in the code, as well as fields inherited from the parent class and owned by itself . And in these objects, the variables defined by the parent class will appear before the subclass, and the same fields will always be allocated together. If the CompactFields parameter is true, the narrow variable of the subclass may be inserted into the gap of the parent class variable.

2.3, code example

Next, analyze the following code

/**
 * @author zhenghuisheng
 * @date : 2023/4/7
 */
public class Customer {
    
    
    Integer id = 1001;
    String name = "zhenghuisheng";
    public Customer(){
    
    
        Account account = new Account();
    }
}
public class Test{
    
    
    public static void main(String[] args){
    
    
        Customer cust = new Customer(); 
    }
}

Then its corresponding memory structure is shown in the figure below. In this main method, because it is a static method, the first slot in the local variable table is not this, and the second cust in the local variable table refers to new in the heap. The instance address of Customer(), the instance object is mainly composed of the above runtime metadata, type pointer, and its filling. The runtime data area includes the hash value of the unique address, the age of the result after multiple GCs, whether to obtain a lock, etc.; the type pointer corresponds to the Klass class meta information of Customer; the instance data includes its own attributes and parent class attributes

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-wdzf03vJ-1681101135142)(img/1680855082757.png)]

3. Object access location

The main purpose of creating an object is to use it better. Inside the JVM, there are mainly two ways to realize object reference access to internal objects, one is a direct pointer , and the other is handle access .

As shown in the following code, creating an object needs to involve the heap, stack and method area, so the location and access of the object also need to design these three places

//第一个User存在方法区,主要是存储类信息和运行时常量池
//第二个user在栈中,作为变量存储
//最后的 new User存储在堆中
User user = new User();

The way of handle access is as follows, there is a handle pool in the Java heap, and then the handle pool stores the address pointing to the instance in the heap and the address pointing to the class information in the method area, and only needs to save the address of the handle pool in the stack. Can.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-TRULI2d8-1681101135142)(img/1680857098011.png)]

Direct pointer means that there is no need to use the handle pool, and the address of the instance in the heap is directly saved in the local variable table of the stack, and there will be a pointer in the heap to point to the address of the reserved class information in the method area. In the Hotspot virtual machine, this method is mainly used

insert image description here

The handle pointer needs to open up space in the heap space to store the handle pool, so there will be a certain amount of space waste, and the efficiency is relatively low, but if the location of the object changes, such as garbage collection, or when using the markup algorithm , the pointer to the handle pool in the heap in this stack does not need to be changed, only the pointer to the instance data and method area of ​​the handle pool needs to be changed. The advantages and disadvantages of this direct pointer are the opposite of the handle pointer.

4. First experience of direct memory (understanding)

In JDK8, the specific implementation of the method area has changed from the permanent generation to the metaspace, and the metaspace uses local memory, also known as direct memory. This part is not part of the runtime data area, nor is it part of the Java Virtual Machine Specification. "The memory area defined in

insert image description here

In java code, you can directly allocate local memory space through ByteBuffer.allocateDirect()this , that is, directly operate through this NIO, and usually the speed of direct memory is directly better than that of Java heap, and its read and write performance is relatively high. Therefore, for performance considerations, direct memory can be used for frequent reading and writing, and Java's NIO library also allows Java programs to use direct memory.

OutOfMemoryErrorAn exception may also occur , since the direct memory is outside the Java heap, so its size will not be limited by the maximum heap size specified by -Xmx, but since the system's memory is always limited, the sum of the heap and direct memory remains It is limited by the maximum memory given by the operating system, but in direct memory, there are also certain disadvantages: the cost of allocation and recovery is high, and it is not managed by JVM memory recovery .

Therefore, the size of direct memory can be set MaxDirectMemorySizethrough , if not specified, then the default -Xmxvalue is consistent with the maximum parameter value of the heap

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100 * 1024);

Guess you like

Origin blog.csdn.net/zhenghuishengq/article/details/130057210