第一步那些对象是垃圾
1引用计数法通过对引用的遍历找到对应的实例让对应的实例计数加 1 如果引用取消或者指向null实例的引用减 1 。把找到的引用都遍历一遍之后如果发现有对象实例的计数是0。那么这个对象 就是垃圾对象了。在通过垃圾回收算法对其进行 回收即可。
缺点想想一下有两个类互相引用也就是A对象的实例也就是对象的全局变量是一个指向B对象的引用B对象实例是一个指向A对象的引用。那么这两个对象的引用计数永远不可能是0 。也就不可能对其进行回收了。
2可达性分析法这个算法类似于树的遍历学过数据结构的小伙伴应该会好理解。简单来说按照一定的规则说明那些可以作为一个根节点GC root然后以这些根节点去访问其引用的对象被访问的对象又会有其他对象的引用。想象一下是不是像极了树的遍历。这个路径称作引用链但凡是在引用链上的对象都是可用的。注意引用连的起始点都是GC root 哦。虽然有其他对象存在类似于引用链的结构但是起始点不是GC root的那一些都是垃圾可以被回收的。
一般情况下都是使用的 可达性分析法去查找垃圾类实例。
GC root哪些对象会被认为是root
GC root的查找规则java栈中的引用方法区中的静态属性静态变量 静态常量方法区中常量引用的对象方法区中有个结构 叫做 常量池 存储的一部分是常量本地方法线程独占区中有个结构叫做 本地方法栈。
jvm里面有一个存储虚拟s1和s2
年轻代里面有一个复制算法这个就要说到
第二步垃圾回收器算法标记-清除、复制算法、标记-整理、分代算法
1标记-清除:找到垃圾类之后标记一下。然后直接 清除即可。算法很快
缺点产生空间碎片不利于大对象的安排进去。
2,复制算法将内存分为四块新生代Eden生存代Survivor * 2老年代。有五种内存分配策略讲完之后再说。类的升级流程是Eden->Survivor->老年代
算法流程1先找到垃圾类将可以使用的类移动到Survivor2将Eden 另一块Survivor1中的内存全部清除。
2将新生成的类实例优先分配到Eden分配不下时放到Survivor2。进行GC时将Survivor2中对象的满足一定条件例如对象年龄达到某一个标准的对象分配到老年代中。将本次GC存活下来的分配到Survivor1中在清除Eden Survivor2 。依次循环即可。
缺点很容易发现吧Survivor中每次都会浪费一个Survivor的内存没有使用所以为了减少浪费一般将Eden的内存扩大Survivor的内存设置小一点。例如HotSpotHotSpot是8中的jvm默认虚拟机 中设置的是 8 1 1
3标记-整理看名字是不是感觉很熟悉没错。跟标记-清除很像也是直接标记。改算法使用到了前面两个算法的精华改善了缺点。
算法流程1直接标记
2集中无缝隙的移动到一端此时会发现剩下的垃圾类都会在其他地方。移动完成之后就会发现有一个边界就是可用类跟其他空间的一个边界下一步直接把边界以外的空间直接清除掉就可以了。
缺点看起来很完美但是越完美的往往在时间上过不去。
4分代算法根据在哪里清除选用算法不一样。
算法流程1新生代采用复制算法
2老年代采用标记-清除算法老年代GC很少访问类也很少去直接分配到里面内存碎片的可怕性就显得不那么重要了
什么样的数据会往老年代里面迁移呢
每次进行垃圾回收的时候比如说当前这个对象存活下来了计数器就会给他1默认的话是当计数器达到16的时候就会放到老年代里面
GC的作用域
JVM内存分配原则
1对象优先分配到Eden区域
2大对象直接分配到老年区大对象的就是对象里面有很大数组或者很大的字符串
3长时间存活的对象存入老年区就是上面复制算法里面说的那个对象升级流程
4动态对象年龄判定jvm并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才可以进入老年代如果Survivor空间中年龄相同的所有对象的总空间>本servivor中的一半那么年龄>本年龄的对象可以直接进入老年区
5空间分配原则简单来说就是在发生Minor GC在新生代进行GC情况下为了防止发生在Minor GC后Eden有大量存活的对象导致survivor不能全部存入这时需要老年代去担保把这些对象放入老年代但是要确保老年要存的下。
1再发生Minor GC之前检查老年区的可用的连续空间是否是大于新生代Eden的所有对象的总空间如果是直接全部晋升老年代保证Minor GC的安全
2如果不行就检查HandlePromotionFailure可以手工设定参数时候允许担保失败允许的话直接分配。不能的话发生一次full GC或者是Major GC 在老年代进行GC。
3不允许担保失败发生一次 full GC。
为什么不直接进行full GC 因为速度慢呀。而且经常GC 也 效果不大因为老年代都是一些长期存活的对象。
如果老年代内存也不够用了怎么办呢
他会进行fullGC
fullGC的时候会有什么现象吗有没有遇到到fullGC的时候影响业务的场景
如果频繁的fullGC会出现cpu内存飙升的问题
收集器有Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1G1是目前最好的收集器
上图是HotSpot的垃圾收集器的使用范围HotSpot是现在主流的 jvm。
CMS
CMSConcurrent Mark Sweep一种以获得最短停顿时间为目标的收集器非常适用B/S系统。
使用 Serial Old 整理内存。
CMS 运行过程
1、初始标记
标记 GC Roots 直接关联的对象需要 Stop The World 。
2、并发标记
从 GC Roots 开始对堆进行可达性分析找出活对象。
3、重新标记
重新标记阶段为了修正并发期间由于用户进行运作导致的标记变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长一些但远比并发标记的时间短也需要 Stop The World 。
4、并发清除
除垃圾对象。
CMS 缺点
1、对 CPU 资源要求敏感。
CMS 回收器过分依赖于多线程环境默认情况下开启的线程数为CPU 的数量 3/ 4当 CPU 数量少于 4 个时CMS 对用户本身的操作的影响将会很大因为要分出一半的运算能力去执行回收器线程。
2、CMS无法清除浮动垃圾。
浮动垃圾指的是CMS清除垃圾的时候还有用户线程产生新的垃圾这部分未被标记的垃圾叫做“浮动垃圾”只能在下次 GC 的时候进行清除。
3、CMS 垃圾回收会产生大量空间碎片。
CMS 使用的是标记-清除算法所有在垃圾回收的时候回产生大量的空间碎片。
注意CMS 收集器中当老生代中的内存使用超过一定的比例时系统将会进行垃圾回收当剩余内存不能满足程序运行要求时系统将会出现 Concurrent Mode Failure临时采用 Serial Old 算法进行清除此时的性能将会降低。
线程类型 多线程
使用算法 标记-清除
指定收集器 -XX:UseConcMarkSweepGC
G1
G1 GC 这是一种兼顾吞吐量和停顿时间的 GC 实现是 JDK 9 以后的默认 GC 选项。G1 可以直观的设定停顿时间的目标相比于 CMS GCG1 未必能做到 CMS 在最好情况下的延时停顿但是最差情况要好很多。
G1 GC 仍然存在着年代的概念但是其内存结构并不是简单的条带式划分而是类似棋盘的一个个 region。Region 之间是复制算法但整体上实际可看作是标记 - 整理Mark-Compact算法可以有效地避免内存碎片尤其是当 Java 堆非常大的时候G1 的优势更加明显。
G1 吞吐量和停顿表现都非常不错并且仍然在不断地完善与此同时 CMS 已经在 JDK 9 中被标记为废弃deprecated所以 G1 GC 值得深入掌握。
G1 运行过程
1、初始标记
标记 GC Roots 直接关联的对象需要 Stop The World 。
2、并发标记
从 GC Roots 开始对堆进行可达性分析找出活对象。
3、重新标记
重新标记阶段为了修正并发期间由于用户进行运作导致的标记变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长一些但远比并发标记的时间短也需要 Stop The World 。
4、筛选回收
首先对各个 Region 的回收价值和成本进行排序根据用户所期望的 GC 停顿时间来制定回收计划。这个阶段可以与用户程序一起并发执行但是因为只回收一部分 Region时间是用户可控制的。
线程类型 多线程
使用算法 复制、标记-整理
指定收集器 -XX:UseG1GCJDK 7u4 版本后可用
【文章转自韩国多IP服务器 http://www.558idc.com/krzq.html 复制请保留原URL】