专栏原创出处:github-源笔记文件 (opens new window)github-源码 (opens new window),欢迎 Star,转载请附上原文出处链接和本声明。

Java JVM-虚拟机专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Java JVM-虚拟机 (opens new window)

# 一、前言

熟悉了虚拟机的「内存数据区域」后,大多数的理解力提留在概念层面,找一段代码细细分析分配过程突然会感觉力不从心。

公网上流出许多错误的概念,比如 基础类型分配在栈上,对象分配在堆上

这个错误的概念,虽然不足以影响我们编程,但是在虚拟机层面的深入学习会有一定误区。

# 代码级别内存分配总结

java 文件编译为 class 文件后,class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

术语说明: Memory m = new Memory();

  • m 为「实例引用」
  • m 对应的对象数据为「实例数据」

针对 JDK 8 细化:

  • class 文件类的版本、字段、方法、接口等描述信息、常量池表->方法区(本地内存)

  • 字符串常量池->方法区(堆)

  • 类静态成员-基础数据(常量)->方法区

  • 类静态成员-实例引用->方法区

  • 类静态成员-实例数据->堆

  • 类非静态成员-基础数据->堆

  • 类非静态成员-实例引用->堆

  • 类非静态成员-实例数据->堆

  • 方法内部-基础数据->栈帧

  • 方法内部-实例引用->栈帧

  • 方法内部-实例数据->堆(逃逸分析,直接将实例数据分配在栈帧上)

# 逃逸分析

public class EscapeAnalysis {
    private int age;
    public EscapeAnalysis(int age) {
        this.age = age;
    }

    public int escape() {
        // 优化为:age 直接在栈上分配,return age + 8;
        return new EscapeAnalysis(10).age + 8;
    }
}

# 写一段简单的实战代码

public class MemoryAllocation {

    /* 类静态成员变量 */
    private static final int DEFAULT_A = 2147483647;
    private static final String DEFAULT_B = "static-variable";
    private static final byte[] DEFAULT_BYTE = new byte[100 * 1024];
    private static final MemoryAllocation MEMORY_ALLOCATION = new MemoryAllocation();

    /* 类非静态成员变量 */
    private int a;
    private String b;
    private byte[] bytes;

    public MemoryAllocation() {}

    public MemoryAllocation(int a, String b, byte[] bytes) {
        this.a = a;
        this.b = b;
        this.bytes = bytes;
    }

    /* 类静态成员方法 */
    public static MemoryAllocation defaultNoneInstance() {
        return MEMORY_ALLOCATION;
    }

    /* 类静态成员方法 */
    public static MemoryAllocation defaultInstance() {
        return new MemoryAllocation(DEFAULT_A, DEFAULT_B, DEFAULT_BYTE);
    }
}

声明术语:Java 中除了基础数据类型后,其余的都看做是对象类型(包括数组,String)

下面的这些数据分配在哪儿?

  • 类静态成员变量-基础数据类型(DEFAULT_A、DEFAULT_B)

  • 类静态成员变量-对象数据类型(DEFAULT_BYTE、MEMORY_ALLOCATION)

  • 类静态成员方法-(defaultNoneInstance、defaultInstance)

  • 类非静态成员变量-基础数据类型(a、b)

  • 类非静态成员变量-对象数据类型(byte)

  • 类静态成员方法-(构造方法)

我们使用 javap -c -v MemoryAllocation.class 反汇编成字节码,保留关键信息逐步分析

# 运行时常量池(Constant pool)分析

运行时常量池是方法区的一部分,也就是 JDK 8 后的元空间(本地内存)的一部分。

public class MemoryAllocation
Constant pool:
   #1 = Methodref          #13.#39        // java/lang/Object."<init>":()V
   #2 = Fieldref           #6.#40         // MemoryAllocation.a:I
   #3 = Fieldref           #6.#41         // MemoryAllocation.b:Ljava/lang/String;
   #4 = Fieldref           #6.#42         // MemoryAllocation.bytes:[B
   #5 = Fieldref           #6.#43         // MemoryAllocation.MEMORY_ALLOCATION:LMemoryAllocation;
   #6 = Class              #44            // MemoryAllocation
   #7 = Integer            2147483647
   #8 = String             #45            // static-variable
   #9 = Fieldref           #6.#46         // MemoryAllocation.DEFAULT_BYTE:[B
  #10 = Methodref          #6.#47         // MemoryAllocation."<init>":(ILjava/lang/String;[B)V
  #11 = Integer            102400
  #12 = Methodref          #6.#39         // MemoryAllocation."<init>":()V
  #13 = Class              #48            // java/lang/Object
  #14 = Utf8               DEFAULT_A
  #15 = Utf8               I
  #16 = Utf8               ConstantValue
  #17 = Utf8               DEFAULT_B
  #18 = Utf8               Ljava/lang/String;
  #19 = Utf8               DEFAULT_BYTE
  #20 = Utf8               [B
  #21 = Utf8               MEMORY_ALLOCATION
  #22 = Utf8               LMemoryAllocation;
  #23 = Utf8               a
  #24 = Utf8               b
  #25 = Utf8               bytes
  #26 = Utf8               <init>
  #27 = Utf8               ()V
  #28 = Utf8               Code
  #29 = Utf8               LineNumberTable
  #30 = Utf8               LocalVariableTable
  #31 = Utf8               this
  #32 = Utf8               (ILjava/lang/String;[B)V
  #33 = Utf8               defaultNoneInstance
  #34 = Utf8               ()LMemoryAllocation;
  #35 = Utf8               defaultInstance
  #36 = Utf8               <clinit>
  #37 = Utf8               SourceFile
  #38 = Utf8               MemoryAllocation.java
  #39 = NameAndType        #26:#27        // "<init>":()V
  #40 = NameAndType        #23:#15        // a:I
  #41 = NameAndType        #24:#18        // b:Ljava/lang/String;
  #42 = NameAndType        #25:#20        // bytes:[B
  #43 = NameAndType        #21:#22        // MEMORY_ALLOCATION:LMemoryAllocation;
  #44 = Utf8               MemoryAllocation
  #45 = Utf8               static-variable
  #46 = NameAndType        #19:#20        // DEFAULT_BYTE:[B
  #47 = NameAndType        #26:#32        // "<init>":(ILjava/lang/String;[B)V
  #48 = Utf8               java/lang/Object

