垃圾回收之分代假设

标记-清除(mark and Sweep)是最经典的垃圾回收算法

碎片整理

内存模型就像一个衣柜有很多隔断,假如每个隔断内部都是满的,如果执行清理,那么最后可能内存会很乱,一三五层隔断有东西,二四六没东西,或者更乱。

大家可以想象一下,假如要写入一个很大的文件,需要一个连续的内存地址来存储。如果内存碎片很多,可能在寻找地址的时候就会浪费很多时间。

所以在JVM每次执行清理的时候会执行内存清理整理,以减少内存碎片。

分代假设

对象越多则收集垃圾的消耗时间越长,所以研究人员发现,程序中的大多可回收的垃圾归为两类:

  • 大部分很快就不使用或立即无用
  • 存活时间很长的

如果统一去处理这些垃圾,可能就会消耗更长的时间处理存活较长的垃圾,所以这些观测形成了弱代假设。基于这一假设,VM的内存分为,年轻代(Young)和老年代(Old/Tenured)

拆分这两个可清理的单独区域,允许采取不同的算法从而来大幅提高GC的性能。

但是这种方法不是没有问题,例如,不同分代中的对象可能会互相引用,这样在收集某个分代可能就是GC root。
没有完美的垃圾回收算法,只有好的算法,之所以这样说是因为,GC算法专门针对“要么死得快,要么活的长”这类特征的对像来优化,JVM对于那些不长不短的对象就很<font color=red>awkword(尴尬)</font>

内存池(Memory Pools)

Java内存模型是很抽象的,很多定义也不同,网上资料各异没有明确官方定义,但是小编感觉,只要理解是对的,有助你Java开发,那么怎么理解怎么记,就算是错的,也会增加你对Java垃圾回收的理解,这里用一个图来把这个抽象的东西,表述一下。


新生代(Eden,伊甸园)

Eden是内存中的一个区域,用来分配新创建的对象。通常会与多个线程同时创建多个对象,所以Eden区被划分为多一个线程本地分配缓冲区,通过分离,避免与其他线程同步操作。


如果线程缓冲区没有足够内存了,就会在Eden共享区分配,如果共享区也没有了内存,就会触发年轻代的GC来释放内存。如果GC之后Eden区依然没有足够的内存,则对象就分配到年老代,Old

当Eden去进行垃圾收集的时候,GC将从root可达的对象过一遍,并标记存活对象,标记完成后,Eden中所有存活的对象,被复制到Eden的右边的存活区(Survivor spaces),这个时候Eden区可能就是空的,然后分配新对象,这种方法就是标记-复制,是复制而不是移动。

存活区(Survivor Spaces)

Eden 区的旁边是两个存活区, 称为 from 空间和 to 空间。需要着重强调的的是, 任意时刻总有一个存活区是空的(empty)。

空的这个存活区在下一次年轻代GC是存放对象,年轻代中所有的存活对象(包括Edenq区和非空的那个 “from” 存活区)都会被复制到 ”to“ 存活区。GC过程完成后, ”to“ 区有对象,而 ‘from’ 区里没有对象。两者的角色进行正好切换 。

存活的对象会在俩个区多次复制,当存活区对象在经历多次GC后依然,存活那么这个时候就可以升级为老年代Old。为了确定一个对象是否“足够老”, 可以被提升(Promotion)到老年代,GC模块跟踪记录每个存活区对象存活的次数。每次分代GC完成后,存活对象的年龄就会增长。当年龄超过提升阈值(tenuring threshold), 就会被提升到老年代区域。

具体的提升阈值由JVM动态调整,但也可以用参数-XX:+MaxTenuringThreshold 来指定上限。如果设置 -XX:+MaxTenuringThreshold=0, 则GC时存活对象不在存活区之间复制,直接提升到老年代。

现代 JVM 中这个阈值默认设置为15个 GC周期。这也是HotSpot中的最大值。

如果存活区空间不够存放年轻代中的存活对象,提升(Promotion)也可能更早地进行。

老年代(Old Generation)

老年代的对象是经历了筛选进来的,老年代内存空间通常会更大,一般是垃圾的概率会相对小。

老年代的GC发生频率比年轻代小很多,因为大部分都是存活的,所以使用的算法也不是标记和复制,对于因为是垃圾的改变会相对小,所以如果说是清理垃圾,更不说是,优化内存。但是都会经历下面几步:

  • 通过标志位(marked bit),标记所有通过 GC roots 可达的对象.
  • 删除所有不可达对象
  • 整理老年代空间中的内容,方法是将所有的存活对象复制,从老年代空间开始的地方,依次存放。

