垃圾回收主要存在于对内存当中。
可达性分析算法
这个算法的基本思路是通过一系列成为“GCRoots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路劲称之为引用链,当一个对象到GCRoots没有认识引用链的时候,则证明此对象是不可用的。
GCRoots的选择:
- 虚拟机栈(堆栈中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般的Native的对象)引用的对象
我对上面四种选择的理解为:1 就是在方法中局部变量引用的对象 2 静态类型的变量引用对象 3 实例变量的引用 4 本地方法的引用对象。
对引用的分类
1 强引用
强引用是指通过new创建的对象,Object obj = new Object();强引用的对象永远不会被回收除非被赋值为null,才会被回收。
2 软引用
软引用是用来描述有用但是非必须的对象。SoftReference这种类型的引用。软引用是当JVM的内存足够的时候不会回收,但是当JVM内存满了的话,会优先回收SoftReference这种类型的对象。
3 弱引用
弱引用用来描述非必需对象。被弱引用引用的对象是在下次垃圾回收到来的时候, 直接回收对象。通过WeakReference来创建这种类型的引用。我理解为:就是当一个对象创建完成后便可以被删除的那种。
4 虚引用
虚引用是最弱的引用的关系。为对象设置一个虚引用关联的唯一的目的就是在这个对象被垃圾收集器回收的时候收到一个系统通知。即提过一个信号的作用。PhantomReference
关于对象的自救
就是当一个对象被垃圾收集器回收的时候,会出现一次自救的机会会调用对象的finalize()的方法,如果我们不想要回收这个对象,可以在这个方法中吧这个对象引用到GCRoots这个引用链上面去。
回收方法区
在HotSpot虚拟机中,喜欢把堆称之为新生代,把方法区称之为永久代。
新生代的垃圾回收非常频繁,而永久代的垃圾相比下次数会比较少。
永久代的垃圾回收主要分类为两个部分:废弃的常量和无用的类
1 废弃的常量存在于常量池中,跟回收堆中的对象一样,当没有字面量指向它的时候,便可以回收。
2 无用的类的回收的判断会比较复杂:
- 该类的所有的实例已经被回收并且在堆中没有任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射来创建该类的方法。
我对回收无用类条件的理解:跟类加载的过程类似,当把一个类加载到虚拟机的时候,首先要选择ClassLoader 然后创建出该类对应的Class的类型,最后创建出实例。
所以当回收这个类的时候:
首先 没有任何该类的实例
第二 该类的Class的对象没有在任何地方被引用
第三 加载该类的ClassLoader即类加载器已经被回收了。
这样这个类便可以被回收了!
垃圾回收算法
标记清除算法(Mark-Sweep)
标记清除算法分为两个阶段:标记阶段和清除阶段。
首先标记处所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点在于:1效率问题,标记和清除两个过程的效率都不是很高。2空间问题,标记清楚之后会产生大量的空闲散碎的内存空间,当需要分配大的内存的对象时候,需要再次进行垃圾回收。
复制算法
为了解决标记清除算法的效率不高的问题,提出了复制算法即把内存分为两个部分,每次使用其中的一块,当一块内存用完了以后,便把存活着的对象复制到另一块内存上面这样便提高了效率。内存分配的时候也不需要考虑内存的碎片化的问题,但是这样做的缺点在于:每次只会利用内存空间的一半,对内存造成了浪费。
现在的做法是把内存分为3各部分8:1:1 ,一块比较大的Eden空间,两块比较小的Survivor空间。
当垃圾回收的时候,把Eden空间和一块Survivor空间上存活着的对象都复制到另一块Survivor对象上面去,最后清理到刚才使用的Eden空间和Survivor空间,当Survivor的空间不够的时候,利用老年代对内存进行分配担保。
标记整理算法(Mark-Compact)
复制算法在对象存活率比较高的情况下,复制的次数会特别的多,效率将会变低。所以在老年代使用的垃圾回收的方法是标记整理的算法。
标记整理也是分为两个阶段:首先是标记,第二个阶段整理,不是直接对可回收对象进行清理,而是让所有存活着的对象移到一端,然后清理掉端边界外的对象。
分代收集
即把堆内存分为两个部分:一个是新生代,一个老年代。新生代是指对象刚出现的地方,而老年代是指对象存活很久以后的内存空间。
HotSpot的算法实现
枚举根节点
GCRoots跟的可达性分析跟刚才一样,全局性引用和执行上下文(栈帧中的本地变量表)
(Stop The World)在分析所有连接到根节点的内容的时候,java虚拟机需要把所有的线程停止下来,否则便不能够全部检索到所有的引用。检测引用的方法会比较慢,所以提出了一种OopMap这样的数据结构,在类加载的构成当中便把所有的引用加载到OopMap当中去,这样便不需要一步一步去搜索检查上下文去。
安全点(Safe Point)
HotSpot没有为每条指令生成OopMap,只有在特定的位置记录这些信息,这些位置称之为安全点即程序执行时候并非在所有的地方都可以进行垃圾回收,只有到达安全点才能暂停。
safePoint的选择:例如方法调用,循环跳转,异常跳转这些比较耗时的指令的时候。
在SafePoint上需要考虑的内容是让所有的线程能够跑到暂停点然后暂停,所以提出了两种中断的方式。
一种是抢先式中断,首先把所有线程安全的全部中断,然后没有到达安全点的在恢复线程让其跑到安全点。
第二种是主动式中断,在安全点上设置一个标记,所有线程到达这个标记的时候然后中断线程。
安全区域
我们需要安全点的目的是:让所有线程暂停不产生新的引用,也就是当一个线程在休眠的时候没有产生任何的关于引用的对象的时候,这个时候就是SafeRegion
如果一个线程倒了安全区域,首先标识自己已经到达SafeRegion这个区域,然后其它线程才可以进行垃圾回收,但是当其离开安全区域的时候需要检查,垃圾回收是否结束如果没有结束那么便等到垃圾回收结束。
垃圾收集器
在这里要值得注意的是:根据堆内存区域划分的不同,针对新生代和老年代采用的垃圾收集器往往不同,但是由于垃圾收集器需要搭配使用才能更好的工作,所以需要选择合适的垃圾收集器来适应JVM。
Serial收集器(新生代采用复制算法,老年代采用标记整理算法)
Serial收集器是一个单线程的垃圾收集器,这个单线程不仅仅是它只会用一个CPU或者一条收集线程去完成垃圾回收工作,更重要的是
当这个收集器工作的时候,必须暂停用户所有的其它的线程,去完成垃圾回收。对于实时交互比较严重的程序比较难以想象。
但是这个垃圾收集器的优点在于:
简单而高效,对于限定单个cpu环境来说,Serial收集器由于没有线程交互导致这样的效率会更高。
Serial收集器对作用去Client模式下面的虚拟机来说是一个很好的选择。
ParNew收集器(新生代采用复制算法,老年代采用标记整理算法)
ParNew收集器是Serial收集器的多线程版本,区别在于在新生代复制算法的手,ParNew采用是多个GC线程去完成垃圾回收工作的。
ParNew是运行在Server工作模式最后的选择,其中一个原因是除了Serial垃圾回收器以外,只有它能够和CMS垃圾回收配合使用。
并行:并行的意思是指多个线程同时执行垃圾回收工作,此时用户线程和处于等待状态。
并发:是指用户线程与垃圾回收线程同时执行,通过CPU切换上下文来实现的。
ParallelScavenge收集器
ParallelScanvenge也是新生代垃圾回收器采用也是复制算法。
有两种评判指标:1 是减少垃圾回收的时候停顿的时间 2 提高CPU的吞吐量即(运行用户代码的时间)/(运行用户代码的时间+垃圾回收的时间)
高吞吐量可以充分利用CPU的使用效率。
ParallelScavenge垃圾收集器提供了两个指标提高cpu的利用率。1,MaxGCPauseMillis参数值允许传入一个大于0的毫秒数,最大垃圾回收的时间,GCTimeRatio设置吞吐量的参数。
并不是MaxGCPauseMillis越小越好,当这个值设置过小的时候就会频繁的发生垃圾回收。
ParallelScanvenge收集器被称为吞吐量优先的收集器。
SerialOld收集器
是Serial收集器的老年代的版本,它使用的是标记整理算法。收集器的意义在于工作在Client的模式下面。
1 在jdk1.5以前与Parallel Scavenge的回收器一起工作
2 当CMS并发回收失败的时候,采用这种收集器来收集。
ParallelOld收集器
这个收集器是ParallelScavenge的老年代版本,可以和ParallelScavenge垃圾收集器一起工作正式的提供吞吐量优先的垃圾回收机制。
CMS收集器(老年代垃圾回收器)
Concurrent Mark Sweep并发标记清楚收集器是一种获取最短回收停顿时间为目标的收集器。
整个过程分为四个步骤:
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark)
- 并发清除(CMS sweep)
首先 初始标记的时候会暂停所有的线程,不过这个时候只会标记与GCRoots直接关联的对象。
第二:并发标记,并发标记是指把用户线程和垃圾回收线程并发的执行,这个时候会按照GCRoots当中的可达性分析一直搜索下去。
第三步: 重新标记,这个重新标记也需要暂停所有的线程,这一步目的是把在并发标记的时候用户线程产生的对象再次进行标记。
第四步:并发清理:并发清理是指对前面标记的对象对于不在GCRoots链的进行垃圾回收。
总结起来就是:对于占用时间比较长的进程比如并发标记,以及并发清理的两个阶段与所有的进程一起进行,这样就减少了用户进程的停顿的时间。
但是这样做会有下面的三个缺点:
CMS 对CPU资源特别敏感,在并发的阶段开启的线程是(cpu+3)/4也就是意味着,会有最少25%的线程是用于垃圾回收或者是并发标记,这样会导致CPU的吞吐量比较低、
CMS没有办法处理浮动垃圾。所谓的浮动垃圾是指在并发清理的过程中,如果清理的时候没有对垃圾处理完,此时并发的用户进程又产生大量的垃圾,这样就会导致方法区溢出,解决的办法是:这个时候采用SerialOld对浮动垃圾进行回收。
CMS是一种标记清除算法,这样会产生大量的内存碎片。为了解决这个问题,CMS垃圾回收器提供了-XX:+UseCMSCompactAtFullCollection,用于进行内存整理。还有一个–XX:CMSFullGcBeforeCollection这个参数用来设置执行多少次不压缩GC后,带一次压缩的。
G1收集器
1 并发与并行同时存在
2 分代收集
3 空间整合
4 可预测停顿即可以由用户指定停顿的时间。
把堆划分为大小相等的Region区域,G1在java进行全区域垃圾收集的时候,记录每个Region的价值的大小,根据用户输入的他停顿的时间优先回收最大价值的内容。
初始标记
并发标记
最终标记
筛选回收。
GC日志的参数
GC 和FullGC fullGC是指发生了StopTheWorld