You don't even know your object yet, and you still shout about object-oriented programming every day?

Recommended reading:

Java is an object-oriented programming language. Whether you have only one sentence to describe your understanding of objects in detail: everything is an object, and the new objects are all on the heap! Wait, isn't that 2 sentences? No, that last sentence is just why I wrote this article. When learning Java, everyone said that the new objects are on the heap, and I believe this! But I became more and more suspicious of this sentence in the future. I think that the toString method of each class will create a new StringBuffer. Does this increase the heap memory? twice as big? Why is there no heap overflow when creating objects in a For loop? How much memory does the created object take up in the heap? Looking down with the above questions, this article describes what an object is as a comprehensive arrangement of Java objects.

Everything in Java is an object, and the creation of an object is mainly as follows:

People people = new People();

Now interviews are all kinds of text pits, such as: asking whether this object allocates memory on the heap? How to answer, yes? no?

This question needs to be answered according to the context, that is, according to the environment in which this line of code is located, what is the environment: the running environment JRE, the writing location, and the results of different environments are different. To know the result, first get to the following knowledge points:

Escape analysis is a technology that is enabled by default after the JDK6+ version (now JDK15 is an old technology==!), it mainly analyzes the reference scope of local variables inside the method for subsequent optimization. After escape analysis, local variables in a method are divided into 3 types of escape objects

  • Global escape object: For the outside world, the object can be directly accessed at the class level (call the class to get the object instance)
  • Parameter escape object: For the outside world, the object can be directly accessed at the method level (call the method to get the object instance)
  • Unescaped object: to the outside, the object appears as if it does not exist and cannot be sniffed

Subsequent optimization refers to the optimization of not escaping, which is mainly divided into scalar replacement and lock elimination

Scalar replacement : In Java, the 8 basic data types can already allocate space directly, and can no longer be refined. They are called standard variables, or scalars for short. Object references are memory addresses that can no longer be refined, and can also be called scalars. Java objects are aggregated from multiple scalars, which are called aggregates. The process of splitting and replacing the member variables of a Java object with scalars according to this standard is called scalar substitution. This process results in the allocation of objects not necessarily on the heap, but on the stack or in registers.

Lock elimination : Java locks are used for multi-threading. When the lock is used in a single-threaded environment and optimized by the JIT compiler, the lock-related code will be removed. This process is lock elimination (it belongs to optimization and does not affect objects) .

Pointer compression : The reference pointer of a 32-bit machine object is represented by 32-bit and 64-bit in 64-bit. The same configuration but the memory usage increases, is this really good? JDK provides pointer optimization technology to compress 64-bit (8-byte) pointer references (Refrence type) into 32-bit (4 bytes) to save memory space.

object escape

A Java object of standard size = 32byte (how to calculate it will be written later)

class People {
    int i1;
    int i2;
    int i3;
    byte b1;
    byte b2;
    String str;
}

object not escaped

public class EscapeAnalysis {

    public static void main(String[] args) throws IOException {
        // 预估:在不发生GC情况下32M内存
        for (int j = 0; j < 1024 * 1024; j++) {
            unMethodEscapeAnalysis();
        }
        // 阻塞线程,便于内存分析
        System.in.read();
    }

    /**
     * people对象引用作用域未超出方法作用域范围
     */
    private static void unMethodEscapeAnalysis() {
        People people = new People();
        // do  something
    }
}

Escape analysis is not enabled

Start JVM parameters

-server -Xss108k -Xmx1G -Xms1G -XX:+PrintGC -XX:-UseTLAB -XX:-DoEscapeAnalysis -XX:-EliminateAllocations

Start console: no output: no GC occurred

heap memory view

$ jps
3024 Jps
16436 EscapeAnalysis
24072 KotlinCompileDaemon
$ jmap -histo 16436

 num     #instances         #bytes  class name
