走进JVM的世界
JVM构成
- 堆(Heap)
- JVM栈
- 本地方法栈
- 程序计数器
- 元空间(JDK8之前为Perm区 永生代区)
1. 堆(Heap)
1.1 简介
- 存储着几乎所有的实例对象,发生OOM最主要的发源地。
- 由垃圾回收器自动回收,各个子线程共享
1.2 参数调整
可以添加JVM参数对堆区大小进行指定
- -Xms xxxM (memory start)
- -Xmx xxxM (memory max)
为了避免堆空间频繁的扩容回缩,一般Xms和Xms设置成一样的大小
1.3 堆被划分为
- 新生代区域
- 老年代区域
其中 新生代分为
- Eden区
- Survivor区
其中 Survivor区 又分为S0/S1两个区域
为什么要设置两个Survivor区?
没有Survivor区,eden区对象直接去老年代,造成老年代频繁FGC。
一个Survivor容易使得Survivor的空间碎片化,因为eden区又存活对象,Survivor区也有存活对象,辅助的时候就会存在碎片化空间。
即若只分一块Survivor,在清除Survivor区已死亡的对象时,因为此刻的Survivor区还有存活的对象,清除要比分两块Survivor麻烦,两块的情况,回收时只需将存活的对象移走,剩下的对象直接清理即可。
另外,分成两块Survivor,From和To分工明确,逻辑理解和技术实现较简单。
1.4 内存申请流程
- 内存申请
- 先到Eden区申请内存空间 如果申请成功 则分配成功
- 申请失败会触发一次Young Garbage Collection即 YGC
- 再次去Eden区申请 如果申请成功 则分配成功
- 申请老年代区域内存空间 如果申请成功 则分配成功
- 触发一次Full Garbage Collection即 FGC
- 再次申请老年代内存空间 如果申请成功 则分配成功 申请失败则发生OOM
- YGC
- 旧对象是否能够在Survivor区存储 如果过放得下,未超过存活阈值,则放置在S0/S1区,并且计数器+1
- 如果放不下或者超过对象存活阈值晋升至老年代
- 流程图
- 内存申请
- YGC
- FGC
用于清理整个堆空间。
- JVM参数
- -XX:MaxTenuringThreshold 调整新生代转至老年代的存活阈值
- -XX:+HeapDumpOnOutOfMemoryError JCM遇到OOM输出堆内信息
2.JVM栈
2.1 简介
JVM栈是描述方法执行的内存区域 线程私有
包含局部变量表、操作栈、动态链接、方法返回地址4个部分
位于栈顶的帧才是有效的,称为当前栈帧。
栈帧是方法运行的基本结构。
是发生StackOverFlow的区域。(递归调用导致JVM栈内存溢出)
2.2 局部变量表
输入参数和输出参数以及方法内的变量
2.3 操作栈
记录出栈、入栈的操作
2.4 动态链接
每个栈帧中包含一个在常量池中对当前方法的引用,目的是支持方法调用过程的动态连接
2.5 方法返回地址
执行过程中成功或异常需执行的字节码指令
3.本地方法栈
与操作系统交互相关的操作
4.程序计数器
- 简介
程序计数器的作用是在一些指令的执行过程中, 记住下一条JVM指令的执行地址。
- 特点
- 线程私有
- 不会出现内存溢出
5.元空间
- 简介
存储包括类元信息、字段、静态属性、方法、常量等信息
- 升级历史
jdk8之前永生代存储在JVM中的方法区来进行垃圾回收 JDK8之后元空间取代了方法之前的方法区
并且将永生代中的常量池,静态变量并入堆内
6.垃圾回收
- 简介
垃圾回收的主要目的是清除不再使用的对象,自动释放内存
- 哪些对象会被垃圾回收
现代虚拟机基本都是采用可达性分析算法来判断对象是否存活,可达性算法的原理是以一系列叫做 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,引出此节点指向的下一个结点。这样通过 GC Root 串成的一条线就叫引用链),直到所有的结点都遍历完毕,如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为垃圾对象,会被 GC 回收。
6.1 垃圾回收器
Serial
- 执行YGC时使用Mark-copy算法,执行FGC时采用标记-整理算法
- 特点:主要应用于YGC,串行执行回收,影响程序运行会产生Stop The World(STW) FGC触发会长时间影响程序运行
CMS
- 执行流程: 初始标记->并发标记->重新标记->并发标记
- 由于CMS采用标记-清除算法,因此会产生大量内存空间碎片。为了解决这个问题可以配置JVM在执行FGC后对老年代进行压缩,执行一次空间整理,整理阶段也会触发STW。为了减少STW次数还可以配置在N次FGC后再执行碎片整理。
- 特点: 目前较常用的垃圾回收器
- JVM参数调整:
- -XX:+UseCMSCompactAtFullCollection 调整FGC后强制执行空间整理
- -XX:+CMSFullGCsBeforeCompaction=n n次FGC后执行空间整理
G1
- 简介
G1将内存空间分割成了若干相同大小的区域,即region,包括Eden,Survivor,Old,Humongous四种类型
其中Humongous专门用来存储大型对象
这样做的好处是不需要连续的内存空间来管理对象
G1将内存内存划分为多个区域,优先回收垃圾最多的区域
- 特点:
- G1采用Mark-Copy算法有非常好的内存整合能力,不会产生大量的内存碎片。
- 并发处理垃圾
- 执行流程:
- Initial Mark 其实就是YGC。该阶段会引起STW,标记GC Roots直接可达的存活对象
- Root Region Scan 根区域扫描,该阶段不会STW,它会并发的从上一阶段标记的存活区域中扫描被引用的老年代对象。
- Concurrent Mark 并发标记。该阶段从堆中标记存活的对象。
- Remark 重新标记 该阶段会引起STW 完成最终的标记处理
- Cleanup 主要为接下来的Mixed GC作准备。该阶段会统计所有堆区域的存活对象,并将待回收的区域进行回收价值排序,优先回收垃圾最多的区域。
6.2 垃圾回收算法
- 标记-清除
- 简介
该算法分为标记和清除两个阶段。标记就是把所有活动对象都做上标记的阶段;清除就是将没有做上标记的对象进行回收的阶段。
- 特点
- 实现简单
- 由于分配一个较大的连续空间时容易触发FGC,会产生大量的内存空间碎片
- 标记-复制
- 简介
复制算法就是将内存空间按容量分成两块。当这一块内存用完的时候,就将还存活着的对象复制到另外一块上面,然后把已经使用过的这一块一次清理掉。这样使得每次都是对半块内存进行内存回收。内存分配时就不用考虑内存碎片等复杂情况,只要移动堆顶的指针,按顺序分配内存即可,实现简单,运行高效。
- 标记-整理
与标记-清理算法类似,后续清除阶段为将存活的对象移动到一端,形成连续的内存空间,然后清除掉边界以外的内存。