一.如何判断对象为垃圾对象?
1.引用计数法
在对象中添加引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,当引用失效的时候,计数器的值就-1
(1)idea中打印GC日志的方法:
添加:-XX:+PrintGCDetails
(2)没有虚拟机采用这种算法回收垃圾,因为出现对象之间互相引用的时候,即使栈中的引用不指向该对象了,引用计数器的值仍不为0
2.可达性分析法
(1)可作为GCRoot:虚拟机栈 方法区中类属性,常量引用的对象 本地方法栈引用的对象
(2)从GCRoot节点向下搜索,当某个对象对GCRoot节点没有任何引用链相连接,就可以被垃圾回收器回收
二.如何回收
1.回收策略
(1)标记-清除算法
标记:标记的过程其实就是遍历所有的GC Roots,然后将所有的GC Roots可达的对象标记为存活的对象
清除:清除的过程将遍历堆中所有的对象,然后将没有标记的对象全部清除掉
带来的问题:效率问题和空间问题-->造成内存区块不连续,当有大的对象需要开辟内存,可能找不到一块足够大的连续的内存空间,然后会再次调用垃圾回收器进行回收,影响效率
(2)复制算法
就是为了解决标记-清除算法产生的碎片
jvm将堆分为新生代和老年代,又将新生代划分为Eden(伊甸园)和两块survivor space(幸存者区)
算法流程
1、当Eden区满的时候,会触发第一次young gc,把还活着的对象拷贝到Survivor From区(如过存不下,触发老年代的分配担保,直接将对象存到老年代);当Eden区再次触发young gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。
2、当后续Eden又发生young gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
3、可见部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代
(3)标记-整理算法
标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段
(4)分代收集算法
根据内存分代选择不同的收集算法
新生代中:每次垃圾回收都会发现有大批对象死去,那么就采用复制算法
老年代中:对象成活率高,没有额外空间对它进行分配担保,采用标记-清除或标记整理算法
2.垃圾回收器
(1)seria
最基本,发展最悠久的(采用复制算法)
单线程的垃圾收集器(gc线程在执行的时候用户线程停止)
应用:桌面应用(占用内存小,所以收集会很快,用户无感知)
(2)parnew
多线程收集器(采用复制算法)
多个收集器并发执行,减少 了收集时间
(3)CMS(concurrent mark sweep)
用于对老年代的回收(标记-清除算法)
默认采用parnew作为新生代回收器
工作过程:初始标记 并发标记 重新标记 并发清理
优点:并发收集 低停顿
缺点:占用大量CPU 无法处理浮动垃圾 空间碎片
(4)parallel
多线程收集器(复制算法),新生代收集器
达到可控制的吞吐量(也就是可以控制垃圾回收的时间)
吞吐量 = 用户执行程序所用时间/(用户执行程序所用时间+垃圾回收时间)
(5)G1收集器
过程:(1)初始标记 (2)并发标记 (3)最终标记 (4)筛选回收
G1垃圾收集器采用的是区域化,分布式的垃圾收集器,其核心思想是将整个堆内存划分为大小相同的子区域(region),这样Eden,Survivor,Tenured就变为了一系列不连续的内存区域,也就避免了全内存的gc操作
G1中不再区分所谓的年轻代,老年代内存空间
优点:能够像CMS收集器一样跟应用线程并行操作,更加精准的预测GC停顿时间