前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >简单介绍GC

简单介绍GC

作者头像
码上积木
发布2021-04-16 10:28:09
7690
发布2021-04-16 10:28:09
举报
文章被收录于专栏:码上积木码上积木
前言

上篇说到Handler的内存泄露,其实是因为主线程作为GC Root间接引用到了Handler。今天就来简单介绍下JVM中的GC,垃圾回收。

GC

GC,全程Garbage Collection,垃圾回收。

垃圾,就是内存中没有用的对象。

在Java中,我们无需手动释放内存,在JVM中有垃圾回收器自动帮我们回收。

JVM中如何决定对象是否可以回收

JVM中通过可达性分析算法来决定对象是否可以回收。

具体做法就是把内存中所有对象之间的引用关系看做一条关系链,

比如A持有B的引用,B持有C的引用。而在JVM中有一组对象作为GC Root,也就是根节点,然后从这些节点开始往下搜索,查看引用链,最后判断对象的引用链是否可达来决定对象是否可以被回收。

为了方便大家理解,我画了一张图来说明:

很明显,ABCD四个引用都是GCRoot可达的,通俗点讲,就是跟GCRoot直接或间接有关系,有线连着的。而EF虽然直接连着线,但是他们和GCRoot是没关系的,也就是GCRoot不可达的对象组。

所以当GC发生的时候,EF就会被回收。

GC发生的内存区域

在说GC发生的内存区域之前,我们先聊聊JVM中的内存分配。

在JVM中,主要有内存分成了五个数据区域:

  • 程序计数器:线程私有,主要用作记录当前线程执行的位置。
  • 虚拟机栈:线程私有,描述Java方法执行的内存模型。
  • 本地方法栈:线程私有,描述本地(native)方法执行的内存模型。
  • :存放对象实例。
  • 方法区:存放类信息、常量、静态变量等

通过上面的介绍,我们了解到前三个都是线程私有,所以会随着线程的死亡而消失。

而后面两块内存区域,也就是堆和方法区是所有线程共有的,如果不处理可能内存就会一直增长,直到超出可用内存。所以需要借助GC机制对这些区域内的无用内存进行回收,特别是堆区的内存,因为堆区就是存储对象实例的。

GC发生的时机

那具体什么时候会被回收呢?主要有两种情况:

  • 在堆内存中分配时,如果因为可用剩余空间不足导致对象内存分配失败,这时系统会触发一次 GC。
  • 在应用层,开发者可以调用System.gc()来请求一次 GC。

GCRoot的类型

刚才说过了可达性分析算法,所以大家应该知道GCRoot的重要性了。

GCRoot,说白了就是JVM认证的可以作为老大的人选,只有这些对象是可以作为引用链的头头,掌管并保护着有用的引用。

在Java中,有以下几种对象可以被作为GCRoot,这些对象是不会被GC的:

  • Java 虚拟机栈(局部变量表)中的引用的对象。

这里又涉及到一个问题了,什么是局部变量表。

刚才说过虚拟机栈是用于支持方法调用或者执行的数据结构,具体是怎么操作的呢?

当某个方法被执行,就会在虚拟机栈中创建一个栈帧,也就是一个方法就对应着一个栈帧,栈帧会管理方法调用和执行所有的数据结构。

而栈帧中又分为几块存储空间,进行存储方法对应的不同的数据结构,比如局部变量表就是用于存储方法参数和方法内创建的局部变量。

所以这第一个GC Root 指得就是方法的参数或者方法中创建的参数。

public class GCTest {
    public static void test1(){
        //局部变量作为GCRoot
        GCRoot root=new GCRoot();
        System.gc();
    }
}

顺便说下栈帧中其他几个内存结构:

  • 局部变量表:存储方法参数和方法内创建的局部变量
  • 操作数栈:后入先出栈。当方法执行过程中,就会通过操作数栈来进行参数传递,又或者进行加数
  • 动态连接:支持方法调用过程中的动态连接。
  • 返回地址:在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态,而这个返回地址区域就是用于存储返回地址信息的。一般方法正常退出时,是可以将调用者的PC计数器值作为返回地址。
  • 方法区中静态引用指向的对象。

这个很好理解,指得就是静态变量。

public class GCTest {
    private static GCRoot root2;
    public static void main(String[] args) {
        //静态变量作为GCRoot
        root2=new GCRoot();
        System.gc();        
    }
}
  • 仍处于存活状态中的线程对象。

活着的线程,比如主线程,上一篇文章就说过Handler内存泄露的原因就是被主线程所引用,所以无法被回收。

 Thread root3=new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    public void test2(){
        //活着的线程作为GCRoot
        root3.start();
        System.gc();
    }
  • Native 方法中 JNI 引用的对象。

在JNI中有如下三种引用类型可供使用:

  • 局部引用
  • 全局引用
  • 弱全局引用

其中局部引用和全局引用都可以作为GC Root,不会被GC回收。

总结

周末愉快~

参考

https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1856

感谢大家的阅读,有一起学习的小伙伴可以关注下公众号—码上积木❤️ 每日一个知识点,建立完整体系架构。

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

本文分享自 码上积木 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • GC
  • JVM中如何决定对象是否可以回收
  • GC发生的内存区域
  • GC发生的时机
  • GCRoot的类型
  • 总结
  • 参考
相关产品与服务
云硬盘
云硬盘(Cloud Block Storage,CBS)为您提供用于 CVM 的持久性数据块级存储服务。云硬盘中的数据自动地在可用区内以多副本冗余方式存储,避免数据的单点故障风险,提供高达99.9999999%的数据可靠性。同时提供多种类型及规格,满足稳定低延迟的存储性能要求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档