This article is participating in “Java Theme Month – Java Development Practice”, for details: juejin.cn/post/696826…

This is the fourth day of my participation in Gwen Challenge

Introduction profile

The corresponding process is: object creation, object memory layout, object access location three processes.

Object creation process

How objects are created

There are many different ways to create objects in Java. The most common ways to create objects are through the new keyword and reflection. In addition, there are clone, deserialization and other ways to create.


Created by the new keyword
// Person zhangsan = new Person(id, height, weight)
Person zhangsan = new Person();
Copy the code

Create by reflection

Reflection creates objects, either by calling class.newinstance () to a constructor with no arguments, or by using constructors to create constructor.newinstance ().


//Class CLZ = class.forname ("Person Class fully qualified Class name ")
Class clz = Person.class;
Person zhangsan = clz.newInstance()
// Create using the constructor
Constructor<Person> cons = clz.getConstructor()
// You can also specify the parameter type to get the parameter constructor
Person zhangsan1 = cons.newInstance()
Copy the code

Create objects using Clone

When a class implements the Cloneable interface, you can use the clone() method to copy an object. Note that the clone method is a shallow copy.

Person libo = new Person(name: "李博", age:12, ...)
Person Livonor = new Person(name: "Livorno", age:32, ...)
libo.setFather(Livonor)
Person zhangsi = libo.clone() // At this point, the same object is referenced in memory for The name and father of The same object
Copy the code
Deserialization creation

Created by reading I/O data streams

Object creation process

Check class loading (whether initialized, loaded, parsed)

For both new and reflection, you need to check that the parameters used to create the object (such as its type and parameter types) are class-loaded. If not, complete the class loading process first.

Allocating memory space

  • The virtual machine allocates memory for objects, that is, the starting address and offset.

  • The amount of space an object needs is determined before creation, but the starting address needs to be allocated in memory to find a large enough space. Addresses can be assigned in two ways: pointer collisions and free lists.

Pointer to the collision

The way Pointers collide is by assuming that memory space is neat, that used and free memory is split into two blocks, and that the cut-off point is recorded by a pointer. When allocating memory for an object, move the pointer’s free area by a distance equal to the size of the object.

The free list

If the memory is not tidy, then you need to maintain a table to keep track of which addresses are free in memory. When allocating an object, use the free list to find a chunk of free memory large enough to allocate to the object and update the free list.

Collision of object memory created by multiple threads

For example, thread 1 and thread 2 create two objects at the same time with the same pointer. Each of them loads a pointer into the CPU cache and then executes the instructions to allocate the address space. As a result, whichever object is allocated later may overwrite the address of the object allocated first.

There are two ways to solve this problem. One is to synchronize the action of allocating memory, that is, adopt CAS and retry to ensure atomicity of update operation.

// Pseudocode indicates CAS+ failed retry
while(true){
    oldPtr = ptr // Read the shared pointer
    newPtr = oldPtr + sizeOfInstance
    if(compareAndSet(oldPtr, ptr, newPtr)){break}}Copy the code

Another way is to use TLAB to separate the allocation space of threads in the heap memory. In the heap, each thread is allocated a small block of different space in advance, and each thread creates objects in its own corresponding space.

That is, each Thread allocates a small chunk of memory (Thread Local Allocation Buffer (TLAB)) in the Java heap in advance. Synchronization locks are required only when the TLAB runs out and new TLabs are allocated. The -xx :+/ -usetlab parameter is used to determine whether the VM uses TLAB.


After allocating memory, the object is already in the virtual machine heap, and the virtual machine initializes the allocated memory space to zero (except for object headers).

Set the object header

The object header contains two kinds of information: MarkWord and type pointer.

  • MarkWord: Holds runtime state data of the object itself (such as HashCode, GC generation age, lock state, bias information, etc.)

  • KlassPointer: A type pointer to the metadata of its type. Object headers are detailed in the memory layout of the object.

    • That is, an object’s pointer to its class metadata
    • The virtual machine uses this pointer to determine which class the object is an instance of
  • Data length: If the object is an array, there must also be a block of data in the object header that records the length of the array

    • Because a virtual machine can determine the size of an object from the metadata information of a normal Java object, it cannot determine the size of an array from the metadata of an array.
Execute the object instance constructor

We first recursively execute the constructor of the parent class, then collect and execute the statements that assign values to instance variables in that class, and finally execute the statements in the constructor.

public class AddA {
    public static void main(String[] args) {
        Father guy = new Son(30); guy.saySomething(); System.out.println(guy.age); }}class Father{

    int age = 60;

    public Father(a) {
        saySomething();
    }

    public Father(int age) {
        this.age = age;
    }

    public void saySomething(a){
        System.out.println("I am the father, " + age + "years old"); }}class Son extends Father{

	int age = 20;

    public Son(int age) {
        // super(); If you do not write, you implicitly call the method, and if you write, you must be in the first line of the subclass constructor
        saySomething();
        this.age = age;
        saySomething();
    }

    public void saySomething(a){
        System.out.println("I am the son, " + age + " years old"); }}Copy the code