静态成员变量分析:

  • #7 = Integer 2147483647 为 DEFAULT_A 的常量
  • #8 = String #45 // static-variable 为 DEFAULT_B 的常量
  • #9 = Fieldref #6.#46 // MemoryAllocation.DEFAULT_BYTE:[B DEFAULT_BYTE 字段对应符号引用指向了类.变量名符号.类型符号

非静态成员变量(也叫类实例变量)分析:

  • #2 = Fieldref #6.#40 // MemoryAllocation.a:I 引用符号
  • #3 = Fieldref #6.#41 // MemoryAllocation.b:Ljava/lang/String; 引用符号
  • #4 = Fieldref #6.#42 // MemoryAllocation.bytes:[B 引用符号

静态成员方法 defaultInstance 分析:

   /* 类静态成员方法 */
   public static MemoryAllocation defaultInstance() {
       return new MemoryAllocation(DEFAULT_A, DEFAULT_B, DEFAULT_BYTE);
   }
    Code:
      stack=5, locals=0, args_size=0
         0: new           #6    // 创建一个对象, 并将其引用引用值压入栈顶
         3: dup                 // 复制栈顶数值并将复制值压入栈顶
         4: ldc           #7    // 将 2147483647 常量值从常量池中推送至栈顶
         6: ldc           #8    // 将 static-variable 常量值从常量池中推送至栈顶
         8: getstatic     #9    // Field DEFAULT_BYTE:[B 获取指定类的静态域 DEFAULT_BYTE , 并将其压入栈顶
        11: invokespecial #10   // Method "<init>":(ILjava/lang/String;[B)V 构造方法
        14: areturn     // 返回对象引用
      LineNumberTable:
        line 39: 0
{
  public MemoryAllocation();
    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 23: 0
        line 24: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LMemoryAllocation;

  public MemoryAllocation(int, java.lang.String, byte[]);
    descriptor: (ILjava/lang/String;[B)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=4
         0: aload_0
         1: invokespecial #1    // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1
         6: putfield      #2    // Field a:I
         9: aload_0
        10: aload_2
        11: putfield      #3    // Field b:Ljava/lang/String;
        14: aload_0
        15: aload_3
        16: putfield      #4    // Field bytes:[B
        19: return
      LineNumberTable:
        line 26: 0
        line 27: 4
        line 28: 9
        line 29: 14
        line 30: 19
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      20     0  this   LMemoryAllocation;
            0      20     1     a   I
            0      20     2     b   Ljava/lang/String;
            0      20     3 bytes   [B

  public static MemoryAllocation defaultNoneInstance();
    descriptor: ()LMemoryAllocation;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #5    // Field MEMORY_ALLOCATION:LMemoryAllocation;
         3: areturn
      LineNumberTable:
        line 34: 0

  public static MemoryAllocation defaultInstance();
    descriptor: ()LMemoryAllocation;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=5, locals=0, args_size=0
         0: new           #6    // class MemoryAllocation
         3: dup
         4: ldc           #7    // int 2147483647
         6: ldc           #8    // String static-variable
         8: getstatic     #9    // Field DEFAULT_BYTE:[B
        11: invokespecial #10   // Method "<init>":(ILjava/lang/String;[B)V
        14: areturn
      LineNumberTable:
        line 39: 0

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: ldc           #11   // int 102400
         2: newarray       byte
         4: putstatic     #9    // Field DEFAULT_BYTE:[B
         7: new           #6    // class MemoryAllocation
        10: dup
        11: invokespecial #12   // Method "<init>":()V
        14: putstatic     #5    // Field MEMORY_ALLOCATION:LMemoryAllocation;
        17: return
      LineNumberTable:
        line 15: 0
        line 16: 7
}
SourceFile: "MemoryAllocation.java"

# 参考

最后修改时间: 2/17/2020, 4:43:04 AM