Java底层:GC相关

发布时间:2020-08-08 13:18:56 作者:ZeroOne01
来源:网络 阅读:4710

垃圾回收之垃圾标记算法

对象被判定为垃圾的标准:

判定对象是否为垃圾的算法:

可达性分析算法遍历引用链如图:
Java底层:GC相关

可以作为GC Root的对象:


Java垃圾回收之回收算法

光有垃圾标记算法还不行,JVM还需要有垃圾回收算法来将这些标记为垃圾的对象给释放回收掉。主要的回收算法有以下几种:

1.标记 - 清除算法(Mark and Sweep):

Java底层:GC相关

缺点:由于标记 - 清除不需要进行对象的移动,并且仅对不可达的对象进行处理,因此使用该回收算法之后会产生大量不连续的内存碎片。而内存碎片过多可能会导致以后在程序运行过程中,需要分配内存给较大的对象时,无法找到足够大的连续内存空间,从而不得不再次触发垃圾回收工作,若依旧无法分配内存的话就会触发内存溢出异常。

1.复制算法(Copying):

Java底层:GC相关

优点:解决内存碎片化问题,顺序分配内存,简单高效。该算法适用于对象存活率低的场景,所以普遍应用在新生代中,因为新生代里的对象存活率通常情况下只有10%左右

3.标记 - 整理算法(Compacting):

Java底层:GC相关

优点:避免了标记 - 清除算法所带来的内存不连续性问题,以及不需要像复制算法那样需要设置两块内存互换。该算法适用于对象存活率较高的场景,所以普遍应用在老年代中,因为老年代里对象存活率较高

4.分代收集算法(Generational Collector):

在JDK7及之前的JVM版本共有三个分代,即新生代、老年代和永久代(注意,永久代不存在于堆中,而是存在于方法区):
Java底层:GC相关

而JDK8及以后的版本只有新生代和老年代:
Java底层:GC相关

分代收集算法的GC分为两种:

新生代用于尽可能快速地收集掉那些生命周期短的对象,新生代分为两个区:

Java底层:GC相关

对象如何晋升到老年代:

常用的调优参数:

综上,老年代用于存放生命周期较长的对象,老年代采用的是标记 - 整理算法。

Full GC和Major GC:

触发Full GC的条件:

注:promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的。


Java垃圾回收之新生代垃圾收集器

在了解垃圾收集器之前,我们需要知道一个概念“Stop-the-World”:

除此之外,我们需要知道什么是Safepoint:

JVM的运行模式:

各垃圾收集器之间的联系,即可以搭配使用关系:
Java底层:GC相关

Serial收集器(启动参数:-XX:+UseSerialGC,采用复制算法):

ParNew收集器(启动参数:-XX:+UseParNewGC,采用复制算法):

Parallel Scavenge收集器(启动参数:-XX:+UseParallelGC,采用复制算法):


Java垃圾回收之老年代垃圾收集器

Serial Old收集器(启动参数:-XX:+UseSerialOldGC,采用标记 - 整理算法):

Parallel Old收集器(启动参数:-XX:+UseParallelOldGC,采用标记 - 整理算法):

CMS收集器(启动参数:-XX:+UseConcMarkSweepGC,采用标记 - 清除算法):

CMS收集器收集流程:

  1. 初始标记:会触发stop-the-world,但是停顿时间很短
  2. 并发标记:并发追溯标记,程序不会停顿
  3. 并发预清理:查找执行并发标记阶段从新生代晋升到老年代的对象
  4. 重新标记:暂停虚拟机,扫描CMS堆中的剩余对象
  5. 并发清理:清理垃圾对象,程序不会停顿
  6. 并发重置:重置CMS收集器的数据结构

CMS收集器图示:
Java底层:GC相关

G1收集器(启动参数:-XX:+UseG1GC,采用复制 + 标记 - 整理算法):

Java底层:GC相关


Java垃圾回收之常见面试题

1.Object的finalize()方法的作用是否与C++的析构函数作用相同:

示例代码:

package com.example.demo.gc;

/**
 * @author 01
 * @date 2019-07-18
 **/
public class Finalization {

    public static Finalization finalization;

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
        finalization = this;
    }

    public static void main(String[] args) {
        Finalization f = new Finalization();
        System.out.println("First print: " + f);
        f = null;
        System.gc();
        System.out.println("Second print: " + f);
        System.out.println(f.finalization);
    }
}

执行结果:
Java底层:GC相关