----------------------------------------------
   1:       1048576       33554432  cn.tinyice.demo.object.People
   2:          1547        1074176  [B
   3:          6723        1009904  [C
   4:          4374          69984  java.lang.String

At this time, a total of 1024*1024 instances have been created in the heap, each instance is 32bytes, a total of 32M memory

Enable escape analysis

Start JVM parameters

-server -Xss108k -Xmx1G -Xms1G -XX:+PrintGC -XX:-UseTLAB -XX:-DoEscapeAnalysis -XX:-EliminateAllocations

Start console: no output: no GC occurred

heap memory view

$ jps
3840 Jps
24072 KotlinCompileDaemon
25272 EscapeAnalysis
$ jmap -histo 25272

 num     #instances         #bytes  class name
----------------------------------------------
   1:       1048576       33554432  cn.tinyice.demo.object.People
   2:          1547        1074176  [B
   3:          6721        1009840  [C
   4:          4372          69952  java.lang.String

At this time, it is the same as if it was not turned on, and 1024*1024 instances are still created in the heap, each instance is 32bytes, a total of 32M memory

Enable escape analysis and scalar substitution

Start JVM parameters

-server -Xss108k -Xmx1G -Xms1G -XX:+PrintGC -XX:-UseTLAB -XX:+DoEscapeAnalysis -XX:+EliminateAllocations

heap memory view

$ jps
7828 Jps
21816 EscapeAnalysis
24072 KotlinCompileDaemon
$ jmap -histo 21816

 num     #instances         #bytes  class name
----------------------------------------------
   1:         92027        2944864  cn.tinyice.demo.object.People
   2:          1547        1074176  [B
   3:          6721        1009840  [C
   4:          4372          69952  java.lang.String

At this point only 92027 instances were created in the heap, and the memory footprint was 11 times less.

Start the console: no output: no GC occurred, indicating that the instance is indeed not allocated to the heap

It is not allocated to the heap because part of it is allocated to the stack. If this unescaped object is allocated to the stack, its life cycle will be automatically destroyed along with the stack after use. The following are the specific details of the java object allocation.

object memory allocation

Instance Allocation Principle


  1. try stack allocation

    • Allocation of thread-private objects directly on the stack based on escape analysis and scalar substitution

    • The object is automatically destroyed after the function call is completed, no GC recovery is required

    • The stack space is very small, the default is 108K, and large objects cannot be allocated

  2. try TLAB

    • Determine whether to use TLAB (Thread Local Allocation Buffer) technology

      • Virtual machine parameters -XX: +UseTLAB, -XX: -UseTLAB, enabled by default

      • Virtual machine parameter -XX:TLABWasteTargetPercent to set the percentage of eEden space occupied by TLAB, the default is 1%

      • Virtual machine parameter -XX:+PrintTLAB prints the usage of TLAB

      • TLAB itself occupies the space in the eEden area, and the space is too small to store large objects.

      • Each thread pre-allocates a small block of memory in the Java heap, and when an object is created to request memory allocation, it will be allocated on this block of memory

      • Using thread control safety, there is no need for memory allocation through synchronization control in the Eden area

  3. Try old age allocation (heap allocation principle)

    • If you can directly enter the old generation, allocate directly in the old generation
  4. When all of the above fail (note that it is easy to trigger GC when allocating objects, the principle of heap allocation)

    • When the memory is contiguous: use pointer collision (Serial, ParNew and other collectors with Compact process)

      • Allocated in the Eden area of ​​the heap, the memory of this area is contiguous

      • The pointer always points to the start of the free area.

      • After the new object allocates space, the pointer moves backward by the size unit of the space occupied by the object, thereby pointing to the starting position of the new free area

      • In the process of object allocation, the method of CAS plus failure retry is used to ensure thread safety (CAS is atomic operation)

      • If successful: set the object header information

    • When the memory is not continuous: use the free list (CMS, a collector based on the Mark-Sweep algorithm)

      • If the heap space is not continuous, the JVM maintains a relational table to make the memory logically continuous for the purpose of object allocation

Heap allocation principle:


  • Priority is given to allocation in the Eden area

    • The ratio of Eden to Survivor can be determined to be 8:1 by -XX:SurvivorRation=8

    • There are 2 Survivor areas (From and To) in the new generation. When the new generation has 10 copies, the Survivor occupies 2 copies and the Eden occupies 8 copies.

    • New objects will be allocated in Eden first

      • Allocate directly when space is sufficient

      • When Eden is running out of space

        • Perform a Minor Gc recycling of the objects in Eden and prepare to put them into the Survivor area of ​​the From type

          • From type of Survivor area

            • When there is enough space, the GC object will be reclaimed when placing the GC object

            • When the space is insufficient, put the GC object directly into the old generation

        • Eden space is still insufficient after Minor GC

          • New objects go directly to the old age
  • Long-lived objects are handed over to the old generation (permanent generation)

    • After the object of Eden enters the Survivo area after a Minor GC, the age field of the object header information of the object is Age+1

    • Each time an object in the Survivor area passes through Minor GC, the age field of the object header information Age+1

      • GC (copy algorithm) will be performed back and forth in the From Survivor and ToSurvivor areas
    • When the age of the object reaches a certain value (default 15 years old), it will be promoted to the old age

    • -XX:MaxTenuringThreshold=15 sets the generation age to 15

  • Large objects go directly into the old generation (permanent generation)

    • Large objects are objects (array classes, strings) that occupy a large amount of contiguous space in the heap

    • -XX:MaxTenuringThreshold=4M can set objects larger than 4M to enter the old age directly

  • Dynamic Age Judgment

    • When GC recycles objects, it is not necessary to strictly require the generation age to promote the old age

    • When the sum of objects of the same age in the Survivor area is greater than 1/2 of the Survivor space

      • Objects whose age is greater than or equal to this age (the same age) can directly enter the old age
  • Old generation object allocation uses space allocation guarantee

    • Minor GC is safe when the size of all objects in the young generation is less than the size of the free space in the old generation

      • It is equivalent to that all objects in the new generation can be placed in the old generation, so there will be no overflow and other phenomena.
    • Conversely, Minor GC is unsafe

      • It is equivalent to only part of the new generation objects can be placed in the old generation, and the other part will fail to be placed due to insufficient space.

      • Safeguard-XX:HandlePromotionFailure=true, allow guarantee failures

      • Before MinorGC occurs, the JVM will determine whether the average size of each promotion to the old generation is greater than the size of the remaining space in the old generation.

        • If less than and allow the guarantee to fail, perform a Minor GC

          • Object GC prediction is stable, and it will not happen that a large number of objects suddenly enter the old age and overflow due to insufficient space
        • If it is less than and does not allow guarantee failures, perform a full GC

          • Even if the object GC prediction is stable, there is no guarantee that there will be no surge, so the safety point is to go to the Full GC first.

          • Reclaim all areas to make more space for the old age

        • If less than a full GC is performed even if the guarantee fails

          • That is, the number of surviving objects after Minor GC suddenly increases sharply. Even if the guarantee is allowed to fail, it is still very likely to be unsafe.

          • Reclaim all areas to make more space for the old age

object instance composition

  • object header

    • MarkWord (required)

    • Type pointer: class metadata pointing to the object (optional)

    • Array length (only for array type objects)

  • instance data

    • The object's field properties, methods, etc., are stored in the heap
  • data filling

    • The JVM requires that the memory size of java objects should be a multiple of 8bit

    • The instance data may not be a multiple of 8 and needs to be padded and aligned with 0

MarkWord structure

object initialization

Since object initialization involves class loading, it is not described here.

  • The allocated space is set to 0

  • Data padding with 0, 8-byte alignment

  • Object header information settings

  • call for initialization (instantiation of the class)

Give an example first

public class ClinitObject {

    static ClinitObject clinitObject;

    static {
        b = 2;
        clinitObject = new ClinitObject();
        System.out.println(clinitObject.toString());
    }

    int a = 1;
    static int b;
    final static int c = b;
    final static String d = new String("d");
    String e = "e";
    String f = "f";

    public ClinitObject() {
        e = d;
        a = c;
    }

    @Override
    public String toString() {
        return "ClinitObject{" + "\n" +
                "\t" + "a=" + a + "\n" +
                "\t" + "b=" + b + "\n" +
                "\t" + "c=" + c + "\n" +
                "\t" + "d=" + d + "\n" +
                "\t" + "e=" + e + "\n" +
                "\t" + "f=" + f + "\n" +
                '}';
    }

    public static void main(String[] args) {
        System.out.println(clinitObject.toString());
    }
}

console

ClinitObject{
	a=0
	b=2
	c=0
	d=null
	e=null
	f=f
}
ClinitObject{
	a=0
	b=2
	c=2
	d=d
	e=null
	f=f
}

object size calculation

  • normal object

    • 4 or 8 bytes (MarkWord) + 4 or 8 bytes (klass Reference) + instance data length + 0 padding (Padding)
  • array object

    • 4 or 8 bytes (MarkWord) + 4 or 8 bytes (klass Reference) + 4 bytes (ArrayLength) + instance data length + 0 padding (Padding)
  • other instructions:

    • The object header (MarkWord) is 4 bytes in 32-bit JVM and 8 bytes in 64-bit JVM

    • To save space, pointer compression is used:

      • JDK6 began to compress the type pointer (Reference), compressing the first 8 bytes and 4 bytes after compression

        • Parameter -XX:+UseCompressedOops
      • JDK8 began to add metadata space metaSpace, so new parameters were added to control pointer compression:

        • -XX:+UseCompressedClassPointers (pointer compression switch, when heap memory >= 32G, it is automatically turned off)

        • -XX:CompressedClassSpaceSize (the size of the class metadata space pointed to by Reference, the default is 1G, and the upper limit is 32G)

    • Data padding (Padding) is to ensure that the object size is an integer multiple of 8 data padding, so that the data is aligned

  • Common data type sizes

object positioning

In the JVM, the calling object in the java source code locates and accesses the object in the heap through the reference of the local variable in the virtual machine stack to point to the object in the heap. There are two mainstream access methods.

  • handle access

    • A table of relationships between reference and object instance data (instantiation data) and object type data (ClassFile data) is maintained separately in the jvm heap

    • Find the java instance object through the relationship table

  • Direct access (used by Sun HotSpot)

    • The reference directly points to the instance data (instantiation data) of the object in the java heap, and the type pointer (Reference) of the instance object points to the type data (ClassFile data)

Pointer Compression Example

public class CompressedClassPointer {

    public static void main(String[] args) throws IOException {
        People people=new People();
        System.in.read();
    }
}

Enable pointer compression (default)

JVM parameters

-server -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -XX:CompressedClassSpaceSize=1G

heap memory view

$ jps
11440
11744 RemoteMavenServer
14928 KotlinCompileDaemon
15540 Launcher
15908 Jps
9996 CompressedClassPointer
$ jmap.exe -histo 9996

 num     #instances         #bytes  class name
----------------------------------------------
... 
233:             1             32  cn.tinyice.demo.object.People

Turn off pointer compression

JVM parameters

-server -XX:-UseCompressedOops

heap memory view

$ jps
11440
11744 RemoteMavenServer
14928 KotlinCompileDaemon
8448 CompressedClassPointer
$ jmap.exe -histo 8448

 num     #instances         #bytes  class name
----------------------------------------------
...
254:             1             40  cn.tinyice.demo.object.People

Example parsing

In the example, the object size will be reduced by 8 bytes after it is enabled. The pointer compression is 8 bytes to 4 bytes. It stands to reason that there should be 4 bytes less or 32 bits. Why is this like this?

Object size calculation when compressed pointers are turned on

/**
 * Size(People) =
 * 8(mark word)+4(klass reference)+ 4(i1)+4(i2)+4(i2)+1(b1)+1(b2)+4(str reference) + 2(padding)
 * |----------------------------------- 30 byte ---------------------------------|----00-------/
 * |---------------------------------------- 32 byte ------------------------------------------/
 */

Object size calculation when compressed pointers are turned off

/**
 * Size(People) =
 * 8(mark word)+8(klass reference)+ 4(i1)+4(i2)+4(i2)+1(b1)+1(b2)+8(str reference) + 2(padding)
 * |----------------------------------- 38 byte ---------------------------------|----00-------/
 * |---------------------------------------- 40 byte ------------------------------------------/
 */

The difference is seen here, which is caused by data padding. In order to facilitate data management, Java objects are all 8-byte aligned, and 0 is used for padding if insufficient.

As for the instantiation of the object, it will be described after the class loading process is written.

Related Posts