JVM_02 memory area (heap, method area, direct memory)

Posted by AmandaF on Thu, 23 Dec 2021 05:28:19 +0100

catalogue

heap

definition

characteristic

Method area

definition

characteristic

Runtime Constant Pool

Definition

Stringtable (string constant pool)

String variable splicing

String compiler optimization

lazy instantiation

intern() method

Direct memory

Application scenario

Principle analysis

heap

definition

  • Through the new keyword, the storage area of the created object
  • It is the largest area of memory managed by the virtual machine to store object instances.

characteristic

  • It is an area shared by threads. All objects in the heap need to consider thread safety
  • There is a garbage collection mechanism
  • It can be implemented as either fixed size or extended. The mainstream is through - Xmx and - Xms(-Xms). The initial size of heap memory is 1 / 64 of physical memory by default, and the maximum size of - Xmx heap memory is 1 / 4 of physical memory by default
    )Configure.

Method area

definition

  • Like Java heap, it is a memory area shared by each thread, which is used to store type information, constants, static variables, code cache compiled by the real-time compiler and other data that have been loaded by the virtual machine.
  • The method is logically a part of the heap. The specific implementation is different. Before HotSpot virtual machine 1.8, its specific implementation is permanently replaced in the JVM memory. After 1.8 and 1.8, its implementation meta space is in the local memory (in the operating system)

characteristic

  • The method area is created at virtual startup
  • The method area is logically a part of the heap, but different versions of JVM have different alignment implementations, such as permanent generation (implementation before jdk8, using heap memory) and meta space (implementation of jdk8, using operating system memory)
  • When the method area cannot meet the new memory allocation requirements, an OOM exception will be thrown (the above situation will be triggered by dynamically generating JSP files and constantly creating new strings)

Runtime Constant Pool

Definition

The runtime constant pool is a part of the method area. In addition to the Class version, bytes, methods, interfaces and other description information, there is also a constant pool table in the Class file, which is used to store various literal quantities and symbol references generated during compilation. This part will be stored in the runtime constant pool of the method area after the Class is loaded. And change the symbolic address into the real address.

  • When running in 1.6, the constant pool table is placed in the permanent generation
  • In 1.8, the runtime constant pool was moved into heap memory

Let's first understand the constant pool under binary bytecode

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

Execute javap - V HelloWorld Class to get the following bytecode instructions

Classfile /Users/lizhijian/IdeaProjects/Demo/out/production/Demo/jvm/HelloWorld.class
  Last modified 2021-6-16; size 541 bytes
  MD5 checksum 2857962a7a60aa95f2f6642d6b02afad
  Compiled from "HelloWorld.java"
public class jvm.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
                           // (2) Get #21#22 pointing information
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23 / / hello world (6) points to hello world
   #4 = Methodref          #24.#25        // (9) java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // jvm/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ljvm/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System  (3)
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream; (4)
  #23 = Utf8               hello world    //(7)
  #24 = Class              #31            // (10)java/io/PrintStream
  #25 = NameAndType        #32:#33        // (11)println:(Ljava/lang/String;)V
  #26 = Utf8               jvm/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public jvm.HelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         // (1) Gets the constant number 2 in the constant pool
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         // (5) Loads the #3 pointed constant into the operand stack
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // (8)Method  java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 11: 0
        line 12: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

You can see that the part below the Constant pool is a Constant pool, which is a table. The virtual machine instruction finds the class name, method name, parameter type, literal and other information to be executed according to this constant table. (1) ~ (11) is the process of loading Constant pool data.

Runtime constant pool: when the class is loaded, the constant pool information of the class will be put into the runtime constant pool, and the symbolic address (#1, #2) in it will be replaced with the real address.

Stringtable (string constant pool)

Decompile the following code

public class Demo1_22 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
    }
}

Compiled bytecode:

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1
         0: ldc           #2                  // String a load a from constant pool #2 number
         2: astore_1                          // Store in slot 1 of LocalVariableTable
         3: ldc           #3                  // String b  
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: return
      LineNumberTable:
        line 12: 0
        line 13: 3
        line 14: 6
        line 15: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            3       7     1    s1   Ljava/lang/String;
            6       4     2    s2   Ljava/lang/String;
            9       1     3    s3   Ljava/lang/String;

