JVM(2)-垃圾收集器与内存分配策略

2018-05-09

一、垃圾收集器

1.如何确定对象已死

1.1.引用计数法-Reference Counting

给对象添加一个引用计数器,当有新的地方引用它时,引用计数器加1,当引用失效时,计数器减1,任意时刻计数器为0的对象就是不可能被再使用了。这种方式实现简单且高效,但是很难解决循环引用的问题,例如有两个对象A、B,除了相互引用之外,并没有可达引用可以访问到它们中的任意一个,这种情况下其实它们已经是垃圾对象,但是它们的引用计数器都不为零。

1.2.可达性分析-Reachability Analysis

这个方法的基本思想是通过一系列的称为 “GC Root”的对象作为起点,从这些节点往下搜索,所经过的路径称为引用链,当一个对象到“GC Root”没有任何引用链相连时,则证明此对象是不可用的。这一些列GC Root对象是哪些对象呢?

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

note: 关于Java中的4中引用可参考文章《Java中的4种引用类型》

2.生存还是死亡

  1. 当一个对象与GC Root没有任何引用链时,那么它会被第一次标记并且进一步筛选,筛选的条件是此对象有没有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被调虚拟机调用过,虚拟机将这两种情况视为“没有必要执行”。
  2. 若有必要执行,则将对象放置一个F-Queue的队列中,并在稍后有虚拟机自动建立的、优先级低的Finalizer线程去执行,但并不承诺等待它运行结束,因为不知道finalize()方法的的耗时,盲目等待有可能造成阻塞。
  3. finalize()方法是对象逃脱死亡命运的最后一次机会,如果在finalize()方法中成功拯救了自己,那就可以继续存活,如果没有逃脱,那么它就真的被回收了。

3.方法区回收

  1. 废弃常量回收,当一个常量没有被任意一个引用变量引用时,此常量就是废弃常量;

  2. 无用类卸载,无用类要满足下面3个条件:

  • 该类的所有实例都被回收了,即Java堆中不存在该类的任何实例;
  • 加载该类的ClassLoader已经被回收;
  • 该类所对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过发射访问该类的方法。

4.垃圾回收算法

4.1.标记-清除(Mark-Sweep)

首先标记所有可回收的对象,在标记完成后,统一回收。
特点:标记清除这两个过程效率都不高,另外一点就是出现内存碎片化问题;

4.2.复制算法(Coping)

将可用内存分为大小相等的两块,只使用其中一块,当这一块用完时,将还存活的对象赋值到另外一块中,然后在把这一块上已使用的一次回收掉。
特点:实现简单、高效,但这使得可用内存变为原来的一半,代价有点儿高。
现代商业虚拟机在新生代中采用这用算法回收,将新生代按照 8:1 的比例分为 Eden 区和两个 Survivor 区,每次只使用Eden和其中一个Survivor,当使用完时,将Eden和Survivor上的存活对象复制到另外一个Survivor上,如果存活的对象超过Survivor的大小,则使用老年代。这样每次使用的内存就是原来的90%,浪费的只有10%。

4.3.标记-整理(Mark-Compact)

标记过程与4.1类似,但后续过程是让所有存活对象向一端移动,让后直接清理掉边界以外的内存。

4.4.分代回收算法

这并不是一种新的思想,而是根据具体的场景采用合适的回收算法。Java虚拟机一般把堆分为新生代和老年代,新生代的特点是每次垃圾收集时都发现大批对象死去,存活率低,适合采用复制算法;而老年代存活率很高,则采用“标记-清除”或者“标记-整理”算法来进行回收。

5.HotSpot实现

5.1根节点枚举

Stop the world,OopMap,不会为每条指令生成对应的OopMap,只在特定的位置记录这些信息,即安全点。

5.2.安全点

抢占试中断,在发生GC时,中断所用用户线程,如果发现某个线程不在安全点,则恢复此线程,让其跑到安全点。现在基本没有虚拟机这个干。
主动式中断,当发生GC时,只是设置标记位,让用户线程自己在安全点检查这个标记,如果是ture则自己中断挂起,另外一个检测的地方时需要分配内存的地方。

5.3.安全区域

