垃圾回收机制

Java GC算法 垃圾收集器

资源泄漏异常

1.1内存溢出

程序申请内存的时候,没有足够大的连续内存空间,导致异常

1.2内存泄漏

程序申请到内存后,使用完成没有释放掉内存空间,导致GC的可达性可以被监测到,所以不会被GC回收
内存泄漏最终会导致内存溢出

  1. 内存泄漏原因通过跟踪GC Roots的引用链,找到泄漏对象如果与GC Roots关联却不被释放。
  2. 是否对象生命存在周期过长

2. 线程栈和本地方法栈溢出

线程数量 = 分配给线程栈的内存是扣除堆内存剩下的内存 / 每个栈容量
如果每个线程过大,扩展线程时会导致溢出,这时候解决方案是"减少堆内存"

  1. 栈深度大于虚拟机所允许的最大深度
  2. 扩展栈时无法申请到足够内存

3. 运行常量池溢出

String.intern()导致,此方法是Native方法。若池中存在此String对象,则返回池中的String对象;否则,将此对象字符串添加到常量池中。


回收条件

判断一个对象是否死亡,有两次标记

  1. 是否与GC Roots有引用链,是否有finalize()方法被覆盖,或者被执行,会执行此方法(此方法可以让对象逃脱被回收),将对象放入F-Queue队列中
  2. 如果F-Queue队列中还存在此对象,则回收

finalize()方法只会被系统调用一次
请忘记这个方法,这是C++的析构函数,已经没人用了

Thread Stack
PC Register
Native Thread Stack

都是使用StackFrame棧帧随着出入栈实现自动内存清理

Heap
Method Area

分配是动态的,内存的回收需要使用GC来实现


Method Area 讨论

主要回收的是废弃常量无用类

  1. 废弃常量是当前系统没有任何一处引用该常量
  2. 无用类需要满足3个条件:
    • 该类所有实例都已经被回收,也就是说Java堆中不存在该类的任何实例
    • 加载该类的ClassLoader已经被回收
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
  3. 为了避免内存溢出,在大量使用反射、动态代理的场景都需要虚拟机具备类卸载功能

Heap GC讨论

