GC入坑路

305 阅读4分钟

初入GC(一)

一、GC垃圾定义

GC全称Garbage Collection,翻译:垃圾收集。GC主要完成的有3件事:

  1. 哪些内存需要回收?
  2. 什么时候回收?
  3. 如何回收?

二、定位垃圾

首先,设想一下我家lucky(一只猫)像房间里丢毛线团,毛线团A的一头在lucky的手上,另一头连接着毛线团B,还有其他毛线团C、D......都是lucky不想玩了的,那么在这个场景中,整个房间就是JVM中的堆内存,毛线团A、毛线团B就是存活对象,其余不想玩了的,又没有AB毛线团缠绕上去的就是规定的垃圾。

所以,在GC中,有两种定位垃圾的算法:

  1. 引用计数算法,通过对象自身进行判断,若有对象指向到自身,则自身标记计数+1,最后统计自身标记计数为0的即为垃圾 缺点:这种算法会排除掉循环引用的一坨垃圾,如:A-->B-->C-->A

  2. 根可达算法,通过GC Roots为起点往下找,搜索到的路径被称为引用链,能找到的对象皆为存活对象,无法找到的即为垃圾。 其中,能作为GC Roots的有:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
    • 方法区中类静态属性引用的对象;
    • 方法区中常量引用的对象;
    • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

三、垃圾回收算法

目前GC存在三种垃圾回收算法:

标记清除:即我进入lucky玩耍的房间,找到lucky没有玩的毛线团,将其做上标记,最后将这些毛线团回收起来,但是这样出现的问题是,当我给lucky清理了好久后,发现满房间都是它丢的线团,最后它想玩一个大的线团,我都没地方放。(lucky丢的到处都是,我会打它!)

因此,标记清除带来的缺点就是会产生无数个碎片化的内存空间,当创建一个大对象时,可能没有一个足够大的内存空间容纳该对象。

复制:即我把lucky玩耍的房间隔成两个区域,我只允许lucky向其中一个区域丢线团,当我再去帮它清理的时候,我会将它正在玩的线团移动到另一个区域中,然后一下把这个满是垃圾的区域清理掉,同样,这也会出现一个问题,lucky每次玩耍的空间只有房间的一半,当我房间足够大时,lucky浪费的空间也就大。(lucky浪费我的空间,就是浪费我的钱,我会打它!!)

因此,复制算法带来的缺点就是会严重浪费掉内存的使用空间。

标记压缩:即我给lucky一整个房间去玩,当我进去给它打扫房间的时候,我需要把它正在玩的线团堆到一起,然后把剩余的垃圾清理掉,可是,这会让我异常的累。(lucky让我不胜腰力,我打不动它了!!!)

因此,标记压缩带来的缺点就是效率太低了。

四、GC模型

  1. 分代模型: Long long ago,GC将内存分为新生代(young/new代)和老年代(old代),其比例为1:2,新生代再分为Eden和两个Survivor区,或者分为Eden区、From区、To区,其比例为8:1:1,当然,这些比例都可以根据参数进行设置的,只是根据大量数据表明(我也不知道是什么数据),一般在第一次清理过后,能存活下来的对象只有1/8。

    我们想象一下,lucky往我的套房中丢线团,首先我只允许它向主卧扔,等它扔的差不多了,我就开始进去打扫,将它还在玩的线团丢到次卧A,并给线团长1岁,然后将主卧里的垃圾全部清理掉,然后lucky继续丢线团,在我第二次去清理的时候,我会将主卧中还在玩的线团以及次卧A中还在玩的线团一起丢到另一个大小相同的次卧B,同时线团的年龄就会长1岁,然后再把次卧A和主卧全部清理掉,经过一段时间的清理后,我会把那些已经到达清理次数阈值的存活线团丢到客厅中。

    在以上场景中,主卧就是Eden区,两个次卧就是Survivor区或者叫From和To区,客厅就是Old区,当GC清理到达一段次数后就会进去Old区,其中CMS默认年龄为6,其他GC默认年龄为15。

新老持元.png

逻辑分代.png

  1. 分区模型:

    将整个堆空间划分为连续的不同⼩区间, 每个⼩区间独⽴使⽤, 独⽴回收. 这样做的 好处是可以控制⼀次回收多少个⼩区间 , 根据⽬标停顿时间, 每次合理地回收若⼲个⼩区间(⽽不是 整个堆), 从⽽减少⼀次GC 所产⽣的停顿

分区模型.png

未完待续...