Because it involves polymorphism and dynamic dispatch of methods. Here is a brief description of its execution process, to grasp the constructor execution is ok.

  • First, you create a Son object, and then call its parameter constructor Son(int age).

    • The parameterless constructor of the parent class is implicitly called in the parameterless constructor, which then goes on to call the Object constructor. The statement that assigns a value to a parent member variable is collected and executed.

    • Since the member variables of the subclass override the member variables of the parent class in polymorphism, the age of the subclass object is still 0.

    • SaySomething () in the no-argument constructor is called by the subclass object, so it prints the first sentence I am the son, 0 years old.

  • The super() method then goes off the stack and back into the subclass constructor. At this point, the statements that assign values to subclass member variables should be collected and executed. Age =20, saySomething() prints the second sentence I am the son, 20 years old.

    • Then execute the constructor assignment int age = age; saySomething();

    • The third sentence was printed. I am the son, 30 years old.

  • Once the subclass object is created, go back to main. At this point, we use polymorphism to turn the object into a Father object.

Due to polymorphic rules: the method being overridden uses dynamic dispatch to find (vtable) a table of methods that actually belong to the subclass object.

So guy.saySomething() actually calls the method of the subclass object, printing out the fourth sentence, I am the son, 30 years old.

Finally, the output is guy.age. The member variable is not polymorphic, so the age 60 of the parent object is printed.

I am the son, 0 years old I am the son, 20 years old I am the son, 30 years old I am the son, 30 years old

Object memory layout

The storage layout of an object in the heap is divided into three parts: the object header, the instance data, and the padding.

Object memory layout

Object head

The object header contains a markword and a type pointer.

markword

Markword stores information unrelated to the object’s self-defined data and is used to represent the runtime state of the object. This includes HashCode, GC age, lock status, and more. In a 32-bit virtual machine, markword is represented by a 32-bit bitmap, the last two bits of which hold lock status information, as shown in the figure below.

  • markword

    • In the common state, the state is 01, hashcode is stored, age is generational, and the biased lock state is 0.

    • In the biased lock state, the state is 01, the thread holding the biased lock and the number of reentrant times are stored, the generation age, and the biased lock state is 1. The hashcode is now gone, but the hashcode can be computed using Object’s hashcode() method, and the resulting hashcode remains the same as long as the method is not overridden.

    • Lightweight lock, status 00. Through CAS, the markword information of the object is atomically exchanged to the thread that holds the lock of the object, which is stored in the lockRecord, and the pointer of the lockRecord is stored in the first 30 bits of the markword of the object head.

    • In the heavyweight lock state, the first 30 bits store the pointer to the lock controller Monitor, and the lock state is 10.

    • When an object is marked to be reclaimed, the last two states are 11.

  • KlassPointer (Type pointer)

Point to type metadata so that an object can be queried for its type information by visiting it.

  • Array length

The length of the main array is typically 4 bytes (in terms of the range of ints)

The instance data
  • The instance data holds the field information of the object. Whether inherited from a parent class or defined in a subclass is stored in the instance data.

  • In order that the fields defined by the parent class appear before the variables defined by the child class.

The contents of the fields defined in the code

Note: The order in which this data is stored is affected by the order in which the VM allocation parameters (FieldAllocationStyle) and fields are defined in the Java source code.

// The default HotSpot vm allocation policy is as follows:

Longs/Doubles, INTS, shorts/chars, Bytes/Booleans, OOP (Ordinary Object Pointers)

  • // As can be seen from the allocation strategy, fields of the same width are always allocated together
  • // If this condition is met, variables defined in the parent class will appear before the child class

CompactFields = true;

// If the CompactFields parameter is true, narrower variables in the subclass may also be inserted into the gap between the parent variables.

Alignment filling

If the size of the object’s instance data is not an integer multiple of 8, the 0 value is padded so that the size of the object is an integer multiple of 8.

Object access location

There are two common methods, handle access and direct pointer access.

Handle access

  • With handle access, a reference to an object, such as a Zhangsan, points to a handle in the handle pool that holds Pointers to the actual instance object and Pointers to the method area data type.

    • The advantage is that when an object is moved (such as when garbage collection requires a lot of moving objects), the reference does not need to be changed frequently, only the instance data pointer in the handle needs to be changed.

  • With pointer access, a reference to an object refers directly to that object. The advantage is that accessing objects by reference does not require an additional pointer location, making access faster.