2Java内存区域

Java内存区域

运行时数据区域

程序计数器

  • 当前线程所执行的字节码的行号指示器。如果执行的是本地方法,计数器为空。每条线程都需要一个独立的程序计数器
  • 唯一一个没有规定任何OutOfMemoryError情况的区域
  • 为0时,可能在执行本地方法。

Java虚拟机栈

  • Java方法执行的内存模型:每个方法在执行的同时,都会创建一个栈帧(Stack Frame:方法运行时的基础数据结构)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
  • 栈内存所说的就是虚拟机栈中的局部变量表部分。
  • 线程私有的
局部变量表
  • 局部变量表:存放了编译期可知的各种基本类型,对象引用(不等同于对象本身,可能是一个指向对象原始地址的引用指针。也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
  • long和double都占用了两个局部变量,其他占用了一个。在编译期间完成内存空间的分配。运行期间不会改变局部变量表的大小。
操作数栈
  • 每个帧包含一个后进先出的栈,用于存储正在执行的jvm指令的操作数,这就是都熟知的操作数栈,这个栈的最大深度在编译时就已确定,随着编译后的方法代码一起提供。
  • 当帧被创建时,操作数栈是空的,jvm提供一些指令用于加载常量值,本地变量值,字段值到操作数栈上,另一些jvm指令采用操作数栈上的操作数进行操作,并把结果放回到操作数栈上
  • 操作数栈也用于准备将要传递给方法调用的参数和接收方法调用返回的结果。
动态连接
  • 在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池。符号引用的形式可以粗略的认为是字符串的形式,就是用字符串标明需要调用哪个类的哪个方法或访问哪个字段或变量。就像符号引用这个名字一样,这些仅仅是符号,是拿不到具体值的,所以必须要进行转换。
  • Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的**动态连接(Dynamic Linking)**。动态链接就是把这些符号方法引用转换为具体的方法引用,在必要时加载类来解析尚未明确的符号,把符号变量的访问转换为这些变量运行时所在存储结构的适合的偏移量(索引)
  • 这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引用,这类转化称为动态连接。
异常状况
  • StackOverflowError
    • 线程请求的栈的深度大于虚拟机所允许的深度
  • OutOfMemoryError
    • 虚拟机栈动态扩展时,无法申请到足够的内存。

本地方法栈

  • 虚拟机使用到的Native服务。异常与Java虚拟机栈一致。

Java堆(GC堆)

  • 被所有线程共享,存放对象的实例。
新生代和老年代
  • 因为分代收集算法,可以分为新生代和老年代。
    • 新生代:1/3的堆内存空间
      • eden区: 8/10 的新生代空间o
      • survivor0:1/10的新生代空间
      • servivor1:1/10的新生代空间
    • 老年代:2/3的堆内存空间

方法区(Non-Heap,永久代)

  • 线程共享,用于存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
  • 1.7以后已经将放在永久代的字符串常量池移到堆中,1.8移除了永久代,取而代之是元空间的区域,运行时常量池位于元空间中
  • 1.7后,符号引用(Symbols)移至native heap,字面量(interned strings)和静态变量(class statics)移至java heap。
元空间
  • 元空间并不在虚拟机中,而是使用本地内存,本地内存中还包含直接内存。
  • 会触发FGC清理
class常量池
  • 用于存放编译器生成的各种字面量(文本字符串、被声明为final的常量、基本数据类型的值)和符号引用(类和接口的全限定名、字段的名称和描述符、方法的名称和描述符)。
  • 位于class文件中,1.7位于方法区,1.8位于堆中。与字符串常量池一致。
运行时常量池
  • 当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个
  • 用于存放编译期生成的各种字面量和符号引用。
  • 1.7位于方法区,1.8位于元空间。
  • 具有动态性,

直接内存

  • 如果启动时未设置则默认为最大堆内存大小
堆外内存的回收
  • 自动回收
    • 它是 由 GC 模块负责的,在 GC 时会扫描 DirectByteBuffer 对象是否有有效引用指向该对象,如没有,在回收 DirectByteBuffer 对象的同时且会回收其占用的堆外内存。
    • Cleaner 继承了 PhantomReference(虚引用),当 GC 某个对象时,如果有此对象上还有虚引用对其引用,会将 虚引用对象插入 ReferenceQueue 队列,队列属于Reference 对象 。Reference 类内部 static 静态块会启动 ReferenceHandler 线程,线程优先级很高,这个线程是用来处理 JVM 在 GC 过程中交接过来的 reference, 在队列中调用了cleaner 的 clean 方法,clean方法调用unsafe.freeMemory 释放内存
  • 手动回收
    • 由开发手动调用 DirectByteBuffer 的 cleaner 的 clean 方法来释放空间。由于 cleaner 是 private 反问权限,所以自然想到使用反射来实现
    • 另外一种方法:DirectByteBuffer 实现了 DirectBuffer 接口,这个接口有 cleaner 方法可以获取 cleaner 对象。

2Java内存区域
https://x-leonidas.github.io/2022/02/01/04Java/JVM/2Java内存区域/
作者
听风
发布于
2022年2月1日
更新于
2025年6月25日
许可协议