JVM垃圾回收机制笔记详解

JVM垃圾回收机制


  阅读前需知:此文章是小白我在学习过程中做的笔记,可能有许多错误之处,欢迎大家指出并提出宝贵的意见,感谢参考的原创文章的作者们!
  最后小伙伴们觉得勉勉强强可以的话点个赞呗!

更多个人笔记以及面试技巧可以到本人的Github上观看:Java_Note


一、基本介绍

1、最基本的问题

  如果确定某个对象是“垃圾”?既然垃圾收集器的任务是回收垃圾对象所占的空间供新的对象使用,那么垃圾收集器如何确定某个对象是“垃圾”?

———即通过什么方法判断一个对象可以被回收了

2、分类

  不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

  • 强引用:默认情况下,new对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)。

  • 弱引用:在GC时一定会被GC回收。

  • 软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)。

SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用
  • 虚引用:由于虚引用只是用来得知对象是否被GC。

二、判断垃圾算法

1.引用计数法(java不采用)

1)算法思路

   在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。

2)算法分析

  引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

3)优点

  实现简单,而且效率较高

4)缺点:

无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

5)缺点的代码分析

public class Main {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
         
        object1.object = object2;
        object2.object = object1;
         
        object1 = null;
        object2 = null;
    }
}
 
class MyObject{
    public Object object = null;
}

  最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们。

2、可达性分析法(Java采用)

JVM垃圾回收机制笔记详解_第1张图片

1)基本思想

  通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。

  不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

2)在Java语言中,可作为GC Roots的对象包含以下几种:

  • 本地方法栈中(Native方法) JNI引用的对象 (可以理解为:引用Native方法的所有对象)
  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。 (可以理解为:引用栈帧中的本地变量表的所有对象)
  • 方法区中静态属性引用的对象 (可以理解为:引用方法区该静态属性的所有对象)
  • 方法区中常量引用的对象 (可以理解为:引用方法区中常量的所有对象)

JNI(Java Native Interface): native方法是通过java中的JNI实现的。

参考:https://www.cnblogs.com/DengGao/p/jni.html

3)finalize()方法最终判定对象是否存活

  如果对象在进行可行性分析后发现没有与GC Roots相连的引用链,也并非是 “非死不可” 的。

经历的步骤:

i. 第一次标记并进行一次筛选。

  标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链;

  筛选的条件是此对象是否有必要执行finalize()方法。

  当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。

ii:.第二次标记

  如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。

  这样做的原因是:如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。

   Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。

3、常见的将对象判定为可回收对象的情况:

1)显示地将某个引用赋值为null或者将已经指向某个对象的引用指向新的对象

比如下面的代码:

Object obj = new Object();
obj = null;
Object obj1 = new Object();
Object obj2 = new Object();
obj1 = obj2;

2)局部引用所指向的对象

比如下面这段代码:

void fun() {
 
.....
    for(int i=0;i<10;i++) {
        Object obj = new Object();
        System.out.println(obj.getClass());
    }   
}

  循环每执行完一次,生成的Object对象都会成为可回收的对象。

3)只有弱引用与其关联的对象,比如:

WeakReference<String> wr = new WeakReference<String>(new String("world"));

三、垃圾回收算法(理论基础)

在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。

  由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,所以在此只讨论几种常见的垃圾收集算法的核心思想。

1、分代收集算法(GC回收)

1.1 算法思路

  根据对象存活周期的不同将堆分为:新生代和老年代,方法区称为永久代(在新的版本中已经将永久代废弃,引入了元空间的概念,永久代使用的是JVM内存而元空间直接使用物理内存)。

1.2堆区划分

  新生代中的对象“朝生夕死”,每次GC时都会有大量对象死去,少量存活,使用复制算法。新生代又分为Eden区和Survivor区(Survivor from、Survivor to),大小比例默认为8:1:1。

  老年代中的对象因为对象存活率高、没有额外空间进行分配担保,大多使用标记-整理算法(Mark-Compact)

  注意:在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。