The information in the constant pool is loaded into the runtime constant pool. At this time, a , b , ab symbols have not yet become string objects. Wait until the sentence String s1 = "a" is executed, and then change the a symbol into "a" string. The process is as follows:

ldc #2 will change the a symbol into "a" string (lazy loading), and find out whether there is the same string in the quasi good stringTable []. If not, put it into the string pool.

String variable splicing

Let's take another demo

//StringTable["a", "b", "ab"]
public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;  //new StringBuilder().append(s1).append(s2).toString();
        System.out.println(s3 == s4);
    }

Compiled bytecode:

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=5, args_size=1
         0: ldc           #2                  // String a
         // Store a in the local variable table
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         // Create a StringBuilder object
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
         // Call the constructor of StringBuilder
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
         // Load element at position 1 of local variable table
        16: aload_1
         // Call the append method in the local variable table
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
         // Call the append method in the local variable table
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
         // Call the toString method in the local variable table 
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
         // Store the toString result in the local variable table No. 4
        27: astore        4
        29: return
      LineNumberTable:
        line 12: 0
        line 13: 3
        line 14: 6
        line 15: 9
        line 16: 29
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      30     0  args   [Ljava/lang/String;
            3      27     1    s1   Ljava/lang/String;
            6      24     2    s2   Ljava/lang/String;
            9      21     3    s3   Ljava/lang/String;
           29       1     4    s4   Ljava/lang/String;

You can see that string S4 = S1 + S2; < = = >  new StringBuilder().append(s1).append(s2).toString();

The source code of toString() is

public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

Create a new string in the heap. To sum up, system out. println(s3 == s4); Output false

String compiler optimization

String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
String s5 = "a" +"b";

Because String s3 = "ab" has been created in the string pool (S3 goes to the constant pool to load, and after loading, "ab" is added to StringTable []), the jvm thinks that "a" and "b" are constants and will not change, so it makes optimization during compilation to make String s5 = "a" + "b", directly find the "ab" string in the string pool without re creating the object. System out. println(s3 == s5)   <==>   true

lazy instantiation

public static void main(String[] args) {
        System.out.println("1");
        System.out.println("2");
        System.out.println("3");
        System.out.println("4");
        System.out.println("5");
        System.out.println("6");
        System.out.println("7");
        System.out.println("8");
        System.out.println("9");
        System.out.println("1");
        System.out.println("2");
        System.out.println("3");
    }

As can be seen from the above figure, with the execution of the code, the number of corresponding strings in StringTable has been increasing until system out. Println ("9") is finished. When the second execution of system out. println("1"); The number of times does not increase because "1" ~ "9" has been generated in the string pool.

intern() method

concept

The function of intern() is to put this string object into the string pool. If it exists, it will not be put into the string pool. If it does not exist, it will be put into the string pool, and the objects in the string pool will be returned (note here that the string pool objects will be returned regardless of whether they exist in the string pool before)

  • 1.8 try to put this string object into the string pool. If there is one, it will not be put in. If there is no one, it will be put into the string pool and the objects in the string pool will be returned
  • 1.6 try to put the string object into the string pool. If there is one, it will not be put in. If there is no one, it will copy the object and put it into the string pool, and the object in the string pool will be returned

1.8 look at some 🌰:

  • demo1
public static void main(String[] args) {
        // "A" in new String("a") means that constants are put into the string pool and "b" means that constants are put into the string pool
        // new String("a") generates a new object in the heap
        String s1 = new String("a") + new String("b"); // Because it is dynamically spliced, "ab" is not put into the string pool
        String s2 = new String("a") + new String("b");
        System.out.println(s1 == "ab");   //false
        System.out.println(s2.intern() == "ab");  // true
    }

When new String("a") is executed, "A" will be put into the string pool and new objects will be generated in the heap. The same is true for new String("b"). However, when String s1 = new String("a") + new String("b"); is executed, because it is dynamically spliced, "ab" will not be put into the string pool and objects will only be created in the heap. Therefore, S1 = = "ab" outputs false, while s2.intern() returns string pool objects, so s2.intern() = " AB "output true.

  • demo2
public static void main(String[] args) {
        String s = new String("a") + new String("b");// Heap object
        String s2 = s.intern();  // "ab" does not exist in the string pool. Put the s object into the string pool and assign the result to s2
        String x = "ab";
        System.out.println(s2 == "ab"); // true
        System.out.println(s == x); // true
    }

--output
true
true

Execute to String s2 = s.intern(); If it is found that there is no "ab" in the string pool, create "ab" in the string pool. Note: it needs to be understood that the s object is put into the string pool!!!!!!!!!!! You can see that s2 gets the address of "ab" in the string pool, so it outputs true, and the "ab" in the string pool itself is the address of S, so s==x(ab) outputs true.

  • demo3

If "ab" is created first, the output is as follows.

public static void main(String[] args) {
        String x = "ab"; // String pool object
        String s = new String("a") + new String("b");// Heap object
        String s2 = s.intern();  // "ab" already exists in the string pool, and s.intern() returns the string pool object
        System.out.println(s2 == x); // true
        System.out.println(s == x); // false
    }

--output
true
fasle

You can see that the variable s returns the address in the heap, while s.intern() returns the address of the string pool, that is, s==x outputs false (because "ab" in the string pool does not depend on the variable s when it is initially generated, so s is not directly related to "ab"), and s2==x outputs true.

1.6 lower 🌰:

  • demo1

Create string first

public static void main(String[] args) {
        String x = "ab";
        String s = new String("a") + new String("b");// Heap object
        String s2 = s.intern();
        System.out.println(s2 == x);
        System.out.println(s == x);
    }

--output
true
false

”AB "create in the string pool first, s2 = s.intern(), and return the string pool address for s2. S itself points to the heap (the string pool already has" ab ", that is, it does not need to rely on s to create it again). Therefore, s2==x outputs true and s = = x outputs false.

  • demo2

Create after string

public static void main(String[] args) {   
        String s = new String("a") + new String("b");// Heap object
        String s2 = s.intern(); // A copy of s.intern() in 1.6 is put into the string pool. It is understood that the creation of "ab" does not directly depend on S
        String x = "ab";
        System.out.println(s2 == x);
        System.out.println(s == x);
    }

-- output
true
false

It can be analyzed from the code that s2=s.intern() returns the string pool object, so s2==x outputs true. S.intern() copies a copy of "ab" to the string pool, that is, "ab" does not directly depend on S, so s==x outputs false.

Summary:

  • In 1.6 / 1.8, s.intern() returns string pool objects
  • Whether the string variable s itself points to "ab" in the string pool depends on whether the creation of "ab" depends on S.

Direct memory

Application scenario

  • Common in NIO operations, it is used for data buffer
  • Allocation recovery cost is high, but read-write performance is high
  • Not managed by JVM memory reclamation
  • Traditional file operation

The operation file code is as follows:

static final String FROM = "E:\\xx.mp4";
    static final String TO = "E:\\a.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io(); // io time: 1535.586957 1766.963399 1359.240226
        directBuffer(); // directBuffer time: 479.295165 702.291454 562.56592
    }

    private static void directBuffer() {
        long start = System.nanoTime();
        try (FileChannel from = new FileInputStream(FROM).getChannel();
             FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
            ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
            while (true) {
                int len = from.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();
                to.write(bb);
                bb.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("directBuffer Time:" + (end - start) / 1000_000.0);
    }

    private static void io() {
        long start = System.nanoTime();
        try (FileInputStream from = new FileInputStream(FROM);
             FileOutputStream to = new FileOutputStream(TO);
        ) {
            byte[] buf = new byte[_1Mb];
            while (true) {
                int len = from.read(buf);
                if (len == -1) {
                    break;
                }
                to.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("io Time:" + (end - start) / 1000_000.0);
    }

--output
io Time: 2921.12
directBuffer Time: 892.55

It can be seen that the performance of direct memory is much higher than that of traditional IO

Principle analysis

  • Traditional file operation

The traditional file operation will first load the data from the disk into the system memory, and then load the data from the system memory into the buffer of the heap memory.

  • Using direct memory

As shown in the figure above, direct memory is used to open up a space in the disk, so that the system memory and Java heap memory can be accessed directly, which greatly improves the file reading and writing performance.

Reference: understanding the Java virtual machine