永久代(PermGen)

在Java 8 之前有一个特殊的空间,称为“永久代”(Permanent Generation)。这是存储元数据(metadata)的地方,比如 class 信息等。此外,这个区域中也保存有其他的数据和信息, 包括 内部化的字符串(internalized strings)等等。实际上这给Java开发者造成了很多麻烦,因为很难去计算这块区域到底需要占用多少内存空间。预测失败导致的结果就是产生 java.lang.OutOfMemoryError: Permgen space 这种形式的错误。除非 ·OutOfMemoryError· 确实是内存泄漏导致的,否则就只能增加 permgen 的大小,例如下面的示例,就是设置 permgen 最大空间为 256 MB:

java -XX:MaxPermSize=256m com.mycompany.MyApplication

元数据区(Metaspace)

既然估算元数据所需空间那么复杂, Java 8直接删除了永久代(Permanent Generation),改用 Metaspace。从此以后, Java 中很多杂七杂八的东西都放置到普通的堆内存里。

当然,像类定义(class definitions)之类的信息会被加载到 Metaspace 中。元数据区位于本地内存(native memory),不再影响到普通的Java对象。默认情况下, Metaspace的大小只受限于 Java进程可用的本地内存。这样程序就不再因为多加载了几个类/JAR包就导致 java.lang.OutOfMemoryError: Permgen space. 。注意, 这种不受限制的空间也不是没有代价的 —— 如果 Metaspace 失控, 则可能会导致很严重的内存交换(swapping), 或者导致本地内存分配失败。

如果需要避免这种最坏情况,那么可以通过下面这样的方式来限制 Metaspace 的大小, 如 256 MB:

java -XX:MaxMetaspaceSize=256m com.mycompany.MyApplication

Minor GC vs Major GC vs Full GC

以下内容原文写的很清楚,是概念上内容,采用原文描述
垃圾收集事件(Garbage Collection events)通常分为: 小型GC(Minor GC) - 大型GC(Major GC) - 和完全GC(Full GC) 。本节介绍这些事件及其区别。然后你会发现这些区别也不是特别清晰。

最重要的是,应用程序是否满足 服务级别协议(Service Level Agreement, SLA), 并通过监控程序查看响应延迟和吞吐量。也只有那时候才能看到GC事件相关的结果。重要的是这些事件是否停止整个程序,以及持续多长时间。

虽然 Minor, Major 和 Full GC 这些术语被广泛应用, 但并没有标准的定义, 我们还是来深入了解一下具体的细节吧。

小型GC(Minor GC)

年轻代内存的垃圾收集事件称为小型GC。这个定义既清晰又得到广泛共识。对于小型GC事件,有一些有趣的事情你应该了解一下:
- 当JVM无法为新对象分配内存空间时总会触发 Minor GC,比如 Eden 区占满时。所以(新对象)分配频率越高, Minor GC 的频率就越高。
- Minor GC 事件实际上忽略了老年代。从老年代指向年轻代的引用都被认为是GC Root。而从年轻代指向老年代的引用在标记阶段全部被忽略。
- Minor GC 每次都会引起全线停顿(stop-the-world ), 暂停所有的应用线程。对大多数程序而言,暂停时长基本上是可以忽略不计的, 因为 Eden 区的对象基本上都是垃圾, 也不怎么复制到存活区/老年代。如果情况不是这样, 大部分新创建的对象不能被垃圾回收清理掉, 则 Minor GC的停顿就会持续更长的时间。

所以 Minor GC 的定义很简单 —— Minor GC 清理的就是年轻代。

Major GC vs Full GC

Minor GC 清理的是年轻代空间(Young space),相应的,其他定义也很简单:

  • Major GC(大型GC) 清理的是老年代空间(Old space)。
  • Full GC(完全GC)清理的是整个堆, 包括年轻代和老年代空间。

悲剧的情况出现了,很多Major GC是由于Minor GC触发的,所以很多情况下这和Full GC就没有多大区别了,这个就很<font color=red>awkword(尴尬)</font>

这也让我们认识到,不应该去操心是叫 Major GC 呢还是叫 Full GC, 我们应该关注的是: 某次GC事件 是否停止所有线程,或者是与其他线程并发执行。

文章参考自附录地址,附带小编自己的学习经历和理解重新翻译,希望大家共进步

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

推荐阅读更多精彩内容