JVM内存模型
对于JVM来说,在哪个区就采取相应的垃圾回收机制
对于s0和s1,在同一个时间点,只有一个是启动的,另外一个是空的
Student stu = new Student();
对于上面代码,new出来的对象stu,首先会分配在eden区,如果eden区满了就会触发第一次GC,这次GC会把活的对象拷贝到s0
如果eden区第二次满了,那么会再次触发GC,此时,eden和from区会再次被扫描,对这两个去进行垃圾回收,如果还有存活的,就把它们全都放在s1区
每扫一次,对象年龄会+1,JDK8,默认是15次,也就是说,s0和s1进行互换的时候,原来的s0经过GC就会变成s1,这样对象就会复制过来,复制过去。当你的年龄到了15,就会把它干到老年代。也有可能你的对象一进来就比较大,也有可能会把它直接干到老年代。
三个需要注意的参数
- -XX:SurvivorRatio=8 eden:s0:s1 = 8
- -XX:NewRatio=2 新生代和老年代在堆中的内存占比
- -XX:MaxTenuringThreshold=15 如果MaxTenuringThreshold=0,就是年轻代的对象不经过survivor,直接进入老年代
真正要做GC的是在堆区
非堆区是独立于堆之外的,是不受JVM的管控
Metaspace存放Class Package Method Field 字节码 常量池等等
JMM Java内存模型
先要明确哪些对象需要回收
- 引用计数
父类引用指向子类对象,体现了Java的多态
如果B里有A,A里有B就相当于死锁了,循环引用,就不能标记为垃圾 - 枚举根节点可达性分析,从根节点开始的可达性分析
根节点有很多类型的
CG算法
- 标记-清除 Mark-Sweep
- 复制 Copy
- 标记-整理 Mark-Compact,基于标记-清除做的优化
- 分带 是前面三个的整合
下面分别说说这四种垃圾回收机制
标记-清除 Mark-Sweep
- 标记:从GC Roots开始找,找到所有存活的对象并标记
- 清除:将没有标记的对象干掉
性能比较高
存在的问题:所有的内存都是碎的,内存碎片
复制 Copy
复制会把内存划分为两个区域,统一时间点,只有一个是活动的,当我们的内存不够的时候,JVM会停止我们的应用程序,然后启用复制算法
GC线程会将活动区域的存活对象全部复制到空闲区域,内存地址是排序的
这样布局就没那么混乱
缺点:真正能用的内存只有一半
对于90%的对象,自创建开始没一会就会被销毁的
适用于新生代GC
标记-整理 Mark-Compact
- 标记
- 整理 会对内存的地址做一次排序
弥补了标记-清除内存区比较分散的缺点
跟Copy相比,Copy的性能会更好一些
分代
前面三者的综合使用,比如说新生代采取什么算法,老年代又是采取什么算法
新生代使用复制
老年代使用标记-清除或者标记-整理
结合Spark来说说线上遇到的OOM异常以及解决方案
- Spark内存模型
- Spark架构
- JVM OOM