判断是否垃圾
引用计数
通过添加一个引用计数器,引用了加1,引用失效减1。引用计数为0则判断为垃圾。但无法收集循环引用的对象。所以一般不采用此方式。
可达性分析
通过一系列称为GCRoots的根对象,根据引用向下搜索。如果对象没有可达的GCRoots则标记为垃圾。
可作为GCRoots的对象:
1、虚拟机栈栈帧中局部变量表里的变量。
2、java类中静态变量。
3、常量池中的引用。
4、类加载器
5、synchronized关键字持有的对象。
引用类型
强引用:Object j = new Object();这种赋值就是强引用。垃圾收集器永远不会回收强引用对象,即使发生OOM。
软引用:软引用对象会在系统发生内存溢出之前,进行回收。
弱引用:下次发生垃圾收集就进行回收。
虚引用
finalize()拯救对象
判断对象是否进行垃圾回收会经过两次标记。一次是GCRoots的可达性分析。一次是是否执行对象的finalize(),并且重新获得引用。
假如对象没有重写finalize(),或者finalize()已经执行过了,finalize()在系统中只会执行一次。都不会执行该方法。
方法区的回收
判断一个类信息是否要进行回收需要同时满足以下条件:
1、该类所有实例都已经进行回收。
2、加载该类的类加载器已经进行回收。
3、该类的class对象没有任何地方引用比如反射
垃圾收集算法
标记清除
算法分为标记、清除两个阶段。利用可达性分析,判断对象是否是垃圾,然后将标记为垃圾的对象清除。
缺点:1、对象较多时,标记时间长。
2、清除操作带来了大量内存碎片。可能引起更频繁的gc.
标记复制
将内存分为两块。一块用于存放,一块用于待命。首先进行标记垃圾,然后执行清除操作,再将存活对象按序放进待命的内存。这样解决了大量内存碎片的缺点。但是此方法的内存利用率只有一半,不高。
标记整理
主要面向老年代。先进行标记操作,然后将存活对象都向内存空间的一端进行移动,清除掉存活边界以外的内存。
垃圾收集器
Serial收集器
![在这里插入图片
当GC线程执行时暂停应用程序线程,也就是stop the world。只有一个线程负责垃圾回收。
在内存资源受限或者单核处理器下,serialGC由于没有线程交互的开销,有较高的处理效率。
ParNew收集器
ParNew 是Serial GC 的多线程版本。GC时有多个线程执行。用的标记复制算法。
Parallel Scavenge收集器
与ParNew收集器很相似。Parallel Scavenge 更关注程序的吞吐量。
吞吐量= 执行用户代码的时间 / (执行用户代码的时间 + 执行垃圾收集的时间)
SerialOld 收集器
Serial Old 是Serial 的老年代版本。
Parallel Old收集器
是Parallel Scavenge的老年代版本
关注吞吐量,可以和Parallel Scavenge配合使用。
CMS 收集器
CMS关注垃圾回收的停顿时间。初始标记、重新标记阶段会stop the world
。采用了标记清除算法。
初始标记:仅标记GCRoots 能直接关联到的对象,速度快。
并发标记:沿着 关联对象继续搜索标记。
重新标记:修正因为用户操作发生引用变动的对象。
并发清理:删除垃圾对象。
缺点:1、并发标记阶段,因为并发标记,降低了应用的吞吐量。
2、并发标记、并发请理阶段,有用户线程存在。产生浮动垃圾。
3、采用标记清除算法,产生大量内存碎片。
G1收集器
其他收集器要么只收集新生代,要么老年代。G1采用Mixed GC 混合收集。G1没有固定分代的区域划分,而是将堆内存划分成多个大小相等的区域Region,不同Region扮演Eden、或者老年代。除此之外,还有特殊的Humongous区域专门存储大对象。
G1关注停顿时间,使用-XX:MaxGCPauseMillis来配置想要的停顿时间
初始标记:只标记GCROOTS直接关联的对象。
并发标记:进行可达性分析,找到要回收的对象。
最终标记:
筛选回收:对各个Region的回收价值和成本进行排序,根据期望的停顿时间制定回收策略。