JVM核心知识-GC知识

Java开发相对于C语言最方便的点,就是代码上不需要主动去管理内存的回收,而由JVM负责分配回收。

GC算法

标记清除算法(Mark-Sweep)

标记出所有存活的内存对象,当垃圾回收时只清除未标记的对象。
缺点:回收的空间不连续,可能导致创建对象时,虽然未使用空间足够,但连续的空间不足无法创建。

复制算法(Copying)

将空间分为A、B两块。对象在A块创建。当A空间满了,垃圾回收,将A中存活的对象复制到B中,清空A空间。之后在B空间中尽享相同的逻辑。
优点:合理规划了空间的连续性,每次回收都是整块内存的回收。
缺点:将内存分为了两块,每次只能用一半的内存。

标记整理算法(Mark-Compact)

结合标记清除和标记复制的缺点,为了优化算法。提出了标记整理算法。对象的回收依然使用标记方式,在垃圾回收时,把存活的对象整理到内存一端,将其他内存回收。这样做的的好处时腾出了一整块连续的空间。
优点:创造了连续的空间环境
缺点:整理耗时,效率有所损失

分代收集算法(Generational Collection)

根据对象的存活特性,将内存分为几个区域,分别对不同区域进行不同的回收算法。目前大部分的JVM都采用分代收集算法。常见的将堆分为年轻代、老年代,以及堆外的永久代。老年代存放的对象一般回收较少,年轻代回收的对象相对更多。一般对象多数是”朝生夕死“,即创建完很快就会被回收。针对这些区域的对象特性采用不同的收集算法,提高内存利用率。
例如:以hotspot为例,堆(heap)区域内存划分为年轻代、老年代、永久代,年轻代又分为Eden、From Survivor、To Survivor。


image.png

年轻代采用标记复制算法,对象的创建在Eden区进行,当Eden区进行垃圾回收时,将Eden存活对象复制到From Survivor区,清空Eden。当From Survivor也满了,将Eden区和From Survivor存活的对象复制到To Survivor区,清空Eden和From Survivor。(之后From Survivor与To Survivor身份交换)。老年代采用标记整理算法,将存活的对象整理到一端,腾出连续的空间,所以FullGC更耗时,需要避免GC频繁。

GC收集器

Serial/Serial Old收集器

Serial收集器是单线程收集器,是分代收集器。年轻代使用单线程复制收集算法(Serial收集器),老年代使用单线程标记整理算法(Serial Old收集器)。
进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop TheWorld)。
参数:-XX:+UseSerialGC
特点:单线程没有多线程切换的开销。

ParNew 收集器

相当于Serial收集器的多线程版本。年轻代使用并行复制收集算法(ParNew 收集器),老年代使用单线程标记整理算法(Serial Old收集器)。
参数:
-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;
-XX:+UseParNewGC":强制指定使用ParNew;
-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
特点:ParNew收集器在单CPU环境中不比Serial效果好,甚至可能更差,两个CPU也不一定跑的过,但随着CPU数量的增加,性能会逐步增加。

Parallel Scavenge/Parallel Old收集器

JDK8默认收集器。
同样是并发收集器,Parallel Scavenge收集器的关注点与其他收集器不同, Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))
Parallel Old是Parallel老年代收集器,使用标记整理算法。
jdk1.6之前老年代只能使用Serial Old收集器。
参数:

  • -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间
  • -XX:GCTimeRatio:设置吞吐量大小
  • -XX:+UseAdaptiveSizePolicy:GC自适应的调节策略(GC Ergonomics),当这个参数打开之后,无需手动指定年轻代空间大小,也不需要设置Survivor对象年龄,虚拟机会根据运行情况,动态调整这些参数值。
    特点:能够控制GC的吞吐量,自适应调整。

CMS 收集器(ConcurrentMarkSweep)

CMS是老年代垃圾收集器,在收集过程中可以与用户线程并发操作。它可以与Serial收集器和Parallel New收集器搭配使用。CMS牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上。
参数:-XX:+UseConcMarkSweepGC来开启CMS。
特点:并发收集、降低停顿时间

CMS收集器GC的过程

  • 初始标记:标记GCRoot关联对象。会触发Stop The World
  • 并发标记:进行GC Roots Tracing的过程。
  • 重新标记:处理因为初始标记过程中用户线程创建对象而没有处理的对象标记。
  • 并发清除:标记清除算法,清除垃圾。吃阶段可能产生浮动垃圾。

G1收集器

G1收集器的设计将整个堆内存分为多个大小相等的块(Region)。任然沿用年轻代、老年代的概念,但相对之前的空间连续,换成了多个Region的集合。
G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。
G1收集器GC的过程

  • 初始标记(Initial Marking) 仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Nest Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建对象,此阶段需要停顿线程,但耗时很短。
  • 并发标记(Concurrent Marking) 从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。
  • 最终标记(Final Marking) 为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。
  • 筛选回收(Live Data Counting and Evacuation) 首先对各个Region中的回收价值和成本进行排序,根据用户所期望的GC 停顿时间来制定回收计划。
  • 官方建议使用G1,应用内存最少分配6G

ZGC

JDK11提供的新GC收集器,暂时了解不多,号称gc耗时能在10ms内。

GC时机

  • 年轻代的GC被称为Young GC或者Minor GC
  • 对老年代的GC称为Major GC
  • 整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代)。

分代回收:

  • 新对象在Eden Space创建内存分配。当Eden Space满了,这时新对象的创建由于空间不足,会触发Young GC。将Eden+From Survivor进行垃圾回收,将存活的对象复制到To Space空间,记录对象的GC次数,清理Eden和From Survivor,这时From Survivor和To Survivor交换身份,这样保证一直有一个Survivor空间为空。
  • 当Young GC时,发现To Survivor空间不足以对象的复制,则将To Survivor和Eden存活的对象复制到老年代。
  • 当对象经过多次Young GC后任然存活,将对象复制到老年代。
  • 当老年代也满了,则进行Full GC。
  • System.gc()
  • heap dump,默认触发 Full GC.

什么对象属于垃圾

引用计数法

即对象被其他对象引用则计数器计数加一,以计数器为0的对象视为可回收对象。目前主流的JVM已经不使用计数法。原因:需要维护对象引用数数数据,影响性能。另外存在相互引用情况无法回收对象。

GCRoot可达性分析

将不再被GCRoots引用的对象进行回收,即不在GCRoots引用路径上的对象。
而JVM中可以被当做GCRoot的对象包括

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

推荐阅读更多精彩内容

  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,664评论 0 7
  • Java和C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进来,墙里面的人想出来。 对象...
    胡二囧阅读 1,089评论 0 4
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,601评论 3 83
  • 内存溢出和内存泄漏的区别 内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,...
    Aimerwhy阅读 741评论 0 1
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight阅读 1,423评论 1 0