Java内存区域

五大运行时数据区+直接内存

Java运行时数据区:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区

程序计数器

  • 是当前线程所执行的字节码的行号指示器
  • 通过改变这个值来取下一条指令从而实现字节码解释器的运转
  • 每条线程都要有一个独立地程序计数器(因为Java是时间片轮转调度,需要保存一些信息)
  • 执行Java方法时PC指向正在执行的字节码指令的地址,执行Native方法时为空
  • 唯一一个不会抛出异常的地方

Java虚拟机栈

  • 线程私有,生命周期与线程相同
  • 栈帧从入栈到出栈,代表一个方法从调用到执行完成的过程
  • 局部变量表保存基本数据类型与对象引用类型,是栈帧的重要组成

    • long与double占用两个空间,其他都是一个
    • 方法运行期间不会改变局部变量表的大小
  • 关于异常

    • 线程请求的栈深度大于JVM所允许的深度,抛出StackOverflowError异常
    • 如果可动态拓展虚拟机栈,且拓展时无法申请到足够的内存时,抛出OutOfMemoryError异常

本地方法栈

  • 和虚拟机栈的作用类似
  • 唯一的区别是虚拟机栈为执行Java方法(字节码)服务,本地方法栈为Native方法服务。
  • 同样也会抛出上面两个异常
  • 有的虚拟机会把这两个栈合二为一
  • 异常和上面的虚拟机栈一样

Java堆

  • 是所有线程共享的一块内存区域,在JVM启动时创建
  • 存放的都是对象实例
  • 是垃圾回收管理的主要区域
  • 物理上不连续,逻辑上连续
  • 主流的虚拟机都是按照可拓展来实现的,拓展不了抛出OOM异常

方法区

  • 各个线程共享
  • 存储JVM加载的类信息、常量、静态变量、即时编译后的代码数据
  • 有个别名叫非堆
  • 不需要物理上连续,逻辑上连续
  • 内存回收主要目标是常量池的回收类型的卸载
  • 可以选择不实现垃圾收集,但是回收是必要的
  • 可拓展,内存不够时抛出OutOfMemoryError异常
方法区——运行时常量池

运行时常量池是方法区的一部分,Class文件除了版本字段方法接口等信息外,还有一项是常量池,用于存放编译期生成的各种字面量和符号引用。

相对于Class文件常量池的另外一个重要特征是具备动态性,并非Class文件中常量池的内容才能进入运行时常量池,运行期间可以将新的常量放入池中

应用较多的就是String.intern()方法,为native方法。当常量池存在当前字符串时直接返回,不存在时把字符串放入常量池后再返回

直接内存

  • 不属于JVM运行时数据区的一部分
  • JDK1.4后可以直接使用NIO类通过native函数库来直接分配堆外内存

总结起来的关系如下图所示

JVM数据区域


对象的访问

Java程序通过栈上的reference数据来操作堆上的具体对象,目前主流的访问方式有使用句柄访问直接指针访问两种

  • 使用句柄访问时,会在堆中划分出一块内存来作为句柄池,储存对象的句柄地址。句柄地址包括了对象实例数据与类型数据各自的地址信息。

    • 优点是句柄地址稳定,在对象被移动(垃圾收集常见)时只会改变句柄中的指针,而不会改变reference数据。
  • 使用指针访问时,reference数据存储的是对象地址而不是句柄池地址,具体对象类型数据由对象内部决定

    • 优点是速度更快(节省了一次指针定位的时间开销)

HotSpot虚拟机是第二种方式进行对象访问的

其详细图如下所示

句柄方式

直接指针

Last modification:May 11th, 2021 at 10:06 pm