1. 可达的
Java对象被创建后,如果被一个或者多个变量引用,就是可达的。
2. 可恢复的
Java对象不再被任何变量引用就会进入可恢复状态。
回收对象前调用finalize()方法让变量引用该对象,则可达,否则不可达。
ps:finalize()方法只能恢复一次。
注意⚠️:
永远不要主动调用finalize方法
,应该留给垃圾回收机制调用,而且具有不确定性,finalize可以使一个对象重新可达,此方法出现异常时,垃圾回收机制不会报告异常,程序继续执行。方法来自Object,protected void finalize()throw Throwable
参考资料:https://blog.csdn.net/qq_21927765/article/details/50809291
3. 不可达的
Java对象不被任何变量引用,且调用finalize()方法后仍然没有变成可达状态。
当对象处于不可达时,系统才会真正回收该对象占有的资源。
无法解决对象相互循环引用的问题
;引用和去引用需要计数记忆,影响性能。public class ReferenceCountingGC {
public Object instance = null;
public static void testGC(){
ReferenceCountingGC objA = new ReferenceCountingGC ();
ReferenceCountingGC objB = new ReferenceCountingGC ();
// 对象之间相互循环引用,对象objA和objB之间的引用计数永远不可能为0
objB.instance = objA;
objA.instance = objB;
objA = null;
objB = null;
System.gc();
}
}
解析:上述代码最后面两句将objA和objB赋值为null,也就是说objA和objB指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为 0,那么垃圾收集器就永远不会回收它们。
ps:所以,主流的java虚拟机中没有选用引用计数法来管理内存,主要原因就是它很难解决对象之间互相循环引用的问题
。
可达性分析:判断对象的引用链是否可达。可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,通过一系列的名为 “GC Roots” 的对象作为起始点,从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可达的。
Java采取了“可达性分析”来判断对象是否存活,同样采用此算法的还有C#。
在Java语言中,GC Roots包括:
- 虚拟机栈(栈帧中的局部变量表)中引用的对象
- 方法区中类静态属性实体引用的对象
- 方法区中常量引用的对象(全局变量)
- 本地方法栈中JNI(Native方法)引用的对象。
在JDK1.2之后,Java对引用的概念做了扩充,将引用分为:强引用(Strong Reference)、软引用(Weak Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种,这四种引用的强度依次递减。
Minor GC:对新生代区进行回收。新生代java对象死亡频繁,所以minor GC也非常频繁,所以使用速度快、效率高的算法可以使得垃圾回收尽快完成。
Full GC:也叫Major GC,对整个堆进行回收,包括新生代、老年代。Full GC需要对整个堆回收,所以比Minor GC慢(因为需要stop-the-world,吞吐量下降,造成性能差),因此要尽量减少Full GC次数。触发GC的例子:老年代满了,System.gc()被显示调用。。。
概念:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
过程:该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收,如下图:
缺点:标记和清除过程效率不高;空间问题,标记清除后会产生大量不连续碎片,可能会导致程序需要分配的较大对象找不到连续足够的内存而提前触发另一次垃圾收集动作。
将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块内存用完,就将还存活着的对象复制到另一块上,然后再把已经使用的内存空间清理掉。
优点:不用考虑内存碎片问题;实现简单,运行高效。适合新生代。
缺点:内存缩小为原来的一半,持续复制长生存期的对象则导致效率降低。不适合老年代。
目前商用的虚拟机都采用这种算法来回收新生代。研究发现,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率还不错。新生代中Eden、survivor0、survivor1比例8:1:1,当回收时,将Eden和Survovor0中还存活的对象一次地复制到另外一块survivor空间上,最后清理掉Eden和Survivor0空间,索引只有10%的内存被浪费。
新生代、老年代概念
1)新生代
新生代的目标就是尽可能快速的收集那些生命周期短的对象。其内存按8:1:1比例分为eden(伊甸区)、survivor0(幸存者0区)、survivor1(幸存者1区)。
1.大部分对象在eden区生成,进行垃圾回收时,先将eden区存活对象复制到survivor0区,然后清空eden区。
2.当survivor0也快满了,就将eden区和survivor0区对象复制到survovr1区,然后清空eden和survivor0区。
3.这时survivor0为空,然后交换survivor0,survivor1角色(下次垃圾回收时会扫描eden、survivor1区),进行反复。
4.如果出现survivor1放不下存活对象时,就将存活对象直接放老年代。
5.如果老年代也满了就会触发一次FullGC(新生代、老年代都回收,新生代发生的GC也叫MinorGC、MinorGC发生频率比较高,不一定等eden区满了才触发)
2)老年代
老年代中存放的都是生命周期比较长的对象。老年代和新生代占内存比例约(2:1),当老年代满时会触发Major GC(Full GC),老年代对象存活时间比较长,因此FullGC发生的概率比较低。
首先标记出所有需要回收的对象,然后让所有存活对象向一端移动,然后直接清理掉端边界以外的内存。适合老年代。
GC分代假设:大部分对象的生命周期非常短暂,存活时间短。
把java堆分为新生代和老年代。根据各个年代的特点采用合适的收集算法。
`目前分代收集算法使用较多。
最古老,稳定,效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法,老年代标记-压缩算法。垃圾收集过程中会服务暂停(stop-the-world)。
参数控制:-XX:+UseSerialGC 串行收集器
Stop the world:虚拟机在进行GC时,暂停其他所有的工作线程。
Serial收集器的多线程版本。新生代并行,老年代串行。新生代复制算法、老年代标记-压缩算法。
参数控制:-XX:+UseParNewGC ParNew收集器;-XX:ParallelGCThreads限制线程数量
并发和并行的区别
- 并行(parallel):指多条垃圾手机线程并行工作,但是此时用户线程仍然处于等待状态。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但是不一定并行,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU上.
Parallel Scavenge类似parNew收集器,回收新生代对象,但是更关注系统的吞吐量。可通过参数来打开自适应调节策略,虚拟机会根据情况动态调整参数以提供最合适的停顿时间或者最大吞吐量。
参数控制:-XX:+UseParallelGC 使用Parallel收集器+老年代串行/老年代并行
Parallel old收集器,是parallel收集器的老年代版本,使用多线程“标记-整理”算法。JDK1.6开始提供。
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,基于“标记-清除”算法实现
,也是唯一一个不会在GC时出现stop-the-world的收集器(标记过程不算)。
过程
CMS 出现FullGC的可能原因:
1、年轻代晋升到老年代没有足够的连续空间,很有可能是内存碎片导致的。
2、在并发过程中JVM觉得在并发过程结束之前堆就会满,需要提前触发FullGC。
概念:Garbage-First是一款面向服务器的垃圾收集器
,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
步骤:
标记GC Root能够直接关联的对象
,并修改TAMS(Next Top Mark Start)值,让下一阶段用户程序并发运行时可以在正确的可用Region中创建新对象,这个过程需要stop-the-world,但是停顿时间较短。找出所有存活对象
,这个过程耗时较长,可与用户线程并发执行。修正并重新标记并发标记阶段用户线程产生的垃圾对象
,JVM将这部分标记记录在线程Remembered Set中,这个阶段需要停顿线程,但是可并行执行。G1的特点:
1)并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间
。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行
。
2)分代收集:分代概念在G1中依然得以保留。虽然G1可以不需要其它收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。也就是说G1可以自己管理新生代和老年代
了。
3)空间整合✨:由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记-整理”算法
实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片
。
4)可预测停顿✨:j降低停顿时间是G1、CMS的共同关注点,但是G1除此之外还可以建立可预测停顿时间模型
,能让使用者明确指定
一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
G1收集器将Java堆划分为多个大小相等的独立区域(Regin),虽然还保留了新生代和来年代的概念,但新生代和老年代不再是物理隔离的了它们都是一部分Region(不需要连续)的集合。
G1新生代收集跟ParNew类似,当新生代占用达到一定比例,开始出发收集。G1老年代收集和CMS类似,回收时对象会有短暂停顿(stop-the-world)。
参考文章:
https://blog.csdn.net/justloveyou_/article/details/71216049
https://www.jianshu.com/p/cc8395631acd
https://www.cnblogs.com/rgever/p/9534857.html