Java虚拟机(JVM) 之 GC

其实我以前看到提到 JVM 之类的东西,就觉得这玩意太难,看不下去啊。可是要进阶吧,又是必须的啦,所以说一句 干就完了


image

今天就来学学 GC,想问就我一个人想到中文的滚粗吗


image

其实这么理解也可以啦,GC 是什么意思呢,可以大概这么理解 公厕只有这么些个,偏偏有些人占着茅厕不那啥,只好让管理员让它滚粗啦,好让其它人可以用,让世界和平。

好了好了 回来了回来了,为什么会有 GC (回收垃圾,释放内存)呢?因为内存就这么大,用完了不回收,就一直占用着,可以用的内存就越来越小,到最后溢出了(OOM),程序也就GG了。

那么问题来了,怎么知道这个占用着的对象是不是垃圾呢?

一:引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1,当引用失效时,计数器就减 1,任何时刻计数器为 0 的对象就是不可能再被使用的,就是垃圾了,可以回收了。(但是主流的虚拟机并不是用这种方法)

二:可达性分析算法

讲解这个之前,我觉得还是要先知道 Java 运行时的内存区域是怎么区分的。自行百度?哈哈
网上找了张图:


image

什么意思呢?
意思就是,这个算法呢就是通过一系列称为 “GC Roots” 的对象作为起点,从这些起点向下搜索,搜索走过的路径称为 引用链,当一个对象到 GC Roots 没有引用链的时候,我们就可以指着它的鼻子骂“垃圾”,等着被回收吧?

那么就有人懵逼了 GC Roots 是什么玩意啊,我不知道啊

举个我认为的例子:看过战狼2的都知道,有了中国的护照,我们就是祖国花朵(哈哈),到哪都是中国人的身份,那么国家就是我们最强的后盾。那么我们就可以把 中国理解为 GC Roots,护照就是筛选是否是中国人的 引用链,有就有中国做我们的后盾,没有就等着被回收(没被的意思....)

那么在程序中 什么可以作为 GC Roots呢?
  • 虚拟机栈中的引用
  • 方法区中类静态属性引用的对象
  • 方法区中常量的引用对象
  • 本地方法栈中 JNI(Native) 引用的对象
什么时候回收

不同的虚拟机实现有着不同的 GC 实现机制,但是一般情况下每一种 GC 实现都会在以下两种情况下触发垃圾回收。
Allocation Failure:在堆内存中分配时,如果因为可用剩余空间不足导致对象内存分配失败,这时系统会触发一次 GC。
System.gc():在应用层,Java 开发工程师可以主动调用此 API 来请求一次 GC。

那么这里就引出了 引用 的概念,全部有4种引用
  • 强引用:
    就是代码中直接 new 对象的引用,这种是永远不会被回收的
  • 软引用:
    用来描述一些非必须的对象,弱引用的对象 将在内存将要发生内存溢出异常之前,会被列进回收范围进行二次回收,如果回收之后内存还不够,才报溢出异常
  • 弱引用:
    也是用来描述一些非必须的对象,但是它是 无论内存够不够都会被会回收
  • 虚引用:
    仅持有虚引用的对象,在任何时候都可能被GC,设置虚引用的唯一目的:就是能在这个对象呗回收时收到一个系统通知。

注意:即使在可达性分析算法中不可达的对象,也不是 非死不可的,这时候它们暂时处于“缓刑”,真正死亡,至少要经历两次标记的过程:判断对象是否有必要执行finalize()方法;若被判定为有必要执行finalize()方法,之后还会对对象再进行一次筛选,如果对象能在finalize()中重新与引用链上的任何一个对象建立关联,将被移除出“即将回收”的集合。任何对象的 finalize()方法,只会执行一次。

public class FinalizeEscapeGC {

    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("yes, i am still alive :)");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize mehtod executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();

        //对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }

        // 下面这段代码与上面的完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
        // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }
    }
}
#################
运行结果:
finalize mehtod execute!
yes, i am still alive :)
no, i am dead :(
两段代码是一样的,执行结果一次成功,一次失败,这就是上面提到的任何对象的 finalize()方法,只会执行一次。

那么问题来了,知道是垃圾后,要怎么去回收呢?当然就是每个人都会说的垃圾收集算法啦

  • 标记 — 清除算法

顾名思义,是垃圾 就 标记,然后把标记的给 清除 就好了(标记过程就是上面讲的)
缺点:效率,空间问题。标记清除后会产生大量的不连续内存碎片。

为啥会有这样的缺点,不理解?举个例子:

你种了好多大白菜,一眼看过去,好多个点都有坏死的,而且都不是同一个区域,你依次走过去,标记是要扔掉的,然后就去清理了,这样下来效率就很慢了,而且你拔掉的地方那么小,翻土重新种又太小,容易弄坏其他的白菜,只能空着,那么就是空间问题了。

复制算法

把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象 复制 到另外一块上面,再将这一块内存空间一次清理掉。
例子:这个就更容易理解了,叫了两打啤酒,每次喝都随机两打里面拿,要结账的时候,把空瓶装一箱,没喝存起来的装一箱,结账时候清理空瓶的那一箱就好了。
缺点:为了解决效率问题。代价 内存缩小了一半

标记 — 整理算法

首先 标记 出所有需要回收的对象,然后进行 整理,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。
例子 :平常我们自己整理房间就是啦,杂乱的房间中 不想要的扔掉,不要让它继续堆放在房间占位置,把空出来的存放自己想要的东西,
优点:即没有浪费50%的空间,又不存在空间碎片问题,性价比较高。
一般情况下,老年代会选择标记-整理算法。

分代收集算法

根据对象存活周期的不同将内存划分为几块。一般是把 JAVA 堆分为新生代和老年代,然后根据各个年代的特点采用最适当的收集算法。
新生代:回收时,有大批对象死去,只有少量存活,适合复制算法。
老年代:存活率高,没有额外的分配担保空间,必须使用 标记 — 清理,或 标记 — 整理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353