从执行结果可以看到,Finalization对象被GC回收时finalize()方法会被调用,finalize()方法里将当前对象this赋值给了静态属性finalization实现了对象的“重生”,所以在GC之后依旧能打印到该对象的地址信息

注:finalize是个不太可控的方法因此并不常用,并且在JDK9+版本被标注为过时方法

2.Java中的强引用,软引用,弱引用及虚引用有什么用:

软引用代码示例:

// 强引用
String str = new String("abc");
// 转换为软引用
SoftReference<String> softReference = new SoftReference<>(str);

弱引用代码示例:

String str = new String("abc");
// 弱引用
WeakReference<String> weakReference = new WeakReference<>(str);

虚引用代码示例:

String str = new String("abc");
// 引用队列
ReferenceQueue<String> queue = new ReferenceQueue<>();
// 转换为虚引用
PhantomReference<String> phantomReference = new PhantomReference<>(str, queue);
// GC在回收一个对象时,如果发现该对象存在虚引用,那么在回收之前会先将该对象的虚引用添加到与该对象关联的引用队列中;程序代码可以通过判断引用队列是否已加入虚引用来得知被引用的对象是否已经被回收

引用队列(ReferenceQueue):

引用强度关系:

Java底层:GC相关

下面流程图简单总结了对象生命周期和不同可达性状态,以及不同状态可能的改变关系:
Java底层:GC相关

上图的具体状态,实际是 Java 定义的不同可达性级别(reachability level),在之前也说过判断对象可达性,是 JVM 垃圾收集器决定如何处理对象的一部分考虑。可达性具体含义如下:

各引用包装类的继承关系图:
Java底层:GC相关


下面我们来用一个例子演示引用包装对象及引用队列的使用,首先定义一个普通的类,并且实现finalize方法以便我们在测试时可以看到该对象是否被GC回收了:

package com.example.demo.gc;

/**
 * @author 01
 * @date 2019-07-18
 **/
public class NormalObject {

    public String name;

    public NormalObject(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalizing obj: " + name);
    }
}

然后定义一个WeakReference的子类,目的是扩展name属性,以便我们在测试时能够得知是哪个对象的引用对象:

package com.example.demo.gc;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

/**
 * @author 01
 * @date 2019-07-18
 **/
public class NormalObjectWeakReference extends WeakReference<NormalObject> {

    public String name;

    public NormalObjectWeakReference(NormalObject referent, ReferenceQueue<NormalObject> queue) {
        super(referent, queue);
        this.name = referent.name;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalizing NormalObjectWeakReference: " + name);
    }
}

最后编写一个测试类:

package com.example.demo.gc;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 01
 * @date 2019-07-18
 **/
public class ReferenceQueueTests {
    // 引用队列
    private static ReferenceQueue<NormalObject> queue = new ReferenceQueue<>();

    /**
     * 检查引用队列里有没有引用对象,有的话则打印相关信息
     */
    private static void checkQueue() {
        Reference<? extends NormalObject> reference;
        while ((reference = queue.poll()) != null) {
            // 存在于引用队列中的引用对象
            System.out.println("In Queue: " + ((NormalObjectWeakReference) (reference)).name);
            // 获取引用的对象实例
            System.out.println("reference object: " + reference.get());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        List<WeakReference<NormalObject>> weakReferenceList = new ArrayList<>();
        // 创建引用对象
        for (int i = 0; i < 3; i++) {
            NormalObject normalObject = new NormalObject("Weak-" + i);
            NormalObjectWeakReference reference = new NormalObjectWeakReference(normalObject, queue);
            weakReferenceList.add(reference);
            System.out.println("Create weak: " + reference);
        }

        System.out.println("\nbefore gc ------");
        checkQueue();

        System.out.println("\ngc ing... ------");
        System.gc();
        // 让线程休眠一会,以保gc能够正常执行完毕
        Thread.sleep(1000);

        System.out.println("\nafter gc ------");
        checkQueue();
    }
}

运行结果:
Java底层:GC相关

可以看到在GC执行之前调用checkQueue方法没有打印任何信息,因为此时引用队列中没有任何引用对象。而当GC执行之后,引用队列中就被添加了与之相关联的引用对象,所以就能够打印出引用对象的相关信息


GC相关参考文章:

推荐阅读:
  1. php gc
  2. java GC笔记

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

面试 jvm gc

上一篇:python基础之 函数的参数

下一篇:odoo Controller接口开发 POST请求的跨域问题解决方法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》