Java GC系列一:原理

一、什么是GC

GC(Garbage Collecor)是JVM的内存回收器,当应用使用的内存不足时,会导致OOM(Out-Of-Memory)。

Java提供的GC可以自动监测对象是否超过作用域从而达到自动回收内存的目的(Java没有提供主动释放已分配内存的方法)。

Java的GC会自动管理内存,如果要主动请求内存回收,可以调用以下方法:

  • System.gc()
  • Runtime.getRuntime().gc()

二、GC如何管理内存(GC算法)

当我们创建对象时,JVM就会为我们创建的对象分配一块内存,那么这块内存GC如何管理呢?

引用计数法(该算法无法解决循环引用的情况,导致内存无法释放,GC已不使用该方法):

  • 对象创建时,初始化计数为1;
  • 每当有一个地方引用它,计数就+1;
  • 每当有一个地方引用失效时,计数就-1;

可达性分析法(GC目前彩该方法):

该方法的基本思想是通过一系列称为“GC Roots”的对象作为起点,从这些点向下搜索,搜索走过的路径称为“引用链”,当某个对象到GC Roots没有任何引用链(即GC Roots不可达至该对象)时,则证明该对象是不可用的,就可以回收该对象内存。

其它算法(会在以后分别分析)

三、GC Roots

如何选择GC Roots呢?在Java中,可以作为GC Roots的包括以下几种:

  • 系统类加载器(bottstrap)加载的类;
  • JVM虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用对象;
  • JVM方法区中的类静态属性引用的对象;
  • JVM常量池中引用的对象;
  • JVM本地方法栈中JNI(Native方法)引用的对象;
  • 活动着的线程

下面给出一个GC Roots的图例:

gc roots.png

上图为GC Roots的引用链,obj8, obj9, obj10都没有到GC Roots对象的引用链,因此会被回收。

四、对象引用以及基于可达性分析的内存回收原理

references.png

对于可达性分析算法而言,未到达的对象并非是“非死不可”的,若要宣判一个对象的死亡,至少需要经历两次标记阶段:

  1. 如果对象在进行可达性分析后发现没有与GC Roots相连的引用链,则对该对象进行第一次标记并进行一次筛选,筛选的条件为:

是否有必要执行该对象的finalize方法

  • 若对象没有覆盖finalize方法或者该finalize方法已经被JVM执行过了,则均视为不必要执行对象的finalize方法,即该对象将会被回收;
  • 若对象覆盖了finalize方法且没有被执行过,则该对象会被放置在一个叫F-Queue的队列中,之后会由JVM自动建立优先级低的Finalizer线程去执行,而JVM不需要等待该线程执行结束,即JVM只负责建立线程,其它事则交由该线程去处理;
  1. 对F-Queue中的对象进行第二次标记:
  • 如果对象在finalize方法中拯救了自己,即关联上了GC Roots引用链(如把this关键字赋值给其它变量),那么在第二次标记时该对象将从“即将回收”的集合中移除;
  • 如果对象没有拯救自己,那么就会被回收;

以下代码演示了对象如何在finalize方法中拯救自己(然后,它只能自救一次,第二次仍旧被回收):

public class GC { 
    
     public static GC SAVE_HOOK = null; 
    
     public static void main(String[] args) throws InterruptedException {
          // 新建对象,因为SAVE_HOOK指向这个对象,对象此时的状态是(reachable,unfinalized)
          SAVE_HOOK = new GC(); 
          // 将SAVE_HOOK设置成null,此时刚才创建的对象就不可达了,因为没有句柄再指向它了,
          // 对象此时状态是(unreachable,unfinalized)
         SAVE_HOOK = null; 
         // 强制系统执行垃圾回收,系统发现刚才创建的对象处于unreachable状态,
         // 并检测到这个对象的类覆盖了finalize方法,因此把这个对象放入F-Queue队列,
         // 由低优先级线程执行它的finalize方法,此时对象的状态变成(unreachable, finalizable)
         // 或者是(finalizer-reachable,finalizable)
         System.gc(); 
         // sleep,目的是给低优先级线程从F-Queue队列取出对象并执行其finalize方法提供机会。
         // 在执行完对象的finalize方法中的super.finalize()时,对象的状态变成(unreachable,finalized)状态,
         // 但接下来在finalize方法中又执行了SAVE_HOOK = this;这句话,又有句柄指向这个对象了,对象又可达了。
         // 因此对象的状态又变成了(reachable, finalized)状态。
         Thread.sleep(500);
         // 这里楼主说对象处于(reachable,finalized)状态应该是合理的。对象的finalized方法被执行了,
         // 因此是finalized状态。又因为在finalize方法是执行了SAVE_HOOK=this这句话,本来是unreachable的对象,
         // 又变成reachable了。
         if (null != SAVE_HOOK) { //此时对象应该处于(reachable, finalized)状态 
             // 这句话会输出,注意对象由unreachable,经过finalize复活了。
             System.out.println("Yes , I am still alive"); 
         } else { 
             System.out.println("No , I am dead"); 
         } 
         // 再一次将SAVE_HOOK放空,此时刚才复活的对象,状态变成(unreachable,finalized)
         SAVE_HOOK = null; 
         // 再一次强制系统回收垃圾,此时系统发现对象不可达,虽然覆盖了finalize方法,但已经执行过了,因此直接回收。
         System.gc(); 
         // 为系统回收垃圾提供机会
         Thread.sleep(500); 
         if (null != SAVE_HOOK) { 
             // 这句话不会输出,因为对象已经彻底消失了。
             System.out.println("Yes , I am still alive"); 
         } else { 
             System.out.println("No , I am dead"); 
         } 
     } 
    
     @Override 
     protected void finalize() throws Throwable { 
         super.finalize(); 
         System.out.println("finalize method executed!"); 
        // 这句话让对象的状态由unreachable变成reachable,就是对象复活
         SAVE_HOOK = this; 
     } 
 }  

运行结果如下:

    finalize method executed!
    yes, i am still alive
    no, i am dead

由此可见,该对象只能拯救自己一次,因为对象的finalize方法最多被JVM调用一次。

此外,我们还可以得知,一个堆对象的this(放在局部变量表中的第一项)引用会永远存在,在方法体内可以将this引用赋值给其它变量,这样堆中对象就可以被其它变量所引用,即不会被回收。

五、方法区的垃圾回收

  1. 方法区的垃圾回收主要回收两部分内容:
  • 废弃常量;
  • 无用的类;

既然进行垃圾回收,就需要判断哪些是废弃常量,哪些是无用的类?

如何判断废弃常量呢?以字面量回收为例,如果一个字符串“abc”已经进入常量池,但是当前系统没有任何一个String对象引用了叫做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

如何判断无用的类呢?需要满足以下三个条件:

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

推荐阅读更多精彩内容