JVM数据区域
- 虚拟机栈(每个方法执行都会创建一个栈帧,包括局部变量表,操作数栈,动态链接,方法出口等信息,方法的执行就是入栈到出栈的过程)
- 本地方法栈(native方法)
- 堆(存储的对象)
- 方法区(存储类信息,静态变量,常量等,运行时常量池)
- 寄存器(线程所执行字节码的行号指示器)
- 直接内存(NIO)
对象已死?
- 引用计数算法:对象被引用,计数+1,引用失效,计数-1,简单高效,但不能解决循环引用问题
- 可达性分析算法:从一系列GC ROOTS对象开始,根据引用关系往下搜索,搜索过得路径称为引用链,如果一个对象到GC ROOT之间没有一条引用链相连,也就是GC ROOT不可达时,证明该对象不再引用。
常用的GC ROOT:栈中引用的对象;方法区中的引用类型静态变量;方法区中的常量;被同步锁持有的对象 - Java中的引用类型
- 强引用:new Object();不会回收
- 软引用: SoftReference;系统要发生垃圾回收时回收
- 弱引用: WeakReference;下一次垃圾回收时回收
- 虚引用: PhantomReference;垃圾回收时通知
对象进入老年代时机
- 大对象直接进入老年代
- 新生代回收时from或to区装不下直接进入老年代
- 超过分代年龄,默认15
- 动态年龄判断:survivor区所有年龄的对象相加超过survivor区的一半,大于该分代年龄的直接进入老年代
什么时候FULL GC
- 老年代满了
- System.gc()
- 分配担保:老年代连续内存空间小于历次晋升到老年代的平均对象大小
垃圾收集算法
-
分代收集理论:
- 绝大多数对象都是朝升夕灭的
- 熬过垃圾次数对象越多的对象越难收集
- 跨代引用相对于同代引用仅占极少数(依据这条假说,我们不应该为了少量的跨代引用去扫描整个老年代,只需要在新生代建立一个全局的数据结构:记忆集,将老年代分成若干小块,标记那个小块有可能会发生跨代引用,需要去维护引用关系)
-
标记-清除算法:执行效率不稳定,会产生空间碎片
-
标记-复制算法:Eden区和Survivor区,8:1,每次分配在Eden区和一块Survivor区,收集时把存活对象移到另一块Survivor区,然后清除
-
标记-整理算法: 将存活的对象往一端引,然后清除其余的对象,移动对象过程极为负重,需要停顿,最新的ZGC和Shenandoah通过读屏障可以实现整理和用户线程同时执行
JVM垃圾收集器
新生代:
- serial:单线程,简单高效,不占内存,复制算法
- ParNew:serial的多线程版本,复制算法,目前只有其可与cms配合
- Parallel Scavenge:多线程,关注吞吐量
老年代:
- serial old:单线程,标记-整理算法
- Parallel Old:多线程,关注吞吐量
- cms:关注与停顿时间,初始标记(stop)-并发标记-最终标记(stop)-并发清除,对处理器核心敏感,(处理器核心+3)/4,无法处理浮动垃圾,基于标记清除,会有内存碎片,需要去整理
G1:面向局部的设计思路和基于Regin的内存分布形式,JDK9完成,把java堆分成多个大小相等的连续区域(Region),根据需要去扮演新生代老年代的角色,Region里还有一块特殊的区域用来存储大对象(超过Region的一半),G1回去跟踪每个Region里的价值大小,根据用户设置的停顿时间,优先收集对应的Region区域,初试标记(stop)-并发标记-最终标记(stop)-筛选回收(stop),并非纯粹的追求低延时,而是在用户设定的停顿时间内尽可能的提高吞吐量,比较占内存
低延迟垃圾收集器(目标停顿时间控制在10ms以内):
- Shenandoah :RedHat开发,openjdk12开始,非Oracle官方,和G1相似,区别:筛选回收阶段并发执行,默认不是分代收集,通过连接矩阵解决跨代引用问题,通过读屏障,写屏障和转发指针解决并发清理和用户读的问题
- ZGC :在JDK14里依然处于试验状态,Region分区不再固定,分为小型中型和大型,通过读屏障和染色指针解决并发问题,低延迟,不支持分代收集
监控工具
- Jps:进程信息
- jstat:虚拟机统计信息 jstat -gc pid
- jinfo:配置信息 jinfo -flags pid
- jmap:生成堆栈 jmap -heap pid
- jstack:堆栈跟踪工具,线程快照 jstack -l pid
可视化工具
-
JConsole
-
VisualVm