GC 及引用类型

gc范围

在JVM五种内存模型中,有三个是不需要进行垃圾回收的:程序计数器、JVM栈、本地方法栈。因为它们的生命周期是和线程同步的,随着线程的销毁,它们占用的内存会自动释放,所以只有方法区(java8改为metaSpace)和堆需要进行GC。

前提条件

找出不可达的对象,将其释放,这里面讲的不可达主要是指应用程序已经没有内存块的引用了, 在Java中,某个对象对应用程序是可到达的是指:这个对象被根(根主要是指类的静态变量,或者活跃在所有线程栈的对象的引用)引用或者对象被另一个可到达的对象引用。

堆内存分区

  • 新生代:Eden,from,to 8:1:1,存放新创建的对象
  • 老年代: 存放大对象和超过一定年龄的新生代对象

GC分类

minorGC

  • 时机:如果Eden上的空间不足时,新建的对象会往from区放,去过仍然放不下会触发minorGC
  • 算法: 复制清理
  • 具体实现:将eden区和from区有引用的对象复制到to区,没有引用的对象清除,并对年龄+1
  • 优缺点
    • 优点:速度快,没有内存碎片
    • 缺点:需要一个额外的空间

majorGC

  • 时机:如果马上要进入老年区的对象所需空间大于剩余空间,触发majorGC,如果GC两次都无法满足需求,就会报OOM.
  • 算法:
    • 标记清除: 1. 从引用根节点开始标记对象;2. 遍历整个堆空间,清除没有引用的对象
      • 优缺点
        • 优点:不需要额外的空间,速度相对较快
        • 缺点:内存碎片
    • 标记压缩:1. 从根节点开始标记引用对象;2. 遍历整个堆空间,清除没有引用的对象,并把存活的空间压缩到堆的一个区域。
      • 优缺点:
        • 优点:不需要额外的空间,不存在内存碎片
        • 缺点: 速度稍慢
    • 引用计数:每增加一个引用计数加一,减少一个引用计数减一,垃圾回收时只回收计数为0的对象。
      • 缺点:不能解决循环引用额问题,会引发内存泄漏

GCRoots

在Java语言里,可作为GC Roots对象的包括如下几种:

  • 虚拟机栈(栈桢中的本地变量表)中的引用的对象
  • 方法区中的类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中JNI的引用的对象

引用分类

  • 强引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
  • 软引用
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
  • 弱引用
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
  • 虚引用
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用于检测对象是否已经从内存中删除,跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
虚引用的唯一目的是当对象被回收时收到一个系统通知。
  • Reference
  static class Strong{
    String str = "hello";

    Strong(String str){
      this.str=str;
    }

    @Override
    public String toString() {
      return str;
    }
  }

  public static void main(String[] args) {
    ReferenceQueue<Object> queue = new ReferenceQueue<>();
    // 软引用
    SoftReference<Strong> softReference = new SoftReference<>(new Strong("aaa"),
        queue);
    System.out.println(softReference.get());    // aaa
    System.gc();
    System.out.println(softReference.get());    // aaa
    System.out.println(queue.poll());           // null
    // 弱引用
    WeakReference<Strong> weakReference = new WeakReference<>(new Strong("bbb"), queue);
    System.out.println(weakReference.get());    // bbb
    System.gc();
    System.out.println(weakReference.get());    // null
    System.out.println(queue.poll().getClass());  // WeakRegerence

    // 虚引用
    Strong x = new Strong("ccc");
    WeakReference<Strong> reference = new WeakReference<Strong>(x);
    PhantomReference<Strong> phantomReference = new PhantomReference<>(
        x, queue);
    System.out.println(reference.get());        // null
  }
  • weakHashMap
  public static void main(String[] args) throws Exception {

    // weakHashoMap key键是弱引用的键, 如果key被回收则get是会自动remove掉value
    String test = new String("test");
    String tmp = new String("tmp");
    Map weakmap = new WeakHashMap();
    Map map = new HashMap();
    map.put(test, "test");
    map.put(tmp, "tmp");


    weakmap.put(test, "test");
    weakmap.put(tmp, "tmp");

    map.remove(test);
    test=null;
    tmp=null;

    System.gc();
    Iterator itrTest = map.entrySet().iterator();
    while (itrTest.hasNext()) {
      Map.Entry en = (Map.Entry)itrTest.next();
      System.out.println("map:"+en.getKey()+":"+en.getValue());
    }

    Iterator itrTmp = weakmap.entrySet().iterator();
    while (itrTmp.hasNext()) {
      Map.Entry en = (Map.Entry)itrTmp.next();
      System.out.println("weakmap:"+en.getKey()+":"+en.getValue());
    }
  }
  • sofrReference
  public static void main(String[] args) throws InterruptedException {

    // 总堆内存设置200M
    //100M的缓存数据
    byte[] cacheData = new byte[100 * 1024 * 1024];
    //将缓存数据用软引用持有
    SoftReference<byte[]> cacheRef = new SoftReference<>(cacheData);
    //将缓存数据的强引用去除
    cacheData = null;
    System.out.println("第一次GC前" + cacheRef.get());  //第一次GC前[B@123772c4
    //进行一次GC后查看对象的回收情况
    System.gc();
    //等待GC
    Thread.sleep(500);
    System.out.println("第一次GC后" + cacheRef.get());  //第一次GC后[B@123772c4
    //在分配一个120M的对象,看看缓存对象的回收情况
    byte[] newData = new byte[120 * 1024 * 1024];
    System.out.println("分配后" + cacheRef.get());      // 分配后null
    
  }