SafePoint似乎能很好的解决进入GC的问题,但试想这样一个场景,当某些线程处于sleep或者blocked状态时,虚拟机很难说等待这些线程得到CUP资源并跑到附近的安全点上,这个时间是不确定的,这就引入了安全区域的概念,当一个线程进入安全区域时,标记位自己在安全区,发生GC时,就不用管在安全区域的线程了,当要离开安全区时它检查是否已经完成了GC,否则中断挂起等待。

6.垃圾收集器

来源于: https://blogs.oracle.com/jonthecollector/our-collectors

图中的连线表示垃圾收集器可以配合工作

6.1.Serial

新生代收集器,单线程GC,采用复制算法,所用用户线程跑到SafePoint中断,然后执行GC,GC完恢复用户线程,这会导致Stop The World。

6.2.ParNew

多线程版的 Serial。

6.3.Parallel Scavenge

新生代收集器,复制算法,并行多线程收集器,看上去和ParNew一样,但它的关注点不一样,前两个收集器的关注点是尽量缩短GC导致用户线程的停顿时间,而这个收集器的关注点是吞吐量,吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。

6.4.Serial Old

老年代收集器,单线程,使用“标记-整理”算法。

6.5 Parallel Old

是Parallel Scavenge的老年代版本,使用“标记-整理”算法。

6.7 CMS

Concurrent Mark Sweep收集器,关注点是获得最短的回收停顿时间,从名字就看得出来是“标记-清除”算法,它运作过程分为四个步骤:

  • 初始标记(CMS initial mark)- Stop The World
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark)- Stop The World
  • 并发清除(CMS concurrent sweep)

在四个步骤中,其中并发标记、并发清除时间是相对较长的,都是可以和用户线程并发执行的,所以Stop The World时间是很短的,总体上来看就是并发执行的,这对要求响应速度较快的应用场景比较适合。CMS还远达不到完美,它有一下几个缺点:

  • 对CUP资源敏感,抢占CUP资源将导致用户线程的CUP资源减少而变得缓慢;
  • 无法处理浮动垃圾,在并发回收垃圾时,用户线程会产生新的垃圾对象,这些垃圾要等下次回收;由于在并发回收的过程用户线程还在工作,这就需要预留一定的内存空间给用户线程,导致内存空间利用率下降;
  • CMS采用的是标记-清除算法,这就导致内存碎片化。若出现内存空间还很多,但由于碎片化的情况,无法满足大对象的分配,当顶不住要触发Full GC时开启内存碎片合并整理过程,这个过程是不能并发的,会Stop The World。

6.8.G1

Garbage First,将内存分为多个Region,使用Remembered Set 避免全盘扫描,标记-整理算法。

  • 初始标记(initial Marking)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)
  • 筛选回收(Live Data Counting Evacuation)

优点:

  • 并行与并发,能充分利用CUP资源;
  • 空间整理,与CMS的标记-清理相比,它采用的是标记-整理,从局部Region来看又是复制算法;
  • 可预测停顿,可以让使用这指定在长度为M毫秒的是时间内,垃圾收集时间不能超过N毫秒;

二、内存分配策略

  1. 对象优先新生代Eden区分配
    大多数情况下,对象在新生代Eden区分配,若Eden区无法分配,则虚拟机会触发一次Minor GC。

Minor GC-新生代; Major GC/Full GC-老年代

  1. 大对象直接直接进入老年代,大对象的界定可通过参数设定;
  2. 长期存活的对象进入老年代,对象的年龄,Minor GC一次且能被Survivor分区容纳则加1,默认是15岁进入老年代;
  3. 动态年龄判断,虚拟机并非永远要求对象的年龄到达了MaxTenuringThreshold才能晋升老年代,如果相同年龄的对象总时占据Survivor分区的一半及以上,年龄大于或者等于改年龄的对象就可以直接进入老年代,无须等到年龄到达MaxTenuringThreshold;
  4. 空间分配担保,处理Minor GC的风险问题,老年代为新生代担保,根据具体情况看是是否执行Full GC,比如老年代的剩余连续空间比新生代大,那就没有必要Full GC,这种情况下Minor GC是没有风险的。

参考:https://blogs.oracle.com/jonthecollector/our-collectors

上一篇:JVM(1)-运行时数据区
下一篇:JVM(3)-类加载机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容