对象被判定为垃圾的标准:没有被其他对象引用
即通过判断对象的引用数量来决定对象是否可以被回收;
每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1;任何引用计数为0的对象实例是可以被当做垃圾收集的。
优点:执行效率高,程序执行受影响小;
缺点:无法检测出循环引用的情况(如:父对象对子对象有一个引用,子对象也对父对象有一个引用),导致内存泄漏。
即通过判断对象的引用链是否可达来决定对象是否可以被回收;
它是从图论引入,从GC Root开始向下搜索引用,看是否有可达对象;搜索走过的路径就叫做引用链;当一个对象从GC Root开始没有任何引用相连,则这个对象是不可达的,即不可用的,它可以被标记为垃圾,被回收。
虚拟机栈中引用的对象(栈帧中的本地变量表);
方法区中的常量引用的对象;
方法区中的类静态属性引用的对象;
本地方法栈中JNI(Native方法)的引用对象;
活跃线程的引用对象;
1)标记清除算法(Mark and Sweep)
标记:从根集合进行扫描,对存活的对象进行标记(即利用前面的可达性分析算法进行标记);
清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存;
如下图:(标记清除算法)
缺点:内存碎片化(如果较大的对象存入时,由于没有连续的剩余内存,需要启动GC)
2) 复制算法(Copying)
将可用的内存分别对象面和空闲面;对象在对象面上创建;
开启垃圾回收时,将对象面上存活的对象复制到空闲面;
再把对象面上已使用的空间一次清除
优点:
适用于对象存活率低的场景,如年轻代;
解决了碎片化问题;
顺序分配内存,简单高效。
缺点:应对对象存活率较高的场景就力不从心了,效率较低;更重的的它需要浪费50%的空间。
3)标记-整理算法(Compacting)
在标记清除算法的基础上进行了移动,因此成本更高,但是解决了碎片化问题;
标记:从根集合进行扫描,对存货的对象进行标记;
清除:移动所有存活的对象,且按照内存地址次序排列,然后将末端内存地址以后的内存全部回收。
优点:
避免了内存的不连续性;
不用设置两块内存互换;
适用于存活率较高的场景(如老年代)
4)分代收集算法(Generational Collector)
是垃圾回收算法的组合拳;按照对象的生命周期的不同划分区域,以采用不同的垃圾回收算法;
目的:提高JVM的回收效率
JDK8之后,永久代被取消,堆被划分为年轻代和老年代,其中年轻代适合使用复制算法回收垃圾,而老年代适合使用标记-整理算法回收垃圾。
GC的分类:
(1)Minor GC: (当Eden区和survivor区空间不足,会触发Minor GC)
(2)Full GC: (当老年代空间不足,或者调用了System.gc()时,会触发Full GC)
年轻代被划分为Eden区和两个Survivor区,一般的比例是8:1:1,采用复制算法回收垃圾,如下图(年轻代和老年代):
当Eden区满了之后,会触发一次Minor GC: 将存活的对象复制到其中的一个survivor区(假设A区),且该存活对象的生命周期加1,然后回收Eden区;
当Eden区第二次满了之后,会再次触发Minor GC: 将Eden区和survivor区(即A区)的存活对象复制到另一个survivor区(假设为B区),并且将它们的生命周期均加1,然后回收Eden区和A区;
… …; 周而复始,当survivor区中对象的生命周期达到一定值(默认情况下为15,可以通过参数-XX:+MaxTenuringThreshold设置),或者当新建的对象太大时,会将这些对象存放在老年代。
年轻代的对象如何晋升到老年代:
(1)经历一定Minor次数依然存活的对象;
(2)survivor区中存放不下的对象
(3)新生成的大对象(-XX:+PretenuerSizeThreshold)
常见的调优参数:
-XX:SurvivorRatio: Eden和Survivor的比值,默认为8:1;
-XX:NewRatio: 老年代和年轻代的内存大小的比例,默认为2:1;
-XX:MaxTenuringThreshold: 对象从年轻代晋升到老年代经过GC次数的最大阈值;
-XX:PretenuerSizeThreshold: 新生的大对象。
一般采用标记-清除算法、和标记-整理算法;
Full GC 和 Major GC(需要Major GC是上面的Full GC,还是整个堆上的垃圾回收);
Full GC 比 Minor GC 慢,但执行频率低;
触发Full GC 的条件:
(1)老年代空间不足;
(2)永久代空间不足(JDK1.7之前);
(3)调用System.gc(); (注意,这只是一个提醒作用,开发人员不能控制GC)
(4)CMS GC 时出现 promotion failed,concurrent model failure(即 )
(5)Minor GC晋升到老年代的平均大小大于老年代的剩余空间;
(6)使用RMI来进行RPC或管理的JDK应用,每小时执行1次Full GC;
Stop-the-World: 即JVM由于要执行GC而停止了应用程序的执行;任何一种GC算法中都会发生;
多数GC优化通过减少Stop-the-World发生的时间来提高程序性能,从而使程序"高吞吐,低停顿"。
Safepoint: 分析过程中对象引用关系不会发生变化的点;
产生Safepoint的地方:方法调用、循环跳转、异常跳转等;
安全点数量的适中
JVM的运行模式:
Server: 启动时速度慢,启动重量级的JVM,当稳定运行后速度快;
Client:启动时速度快,启动轻量级的JVM,当稳定运行后速度慢;
注:可以使用java -version查看当前JVM的运行模式
垃圾收集器之间的联系,如图(垃圾收集器):
1)Serial收集器(-XX:UseSerialGC,复制算法):
单线程收集,进行垃圾收集时,必须暂停所有工作线程;(停顿几十毫秒或者几百毫秒)
简单高效,Client模式下默认的年轻代收集器。
2)ParNew收集器(-XX:UseParGC,复制算法)
多线程收集,其余的行为、特点和Serial收集器一样;
单核执行效率不如Serial,在多核下执行才有优势(Server模式下)
3)Parallel Scavenge收集器(-XX:UseParallelGC,复制算法):
吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾回收的时间)
比起关注用户线程停顿时间,更关注系统的吞吐量;
在多核下执行才有优势,Server模式下默认的年轻代收集器。
(-XX:UseAdaptiveSizePolicy)
1)Serial Old收集器(-XX:UseSerialOldGC,标记-整理算法):
单线程收集,进行垃圾收集时,必须暂停所有工作线程;(停顿几十毫秒或者几百毫秒)
简单高效,Client模式下默认的老年代收集器。
2)Parallel Old收集器(-XX:UseParallelOldGC,标记-整理算法):
多线程,吞吐量优先;
3)CMS收集器(-XX:UseConcMarkSweepGC,标记-清除算法):老年代垃圾收集器的半壁江山
Step1: 初始标记 stop-the-world
Step2: 并发标记 并发追溯标记,程序不会停顿
Step3: 并发预清理 查找执行并发标记阶段从年轻代晋升到老年代的对象
Step4: 重新标记 暂停虚拟机,扫描CMS堆中的剩余对象
Step5: 并发清理 清理垃圾对象,程序不会停顿
Step6: 重置CMS收集器的数据结构
缺点:碎片化
了解:G1收集器(-XX:UseG1GC,复制+标记-整理算法):
Garbage First 收集器的特点:并发与并行、分代收集、空间整合、可预测的停顿
与C++的析构函数不同,析构函数调用确定(即对象离开作用域后就会被删除),而它的是不确定的;
java中当垃圾回收器要宣告对象死亡时时,至少要经过两次的标记过程:如果对象在进行可达性分析后,没有和GC Root连接的引用链时,就会被第一次标记,并且判断是否执行finalize()方法;如果对象覆盖finalize方法,且未被引用过,这个对象就会被放在F-Queue队列;
方法执行随时可能会被终止;
给予对象最后一次重生的机会。
1)强引用(Strong Reference)
最普遍的引用:Object obj=new Object()
抛出OutOfMemoryError终止程序也不会回收具有强引用的对象;
通过将对象设置为null来弱化引用,使其被回收。
2)软引用(Soft Reference)
对象处在有用但非必须的状态;
只有当内存空间不足时,GC会回收该引用的对象的内存;
可以用来实现高速缓存。
String str=new String("abc");//强引用
SoftReference softRef=new SoftReference (str);//软引用
3)弱引用(Weak Reference)
非必须的对象,比软引用更弱一些;
GC时会被回收;
被回收的概率也不大,因为GC线程优先级比较低;
适用于引用偶尔被使用且不影响垃圾收集的对象。
String str=new String("abc");//强引用
WeakReference abcWeakRef=new WeakReference (str);//弱引用
4)虚引用(PhantomReference)
不会决定对象的生命周期;
任何时候都可能被垃圾收集器回收;
跟踪对象被垃圾收集器回收的活动,起哨兵作用;
必须和引用队列ReferenceQueue联合使用
String str=new String("abc");//强引用
ReferenceQueue queue=new ReferenceQueue();
PhantomReference ref=new PhantomReference (str,queue);//虚引用
强引用 > 软引用 > 弱引用 > 虚引用
引用类型 | 被垃圾回收的时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 在内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 在垃圾回收时 | 对象缓存 | gc运行后终止 |
虚引用 | Unknown | 标记、哨兵 | Unknown |
引用队列(ReferenceQueue):无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达(head next)