垃圾回收器

CMS(并发-标记-清除)

CMS 是一种以获取最短回收停顿时间为目标的收集器。

步骤:

1.初始标记
此阶段仅仅是标记一下 GC Roots 能直接关联到的对象,速度很快,但是会停顿

注意:这里不是 GC Roots Tracing 的过程

2.并发标记
GC Roots Tracing 的过程,这个阶段可以与用户线程一起工作,不会造成停顿,从而导致整个停顿时间大大降低
3.重新标记
是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
4.并发清除

优点:停顿时间短,但是总的 GC 时间长

缺点:

1.并发程序都是 CPU 敏感的,并发标记和并发清除可能会抢占应用 CPU
2.总的 GC 时间长
3.无法处理浮动垃圾

浮动垃圾:在并发清除过程中,程序还在运行,可能产生新的垃圾,但是本次 GC 确不可能清除掉这些新产生的垃圾了,所以这些新产生垃圾就叫浮动垃圾,也就是说在一次 CMS 的 GC 后,用户获取不到一个完全干净的内存空间,还是或多或少存在浮动垃圾的。

4.由于在并发标记和并发清除阶段,用户程序依旧在运行,所以也就需要为用户程序的运行预留一定空间,而不能想其他收集器一样会暂停用户程序的运行。在此期间,就可能发生预留空间不足,导致程序异常的情况。
5.是基于标记-清除的收集器,所以会产生内存碎片

G1

独立管理整个 java heap 空间,而不需要其他收集器的配合。

步骤:

  1. 初始标记
    与CMS 一样,只是标记一下 GC Roots 能直接关联到的对象,速度很快,但是需要停顿
  2. 并发标记
    GC Roots Tracing 过程,并发执行
  3. 最终标记
    并行执行,需要停顿
  4. 筛选回收
    并行执行,需要停顿
    G1收集器把 Heap 分为多个大小相等的 Region,G1可以有计划的避免进行全区域的垃圾收集。G1跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先收集价值大的 Regin,保证 G1收集器在有限时间内获取最大的收集效率。

优点:

  1. 存在并发与并行操作,最大化利用硬件资源,提升收集效率
  2. 分代收集,虽然 G1可以独立管理整个 Heap,但是它还是保留了分代的概念,实际上,在分区时,这些区域(regions)被映射为逻辑上的 Eden, Survivor, 和 old generation(老年代)空间,使其有目的的收集特定区域的内存。
  3. 空间整合,G1回收内存时,是将某个或多个区域的存活对象拷贝至其他空区域,同时释放被拷贝的内存区域,这种方式在整体上看是标记-整理,在局部看(两个 Region 之间)是复制算法,所以不会产生内存碎片
  4. 可预测的停顿时间


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

推荐阅读更多精彩内容