前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >jvm系列之垃圾收集器

jvm系列之垃圾收集器

作者头像
六个核弹
发布2022-12-23 20:48:31
1620
发布2022-12-23 20:48:31
举报

jvm系列之垃圾收集器

1 垃圾收集器介绍

   java内存在运行时被分为多个区域,其中程序计数器、虚拟机栈、本地方法栈三个区域随线程生成和销毁;每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,在这几个区域内就不需要过多考虑回收问题,因为方法结束或者线程结束时,内存自然就跟着回收了。而堆区就不一样了,我们只有在程序运行的时候才能知道哪些对象会被创建,这部分内存是动态分配的,垃圾收集器主要关注的也就是这部分内存。

2 垃圾收集器算法

   jdk11刚发布不久,这个版本发布了一款新的垃圾收集器——G1垃圾收集器,这款垃圾收集器有很多优异的特性,我会在后文做介绍,这里先从简单的慢慢说起。

   引用计数算法是最初垃圾收集器采用的算法,也是相对简单的一种算法,其原理是:给对象中添加一个引用计数器,每当有一个地方引用它的时候这个计数器就加一;当引用失效,计数器就减一;任何时刻计数器为0则该对象就会被垃圾收集器回收。这种算法的缺点是当对象之间相互循环引用的时候,对象将永远不会被回收。举个例子——有类TestOne,类TestTwo;它们互相是对方的成员,如下:

代码语言:javascript
复制
 public static void main(String[] args) {
    TestOne testOne=new TestOne();
    TestTwo testTwo=new TestTwo();
    testOne.obj=testTwo;
    testTwo.obj=testOne;
    testOne=null;
    testTwo=null;
}

理论上当代码执行到testTwo=null的时候 new TestOne() new TestTwo() 两块内存应该要被回收的,但是因为它们相互引用对方导致引用计数器不为0,所以这两块内存没有引用指向它们却无法被回收——这便是这种算法所存在的问题。

   可达性分析算法是使用比较广泛的算法。这个算法的基本思路是通过一系列的称为“GC Roots”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称作引用链;当一个对象和GC Roots之间不存在引用链的时候,这个对象将被回收;也就是说一个存活的对象向上追溯它的引用链,其头部必然是GC Roots,如果不是将被回收。在虚拟机中可以作为GC Roots的可以是:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象,本地方法栈中Native方法引用的对象;在堆区一个存活的对象被这些对象所直接引用或间接引用(引用又分为强引用、软引用、弱引用、、虚引用,引用强调依次降低,感兴趣的可以详细了解一下)。 当一个对象的引用链中没有GC Roots的时候并不会被马上回收,第一次他会被标记并筛选,当对象没有覆盖finalize()方法或该方法已经被虚拟机调用过,那么它会被放入一个叫做F-Queue的队列中等待被虚拟机自动回收;否则虚拟机会执行finalize()方法——当我们没有重写finalize()方法时,对象内存自然被回收掉,如果重写了这个方法,那么结果就会变得很有趣,下面做一个示例:

代码语言:javascript
复制
public class Main {
    public static  Main test=null;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("执行了一次 finalize()");
        Main.test=this;
    }

    public static void main(String[] args) {
        test=new Main();
        // 让test失去 GC RootS
        test=null;
        // 调用 finalize()方法
        System.gc();
        // sleep一会确保finalize()方法执行
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 因为在finalize()方法中重新将this(也就是 new Main())赋值给了test 所以没被回收
        if(test!=null){
            System.out.println("对象存活了下来");
        }else{
            System.out.println("对象死了");
        }
        // 再来一次
        test=null;
        System.gc();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 这一次却死了,因为finalize()方法已经被执行过,虚拟机直接将对象扔到 F-Queue里面等待回收
        if(test!=null){
            System.out.println("对象存活了下来");
        }else{
            System.out.println("对象死了");
        }
    }

}

运行结果:

执行了一次 finalize() 对象存活了下来 对象死了

3 回收方法区

   因为方法区的内存回收条件很苛刻,因此方法区被人称作永久代,在这个区域回收的内存主要为废弃的常量和无用的类;那么如何判定一个常量是否废弃呢?比如当一个字符串进入了常量池,但没有任何地方引用它,如果此时发生了内存回收,那么这个常量就会被清除出常量池——发生场景:一个类有一个成员 pubulic static String test="aaa";当这个类被加载的时候"aaa"进入常量池,当其他地方没有字符串等于"aaa"的时候并且此时这个类由于某种原因被卸载掉,此时这个"aaa"将会被回收。如何判定一个类是无用的类呢?需要满足三个条件:

该类所有的实例都被回收 加载该类的ClassLoader已经被回收 该类的Class对象没在任何地方被引用,无法通过反射访问该类

### 写在末尾 本来还想写垃圾回收的算法的,结果时间不太够,那就留在下一次写吧。微信留言功能不能开通,有没有大佬指点一下是怎么回事?开通留言和大佬们沟通一波岂不是美滋滋。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 六个核弹 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • jvm系列之垃圾收集器
    • 1 垃圾收集器介绍
      • 2 垃圾收集器算法
        • 3 回收方法区
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档