Java内存区域
五大运行时数据区+直接内存
Java运行时数据区:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 堆
- 方法区
程序计数器
- 是当前线程所执行的字节码的行号指示器
- 通过改变这个值来取下一条指令从而实现字节码解释器的运转
- 每条线程都要有一个独立地程序计数器(因为Java是时间片轮转调度,需要保存一些信息)
- 执行Java方法时PC指向正在执行的字节码指令的地址,执行Native方法时为空
- 唯一一个不会抛出异常的地方
Java虚拟机栈
- 线程私有,生命周期与线程相同
- 栈帧从入栈到出栈,代表一个方法从调用到执行完成的过程
局部变量表保存基本数据类型与对象引用类型,是栈帧的重要组成
- long与double占用两个空间,其他都是一个
- 方法运行期间不会改变局部变量表的大小
关于异常
- 线程请求的栈深度大于JVM所允许的深度,抛出
StackOverflowError
异常 - 如果可动态拓展虚拟机栈,且拓展时无法申请到足够的内存时,抛出
OutOfMemoryError
异常
- 线程请求的栈深度大于JVM所允许的深度,抛出
本地方法栈
- 和虚拟机栈的作用类似
- 唯一的区别是虚拟机栈为执行Java方法(字节码)服务,本地方法栈为Native方法服务。
- 同样也会抛出上面两个异常
- 有的虚拟机会把这两个栈合二为一
- 异常和上面的虚拟机栈一样
Java堆
- 是所有线程共享的一块内存区域,在JVM启动时创建
- 存放的都是对象实例
- 是垃圾回收管理的主要区域
- 物理上不连续,逻辑上连续
- 主流的虚拟机都是按照可拓展来实现的,拓展不了抛出OOM异常
方法区
- 各个线程共享
- 存储JVM加载的类信息、常量、静态变量、即时编译后的代码数据
- 有个别名叫非堆
- 不需要物理上连续,逻辑上连续
- 内存回收主要目标是常量池的回收和类型的卸载
- 可以选择不实现垃圾收集,但是回收是必要的
- 可拓展,内存不够时抛出
OutOfMemoryError
异常
方法区——运行时常量池
运行时常量池是方法区的一部分,Class文件除了版本字段方法接口等信息外,还有一项是常量池,用于存放编译期生成的各种字面量和符号引用。
相对于Class文件常量池的另外一个重要特征是具备动态性,并非Class文件中常量池的内容才能进入运行时常量池,运行期间可以将新的常量放入池中
应用较多的就是String.intern()
方法,为native方法。当常量池存在当前字符串时直接返回,不存在时把字符串放入常量池后再返回
直接内存
- 不属于JVM运行时数据区的一部分
- JDK1.4后可以直接使用NIO类通过native函数库来直接分配堆外内存
总结起来的关系如下图所示
对象的访问
Java程序通过栈上的reference
数据来操作堆上的具体对象,目前主流的访问方式有使用句柄访问和直接指针访问两种
使用句柄访问时,会在堆中划分出一块内存来作为句柄池,储存对象的句柄地址。句柄地址包括了对象实例数据与类型数据各自的地址信息。
- 优点是句柄地址稳定,在对象被移动(垃圾收集常见)时只会改变句柄中的指针,而不会改变
reference
数据。
- 优点是句柄地址稳定,在对象被移动(垃圾收集常见)时只会改变句柄中的指针,而不会改变
使用指针访问时,
reference
数据存储的是对象地址而不是句柄池地址,具体对象类型数据由对象内部决定- 优点是速度更快(节省了一次指针定位的时间开销)
HotSpot虚拟机是第二种方式进行对象访问的
其详细图如下所示