大家好,又见面了,我是你们的朋友全栈君。
垃圾回收是指不定时去堆内存中清理不可达对象。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,程序员唯一能做的就是通过调用System.gc 方法来建议执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。这也是垃圾收集器的最主要的缺点。当然相对于它给程序员带来的巨大方便性而言,这个缺点是瑕不掩瑜的。
先谈一下新生代与老年代
根据垃圾回收机制的不同,Java堆有可能拥有不同的结构,最为常见的就是将整个Java堆分为 新生代和老年代。其中新生带存放新生的对象或者年龄不大的对象,老年代则存放老年对象。
一、为什么需要垃圾回收
如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分配内存空间而不进行回收。除非内存无限大,我们可以任性的分配而不回收,但是事实并非如此。所以,垃圾回收是必须的。
✍上代码
public class GCTest {
public static void main(String[] args) {
Object object = new Object();
object = null;
//System.gc(); 没用不会执行finalize()
GCTest gcTest = new GCTest();
gcTest=null;
System.gc();// 手动回收垃圾
}
/** * Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前, * 做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。 * 它是在Object类中定义的,因此所有的类都继承了它。 * 子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。 * finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。 * * @throws Throwable */
@Override
protected void finalize() throws Throwable {
System.out.println("开始垃圾回收");
}
}
二、哪些内存需要回收?
哪些内存需要回收是垃圾回收机制第一个要考虑的问题,所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象。那么如何找到这些对象?
这个算法的实现是,给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。这种算法使用场景很多,但是,Java中却没有使用这种算法,因为这种算法很难解决对象之间相互引用的情况。
✍上代码
public class ReferenceCountingGC
{
private Object instance = null;
private static final int _1MB = 1024 * 1024;
/** 这个成员属性唯一的作用就是占用一点内存 */
private byte[] bigSize = new byte[2 * _1MB];
public static void main(String[] args)
{
ReferenceCountingGC objectA = new ReferenceCountingGC();
ReferenceCountingGC objectB = new ReferenceCountingGC();
objectA.instance = objectB;
objectB.instance = objectA;
objectA = null;
objectB = null;
System.gc();
}
}
结果:
[GC 4417K->288K(61440K), 0.0013498 secs]
[Full GC 288K->194K(61440K), 0.0094790 secs]
看到,两个对象相互引用着,但是虚拟机还是把这两个对象回收掉了,这也说明虚拟机并不是通过引用计数法来判定对象是否存活的。
这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
那么问题又来了,如何选取GCRoots对象呢?在Java语言中,可以作为GCRoots的对象包括下面几种:
下面给出一个GCRoots的例子,如下图,为GCRoots的引用链。
由图可知,obj8、obj9、obj10都没有到GCRoots对象的引用链,即便obj9和obj10之间有引用链,他们还是会被当成垃圾处理,可以进行回收。
如果不好理解请看下图:
. 五、垃圾收集算法
该算法有两个阶段。
应用场景:
该算法一般应用于老年代,因为老年代的对象生命周期比较长。
关于回收后碎片化的理解:
该算法的优缺点:
优点
缺点
标记清除算法和标记压缩算法非常相同,但是标记压缩算法在标记清除算法之上解决内存碎片化.让所有存活对象都向一端移动,然后直接清理掉边界以外的内存
优点:解决内存碎片问题,缺点压缩阶段,由于移动了可用对象,需要去更新引用。
如果JVM使用了复制算法,一开始就会将可用内存分为两块,from域和to域, 每次只是使用from域,to域则空闲着。当from域内存不够了,开始执行GC操作,这个时候,会把from域存活的对象拷贝到to域,然后直接把from域进行内存清理。
应用场景
复制算法一般是使用在新生代中,jvm将Heap 内存划分为新生代与老年代,又将新生代划分为Eden(伊甸园) 与2块Survivor Space(幸存者区) ,然后在Eden –>Survivor Space 以及From Survivor Space 与To Survivor Space 之间实行Copying 算法。
不过jvm在应用复制算法时,并不是把内存按照1:1来划分的,这样太浪费内存空间了。一般的jvm都是8:1。HotSpot虚拟机默认Eden区和Survivor区的比例为8:1,意思是每次新生代中可用内存空间为整个新生代容量的90%。当然,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖老年代进行分配担保(Handle Promotion)。
优缺点
优点:在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中导致的引用更新问题。
缺点: 会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,复制的性能会变得很差。
这种算法,根据对象的存活周期的不同将内存划分成几块,新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。可以用抓重点的思路来理解这个算法。 新生代对象朝生夕死,对象数量多,只要重点扫描这个区域,那么就可以大大提高垃圾收集的效率。另外老年代对象存储久,无需经常扫描老年代,避免扫描导致的开销。
新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。 老年代 GC(Major GC / Full GC):指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。
虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
还是感觉得有书学着才踏实,看视频看博客总感觉缺少一点信服力,先就以此对垃圾回收机制做个基础的了解.后期如果遇到更好的资料再做个深入的学习.?
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/143799.html原文链接:https://javaforall.cn