垃圾收集器与内存分配策略 -- 如何判断对象是否存活(1)

背景与目的:在Java与C++之间有一个由内存动态分配与垃圾回收技术所组成的围墙,我们通过学习这围墙的知识可以更好的解决:出现各种内存溢出,内存泄漏时,当垃圾收集成为系统达到更高并发量的瓶颈时的问题。

回收对象:Java内存运行时区域中的程序计数器,虚拟机栈,本地方法栈都是跟线程相同的生命周期,在线程结束后,内存自然也就随着回收了。而Java堆与方法区不一样,我们只有在运行时才能知道对象具体分配的内存大小,内存的创建于回收都是动态的,因此垃圾收集器最主要关注的就是这部分的内存。

既然垃圾收集器要将堆中的对象进行回收,那就要判断哪些对象是可以进行回收,哪些是不可以回收的,即判断哪些是不可能再被任何途径所使用的对象。

如何判断对象可以回收

一,引用计数算法(Reference Counting)

算法原理:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就添加1,反之减1;如果对象的引用计数器值为0,说明该对象不被任何地方引用。

算法优点:实现简单,判断效率高,可以立即回收垃圾(一个对象的引用计数归零的那一刻即是它成为垃圾的那一刻,同时也是它被回收的那一刻),没有暂停时间。

算法缺点:每次赋值操作的时候都要做相当大的计算,尤其这里面还有递归调用;无法解决对象相互引用的问题,因此在主流的Java虚拟机中并没有采用这种算法来管理内存。

计数器的值的变化是由mutator引起的,比如以下代码,该例子来源 (引用计数算法 https://zhuanlan.zhihu.com/p/27939756

A objA = new A();
B objB = new B();
objA.ref = objB;

objA与objB的引用分析如图

objA与objB的引用分析1

objA与objB的引用计数器本来均为1,但是 objA.ref也引用了objB,因此objB的计数器值为2.
如果我们添加这样的代码

objA.ref = null;

那么objA.ref 没有引用objB,objB的计数器值就改为为1
那如果说objB也有一个字段引用到了objA呢?

A objA = new A();
B objB = new B();
objA.ref = objB;
objB.ref = objA;

两个的引用分析图

objA与objB的引用分析2

那么这两个对象的引用计数就都是1,在使用引用计数算法时就无法将这连个对象回收,产生循环引用问题。

二,可达性分析算法(Reachability Analysis)
在Java虚拟机中使用的是这种算法来进行垃圾收集。
算法原理:将一系列称为"GC Roots"的对象作为起始点,从这些节点开始往下搜索,搜索走过的路径称为路径链(Reference Chain),当一个对象到 GC Roots 没有任何引用链连接,那么说明GC Roots到此对象不可达,说明此对象不可用。

比如图中的Object5,Object6,Object7 是判定可以回收的对象。(图来源: 周志明的《深入理解Java虚拟机》)

GCRootReachAnalysis.png

在Java语言中,可作为GC Roots对象的由下面几种:
①虚拟机栈(栈帧中的本地变量表)中引用的对象。
②方法区中的类静态属性引用的对象。
③方法区中的常量引用对象。
④本地方法栈中JNI(Native方法)引用的对象。

关于GC Roots的正确定义和在 Java可达性分析算法会不会出现循环引用问题?https://www.zhihu.com/question/29218534/answer/43580432 中 对 GC Roots有这样的解释:

GC Root在对象图之外,是特别定义的“起点”,不可能被对象图内的对象所引用。
一个常见的误解是以为GC Root是一组对象。
实际情况是GC Root通常是一组特别管理的指针,这些指针是tracing GC的trace的起点。它们不是对象图里的对象,对象也不可能引用到这些“外部”的指针,所以题主想像的情况无法成立。
另外,tracing GC能正确处理循环引用,保证每个活对象只会被访问一次就能确定其存活性。对象图里是否存在循环引用,tracing GC都能正确判断对象的存活与否。

相对于引用计数算法,可达性分析算法的优势是避免了引用计数算法中的循环引用问题。

接下来,我们根据这两种算法,具体分析两种算法是如何维护所有对象引用的
(参考文章https://www.zhihu.com/question/21539353/answer/95667088

按照我们上面说的,采用引用计数算法需要在每个实例对象创建之初,通过更改计数器上的引用次数来判断该对象是否可回收;而使用可达性分析算法时,每次GC时,需要遍历整个GC根节点来判断是否回收。

public class Client {
   public static void main(String[] args) {
      GcObject obj1 = new GcObject(); // Step1
      GcObject obj2 = new GcObject(); // Step2
      obj1.instance = obj2; // Step3
      obj2.instance = obj1; //  Step4
      obj1 = null; // Step5
      obj2 = null; // Step6
   }
}
class GcObject{
   public Object instance = null;
}

根据我们上面说明的,如果采用引用计数算法,最终obj1 与 obj2 两个实例相互引用,引用计数器上都为1,都不会被GC回收

使用引用计数算法 JVM分析图

使用引用计数算法 JVM分析图1

Step1:obj1指向堆中的 GcObject实例1,此时 GcObject实例1的引用计数器值由0变为1;
Step2:obj2指向堆中的 GcObject实例2,此时 GcObject实例2的引用计数器值由0变为1;
Step3:实例1中的instance指向 GcObject实例2, GcObject实例2的引用计数器值由1变为2;
Step4:实例2中的instance指向 GcObject实例1, GcObject实例1的引用计数器值由1变为2;

执行到这里,GcObject实例1和GcObject实例2的引用计数器值均为2。

然后执行Step5,Step6

使用引用计数算法 JVM分析图2

Step5:obj1不再指向堆中的 GcObject实例1, GcObject实例1的引用计数器值由2变成1;
Step6:obj2不再指向堆中的 GcObject实例2, GcObject实例2的引用计数器值由2变成1;

到这一步,GcObject实例1和GcObject实例2的引用计数器值均为1,GC无法回收。但是明显这两对象已经没啥用处,是可以回收的,所以产生了循环引用问题,严重还会导致内存泄漏的问题。

使用可达性分析算法 JVM分析图

使用可达性分析算法 JVM分析图.png

图中reference1,reference2,reference3都是GC Roots,根据路径链(Reference Chain)来判断:
reference1 ---> 对象实例1
reference2 ---> 对象实例2
reference3 ---> 对象实例4 ---> 对象实例6

明显对象实例3,对象实例5之间虽然存在引用,但是均与GC Roots无可达性,所以两个依旧可以回收的垃圾对象。

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

推荐阅读更多精彩内容