<<深入理解Java虚拟机>>第三版笔记-第二章
学习<<深入理解Java虚拟机>>第三版时做的笔记
文章目录
运行时数据区
程序计数器
- 线程私有
- 记录正在执行的虚拟机字节码指令的地址(如果是Native方法,则位空)
- 不会有OutOfMemoryError
虚拟机栈
- 线程私有
- 存储的是一个个栈帧
- 每个方法对应一个栈帧,方法调用则入栈,执行结束则出栈
- 可能出现*Error与OutOfMemoryError,前者出现是为栈分配了这么大的内存空间,但是线程请求的空间大于所分配的空间,例如不断递归。后者是允许栈动态扩展,但是扩展了还是无法申请到足够的内存,相当于把所有的内存都耗尽了
栈帧
局部变量表
- 局部变量槽(slot)中long和double占用两个槽
- 所需的内存空间在编译器间完成分配
操作数栈
动态连接
方法出口
本地方法栈
与虚拟机栈类似,只是虚拟机栈是为虚拟机执行Java方法服务,本地方法栈是为虚拟机执行本地方法(Native)服务。
堆
- 虚拟机管理的内存最大的一块
- 所有线程共享
- 存放几乎所有对象实例(因为逃逸技术。栈上分配,标量替换优化手段使得并不绝对)
- 堆中划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB),提高对象分配时的效率
- 物理上不连续,逻辑上连续
- 有可能导致OutOfMemoryError异常
方法区
- 线程共享
- 存储类型信息,常量,静态变量,即时编译器的代码缓存等
- 有可能导致OutOfMemoryError异常
- 垃圾收集行为比较少,但是存在,主要针对常量池的回收和对类型的卸载
运行时常量池
- 类加载后,Class文件的常量池表(编译期生成的各种字面量和符号引用)存放在运行时常量池中
- 具备动态性,允许期间也可以将新常量放入池中,例如String的intern()方法
直接内存
- 不是运行时数据区的一部分,但这部分内存经常使用,也有可能导致OutOfMemoryError异常
- 与NIO有相关
- 直接内存分配不受Java堆的限制,但是受本机总内存限制
HotSpot虚拟机中的对象
HotSpot虚拟机
热点代码探测能力:通过执行计数器找出最具有编译价值的代码,然后通知即时编译器以方法为单位进行编译.
编译器与解释器协同工作,在最优化程序响应时间与最佳执行性能中取得平衡
对象的创建
- 遇到字节码new指令时,先检查能否在常量池中定位到一个类的符号引用
- 检查符号引用代表的类是否已被加载,解析,初始化,如果没有则执行类加载过程
- 为对象分配内存,根据是否规整,有指针碰撞法,空闲链表法
- 内存分配还要考虑线程安全问题,可以有两种办法解决:分配内存空间进行同步处理,如CAS配上失败重试。或者可以先分配到每个线程的TLAB中,当本地缓冲区用完后,再同步锁定
- 初始化为零值,所以实例字段不赋初始值也能用
- 设置对象头(Object Header),如锁信息,哈希码,GC分代年龄等
- 执行构造函数 ()
对象内存布局
对象头,实例数据,对齐填充
对象头
- 分为两类,第一类为Mark Word,第二类是类型指针
- Mark Word存储对象自身的运行时数据,如HashCode,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等
- Mark Word有着动态定义的数据结构,根据对象的状态复用自己的存储空间,根据标志位的不同,存储内容也不同。
- 类型指针是指向他的类型元数据的指针,通过该指针确定对象是哪个类的实例
- 如果是数组,还有一块记录数组长度的数据
实例数据
对象真正存储的有效信息,包括定义的各种类型的字段内容,父类继承下来的字段等等
对齐填充
确保对象是8字节的整数倍
对象访问定位
通过栈上的reference数据来操作堆上的具体对象,访问方式有下面两种:
句柄访问,直接指针访问,前者在对象改变时,reference本身不需要修改。后者节省一次指针定位的内存开销,速度更快。HotSpot虚拟机采用直接指针方式。
# 实战
idea配置:
Run-Edit Configurations-Templates-Application-VM options
## 堆的OOM
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
解释:设置堆的最小值,最大值一样即可避免堆自动扩展。设置了堆大小为20MB,且出现内存溢出异常Dump出当前的内存堆转储快照以便进行事后分析
分析是内存泄漏还是内存溢出.
内存泄漏
则查看泄漏对象到GC Roots的引用链,查看为什么垃圾回收器无法回收他们
内存溢出
即内存对象确实是必须存活的,则首先看看堆参数设置是否合理(-Xmx,-Xms),能否再向上调整。再从代码上检查,减少程序运行期的内存消耗。
虚拟机栈和本地方法栈的溢出
- HotSpot虚拟机不区分这两者,栈容量只能通过-Xss来设置
- *Error与OutOfMemoryError异常,但是HotShot不支持栈内存动态扩展,所以除非创建线程申请内存时就因无法获取足够的内存报OOM,其他只会因为线程请求的栈的深度大于虚拟机所允许的最大深度而报*Error异常
方法区和运行时常量池的溢出
JDK7以后,运行时常量池移至堆之中,主流的框架经常在运行时生成大量动态类的应用场景,需要特别关注这些类的回收状况。但是类被回收的条件很苛刻。
JDK8的元空间中,很少出现方法区的溢出异常,但是还有参数可以设置:
- -XX:MaxMetaspaceSize 设置元空间最大值,默认是-1,即不限制,只受本地内存的影响
- -XX:MetaspaceSize 设置初始空间的大小,达到该值则触发垃圾收集进行类型卸载
直接内存溢出
容量可以通过-XX:MaxDirectMemorySize指定,默认与堆最大值一致。
内存的影响
2. -XX:MetaspaceSize 设置初始空间的大小,达到该值则触发垃圾收集进行类型卸载
直接内存溢出
容量可以通过-XX:MaxDirectMemorySize指定,默认与堆最大值一致。
间接使用直接内存的典型就是NIO