1.3 算法实现

 新产生的对象优先进去Eden区,当Eden区满了之后再使用Survivor from,当Survivor from 也满了之后就进行Minor GC(新生代GC),将Eden和Survivor from中存活的对象copy进入Survivor to,然后清空Eden和Survivor from,这个时候原来的Survivor from成了新的Survivor to,原来的Survivor to成了新的Survivor from。复制的时候,如果Survivor to 无法容纳全部存活的对象,则根据老年代的分配担保(类似于银行的贷款担保)将对象copy进去老年代,如果老年代也无法容纳,则进行Full GC(老年代GC)。

  大对象直接进入老年代:JVM中有个参数配置-XX:PretenureSizeThreshold,令大于这个设置值的对象直接进入老年代,目的是为了避免在Eden和Survivor区之间发生大量的内存复制。
长期存活的对象进入老年代:JVM给每个对象定义一个对象年龄计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳,将被移入Survivor并且年龄设定为1。每熬过一次Minor GC,年龄就加1,当他的年龄到一定程度(默认为15岁,可以通过XX:MaxTenuringThreshold来设定),就会移入老年代。但是JVM并不是永远要求年龄必须达到最大年龄才会晋升老年代,如果Survivor 空间中相同年龄(如年龄为x)所有对象大小的总和大于Survivor的一半,年龄大于等于x的所有对象直接进入老年代,无需等到最大年龄要求。

XX:MaxTenuringThreshold: 不同垃圾回收器不同设置

CMS:6

其他回收器最大为15

2、标记-清除算法(Mark-Sweep)

1)基本介绍

  最基础的算法,分标记和清除两个阶段:首先标记处所需要回收的对象,在标记完成后统一回收所有被标记的对象。

2)不足:

一个效率问题,标记和清除过程都效率不高;

  一个是空间问题(碎片化),标记清除之后会产生大量不连续的内存碎片(类似于我们电脑的磁盘碎片),空间碎片太多导致需要分配大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。

JVM垃圾回收机制笔记详解_第2张图片

3、复制算法

1)基本介绍

  为了解决效率问题,出现了“复制”算法,他将可用内存按容量划分为大小相等的两块,每次只需要使用其中一块。当一块内存用完了,将还存活的对象复制到另一块上面,然后再把刚刚用完的内存空间一次清理掉。

2)不足

  这样就解决了内存碎片问题,但是代价就是可以用内容就缩小为原来的一半。

JVM垃圾回收机制笔记详解_第3张图片

4、标记-整理算法(Mark-Compact)

1)思想

  复制算法在对象存活率较高时就会进行频繁的复制操作,效率将降低。因此又有了标记-整理算法,标记过程同标记-清除算法,但是在后续步骤不是直接对对象进行清理,而是让所有存活的对象都向一侧移动,然后直接清理掉端边界以外的内存。

2)优缺点

不容易产生内存碎片;内存利用率高;存活对象多并且分散的时候,移动次数多,效率低下

JVM垃圾回收机制笔记详解_第4张图片


四、Java有了GC同样会出现内存泄露问题

  1.静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。

Static Vector v = new Vector(); 
for (int i = 1; i<100; i++) 
{ 
    Object o = new Object(); 
    v.add(o); 
    o = null; 
}

  在这个例子中,代码栈中存在Vector 对象的引用 v 和 Object 对象的引用 o 。在 For 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 o 引用置空。问题是当 o 引用被置空后,如果发生 GC,我们创建的 Object 对象是否能够被 GC 回收呢?答案是否定的。因为, GC 在跟踪代码栈中的引用时,会发现 v 引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。

  2.各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。

  3.监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。


参考文献:

https://www.cnblogs.com/DengGao/p/jni.html

https://www.cnblogs.com/dolphin0520/p/3783345.html

https://www.cnblogs.com/xiaoxi/p/6486852.html

https://blog.csdn.net/youyou1543724847/article/details/52728244

https://blog.csdn.net/jiankunking/article/details/85626279

你可能感兴趣的:(java基础)