Garbage Collect(GC)在动态的回收内存是通过对象是否存活来判断:

  1. 引用计数:每个对象有一个引用计数器,新增引用+1,释放-1,计数器为0可以回收(无法解决循环引用问题)
  2. 可达性分析/根搜索算法:从GC Roots往下查询,所走过的路径为引用链。如果一个对象到GC Roots没有任何引用链接,则为不可达,可回收(进行GC Roots枚举时,必须stop the world来保证引用关系不再改变。所以这也要求GC Root的枚举不能太耗时
HotSpot
快速查找到GC Roots
使用一组叫做OopMap的数据结构。
(1) 记录每个类的每个数据位置(通过偏移量来确定)的类型(即是否是引用)。这一步在类加载后即可确定。
(2) 在特定的位置上记录栈和寄存器中哪些位置是引用。
这样我们在GC Root枚举时,可以快速找到所有引用。

设置安全点(只有在安全点上才有全部的OopMap信息,安全点由程序结构决定)
抢占式:在GC发生时,中断所有线程,如果发现有的线程不在安全点,
    则重新唤醒知道跑到最近的安全点。--这种方式几乎没人使用
主动式:线程主动发起。在GC发生时,在安全点位置上设置标记位,
    线程执行到安全点时轮询这个标志位,如果发现为true,则中断。

安全区(解决就绪线程态无限等待CPU导致无法GC进入安全点)
一段代码内无引用关系改变。
线程进入安全区代码后,标记自己“安全区”标记,GC发生时就不用管标记“安全区”的线程了。
当线程想要离开安全区时,判断GC是否结束,如果没结束要等GC结束后才能继续执行

GC Roots的对象

  • Thread Stack(StackFrame局部变量表)中 引用的对象
  • Method Area 类静态属性引用的对象
  • Method Area 常量引用的对象
  • Native Method Stack JNI中引用的对象

垃圾收集算法/方法论

并行(Parallel):用户工作线程停止,多条GC线程并行工作,可以同时处理多件事情(A B同时开始处理从0%->100% )
并发(Concurrent):用户工作线程与GC线程并发工作,GC集中在另一个CPU上,可以处理多个事情,不需要同时(先处理A 50%,再处理B 50%,再处理A 50%,再处理B 50%)

1. 标记-清除(Mark-Sweep)算法

标记出需要回收的对象,在标记完成后统一回收掉所有的被标记对象。
issues:

  1. 标记和清除的效率不高
  2. 标记清除后的会产生大量的不连续内存,碎片太多导致需要连续的大对象无法找到连续内存,再次提前促发垃圾回收

2. 复制(Copy)算法

将内存空间分成大小相等的两块,每次只使用一块。当一块用完类,将存活的对象copy到另一块上面,然后把使用的内存一次清空,在对象存活率低时回收的效率高
issues:

  1. 内存只有一半
  2. 长生存期的对象被持续复制效率低

3. 标记-整理(Mark-Compact)算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,在对象存活率高时回收效率高

4. 分代收集算法

基本假设是:绝大多数的对象生命周期都非常短暂,频繁回收,但是有部分对象生命周期比较长
将Java堆分为
Young Generation:每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集
Old Generation:对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”“标记-整理”算法来进行回收

垃圾收集器/方法论实现

1. Serial收集器

串行收集器,只使用一个线程去回收,当回收的时候会Stop The World。
缺点:工作时会停止用户线程
算法:新生代,复制算法
关注点:响应时间优先

2. ParNew收集器

多线程串行收集器,当新生代占用达到一定比例时开始收集
缺点:工作时会停止用户线程
算法:新生代,复制算法
关注点:响应时间优先

3. Parallel Scavenge

关注点:吞吐量优先,各代比例自适应调整
算法:复制算法

4. Parallel Old收集器

5. CMS(Concurrent Mark Sweep)收集器

采用标记-清除算法,使用并发的方式
分为四个阶段:初始标记(stop-the-world)->并发标记->重新标记(stop-the-world)->并发清除
存在浮动垃圾,需要预留内存空间(默认68%),如果内存不足,会使用Serial Old收集器来执行。
优点:并发收集、停顿低,可以与用户线程并发工作最短用户线程停顿的时间
缺点:会产生大量的空间碎片,导致提前促发full gc(预留给标记中用户修改的空间),并发阶段会降低吞吐量(解决方案是在N次full gc后提供碎片整理
关注点:响应时间优先

分为四个步骤:三标记一清除

  1. 初始标记mark GC Root直接关联的对象
  2. 并发标记:进行GC Root的trace过程,进行可达性分析
  3. 重新标记:修正(2)时,由于用户线程也在运行导致的引用关系变化
  4. 并发清除:就是清除的过程,并发清理阶段用户线程还在运行,这段时间就可能产生新的垃圾(浮动垃圾),新的垃圾在此次GC无法清除,只能等到下次清理

(1)和(3)需要stop the world,但(1)和(3)的消耗时间非常短。
(2)和(4)为并发,即可以和用户线程同时运行。消耗时间相较于1和3较长
但由于是并发,所以整个收集器看来就是和用户线程并行运行。

CMS流程.png

6. G1收集器

Young/Old Generation 不再是物理隔离,将Heap 划分成N个相等大小独立的Region,Young/Old Generation是一部分Region的合集,后台维护一个优先列表优先回收垃圾最多的区域
region和跨代之间的堆引用全局扫描问题,采用Remembered Set记录每次赋值引用时是否引用其它region,是则记录。(保证不会全堆扫描)
关注点吞吐量优先
优点:

  1. 整体来看采用标记-整理算法,局部(region)来看采用复制算法,不会产生大量碎片而导致无法分配大对象
  2. 可预测停顿,在指定N毫秒内的时间片段内,GC时间不超过N毫秒
  3. GC线程与用户线程可以并发执行

分为四个步骤:三标记一清除

  1. 初始标记
  2. 并发标记
  3. 最终标记
  4. 筛选回收:在运行的GC停顿时间内,选择回收价值最高的Region回收。这个阶段虽然可以和用户线程并行,但实际上由于所花费时间短,且停顿用户线程这个时间会更短,所以采用停顿用户线程的方式

常用的收集器组合

YoungGeneration: ParNew
OldGenration: CMS


YoungGeneration: G1
OldGenration: G1

Minor GC和Full GC

  1. Minor GC
    新生代的垃圾收集动作,由于新生代的Java对象存活率都很低,Minor GC会非常频繁,并且回收速度快
  2. Full GC
    老年代的垃圾回收动作,出现Full GC的时候通常会执行一次Minor GC。并且时间较长,会产生STW(stop the world)

GC促发条件

整体分配策略.png
  • 对象优先分配在Eden区 如果Eden区没有足够的空间时,虚拟机执行一次Minor GC
  • 大对象直接进入老年代 (大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
  • 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
  • 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
  • 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置(这个意思为是否担保失败),如果true则只进行Monitor GC,如果false则进行Full GC
    空间担保.png
  • 手动调用System.gc()方法,通常这样会触发一次的Full GC以及至少一次的Minor GC
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容

  • 来自: Android梦想特工队作者: Aaron主页: http://www.wxtlife.com/原...
    技术特工队阅读 4,361评论 0 28
  • JVM内存区域 JVM将其管理的内存分为若干数据区域,这些数据区域分布情况如下图所示: 程序计数器:一块较小内存区...
    luoxn28阅读 693评论 0 0
  • 如果你想要从太空观察地球,卫星技术就能够做到这一点。图中的海洋流是卫星地图展现的,紫色和粉红色的漩涡代表更暖的洋流...
    allever阅读 1,393评论 0 8
  • 了解本章之前需要对jmm有基本的了解,然后对于垃圾回收感兴趣的朋友可以细看一下这篇文章 一、按代实现垃圾回收 先看...
    倪宝华阅读 744评论 1 3
  • 睡过几人的床榻,又爱过几人的正当年华,以为温酒不烈,便接二连三 ,你不断搓揉着凌乱的头发,忧伤地问我爱是什么,我一...
    流经他年阅读 228评论 2 2