【JVM】 内存管理

读《深入理解 Java 虚拟机》的一些思考之对 JVM 内存管理的一些思考~

1. JVM 内存区域划分

alt text

1.1 程序计数器

  • 平时 Java 开发并没有什么概念,它的主要作用就是用来记录「当前线程」运行的字节码行号;
  • 每一个线程独占空间

1.2 虚拟机栈、本地方法栈(Stack)

无论是虚拟机栈、还是本地方法栈,都是栈空间,区别只是在于是用来记录本地方法产生的栈帧还是 Java 方法产生的栈帧;

和我们理解的栈类似,拥有先进后出的特性;每一个线程独占的空间

A. 栈帧

  • 栈内元素称为「栈帧」,JVM 每执行一个方法,就会往栈内压入一个「栈帧」,栈帧内记录着方法执行需要的一些元素,例如局部变量表;局部变量表记录在编译期可以确定的一些变量,简单可以理解成 Java 方法内的临时变量;
  • alt text
  • JVM 在执行方法的时候,就会往栈中压入一个栈帧,方法执行完成则将栈帧弹出;
  • 局部变量表所占用的空间往往都是确定的,因为一个方法内的局部变量的数量总是确定的;
  • 注意👀,局部变量表占用空间的大小是确认的,不代表其所占用的内存大小是固定,因为存在「递归」调用的情况,以及各种逻辑判断的存在,有些方法是不会执行~

1.3 堆(heap)

堆是 JVM 内存管理中最大的一块区域,Java 所有的对象实例数组都诞生于此,相较于程序计数器、栈为线程私有的空间,堆空间为所有线程共享的空间;堆空间的大小是 JVM 启动的时候就确定的,可以通过 XmsXmx 参数设定堆空间大小

一般 Xms 和 Xmx 都是设定的相同大小,即一开始就明确堆空间大小,避免在运行期间发生堆空间的伸缩,对服务性能造成影响。

堆空间是垃圾收集器的主要区域,因为占用空间最大,「垃圾」产生最多,所谓「垃圾」即方法在运行过程中创建出来的作用域仅在方法内的对象实例,在方法运行结束,该对象实例就已经没有用了,其所占用的内存空间应该会清理回收,以提供给到新的对象实例创建。

alt text

对象实例

alt text
每一个对象实例在堆空间所占用的空间,都有以下几部分组成

A. 对象头

存储的数据主要包含两部分:

  1. 运行时数据,例如锁信息、GC 年龄(超过一定年龄后进入老年代)、Hashcode
  2. 执行实例类信息的指针,类信息存储在方法区(后续会讲到)
B. 实际存储的数据

存储非静态(非 static)的实例变量相关值,主要是指基本类型(int、long、char等)和引用类型

C. 填充部分

为了提高CPU访问速度,JVM 可能会根据不同平台的要求进行字节对齐。

1.4 方法区(Method Area)

与堆空间一样,为所有线程共享的空间,主要是用来存储类型信息、常量、静态变量、运行时常量池等数据;简单来讲就是「类级别」的相关数据都在方法区中,实例级别的数据则存储在堆中。

  • 类型信息:不同的类有不同的 Class 类,包含了这个类的所有元数据信息
  • 常量:final static 修饰的变量
  • 静态变量:static 修饰的变量
  • 运行时常量池

运行时常量池

字符串常量池在 Java 7 之前是存储在永久代,在 Java 7 之后移到了堆空间中

经过编译后生成的 Class 文件除了包含类的版本、字段、方法、接口等描述信息,还包含一个静态常量池表,其中就存储在编译期间就能够确认的各种字面量和符号引用;在运行阶段会讲静态常量池的字面量加载到运行时常量池中,符号引用则是在经过链接、解析之后转化成直接引用,也会被加载到运行时常量池中。

A. 字面量

简单来理解就是编译期可以确定的「常量」值;不会随着运行变化的值

  • 字符串字面量:如 “Hello, World!”。
  • 数值字面量:如整数 42 或浮点数 3.14。
  • 字符字面量:如 ‘A’。
  • 布尔字面量:如 true 或 false。
  • 类或接口的字面量:如使用 Class.forName(“java.lang.String”) 时,字符串 “java.lang.String” 就是一个类字面量。
B. 符号引用

符号引用简单来讲就是用来在编译期用来唯一标识类、接口、方法、字段;因为编译期还没有实际分配内存,所以用符号引用来「暂替」,在经过解析之后就会转化成实际指向内存的地址,称为「直接引用」,而直接引用也会被存储在运行时常量池中,直接进行使用。

  • 类和接口的符号引用:描述了类或接口的完全限定名,例如 java/lang/String
  • 字段的符号引用:包括字段所属的类或接口的符号引用、字段名称和字段描述符(即字段的类型签名)。
  • 方法的符号引用:类似于字段,它包括方法所属的类或接口的符号引用、方法名称和方法描述符(即参数列表和返回类型)。