垃圾收集器知识点汇总2:Java中的垃圾收集

Java中的垃圾收集

碎片整理

上篇文章提到,标记清除法会导致内存碎片,内存碎片会使写入操作越来越耗时,因为寻找一块足够大的空闲空间会变得非常麻烦,并且在创建对象时JVM需要在连续的块中分配内存,内存碎片很容易导致没有足够连续的块创建对象导致内存分配错误(allocation error)。

要避免这种问题,就需要对内存进行碎片整理。方法就是对可达对象依次排序。
图例:
在这里插入图片描述

分代假设

为了避免垃圾回收的对象过多而导致STW(STOP THE WORLD PAUSE,全线暂停)时间过长,我们需要想办法实现每次进行gc时只针对一小部分内存区域进行回收,这就引入了分代假设的概念。

首先需要明确内存中的可回收对象大致分为两类:一类是很快就不再使用的,还有一类是不会立即无用(近期有使用),但也不会持续很长时间。
因此,我们可以将内存划出两个大区域,分别是新生代和老年代,年轻代:老年代=1:2。
先上图:
在这里插入图片描述

年轻代

我们先说年轻代。年轻代可继续划分为eden区(又称为新生代)、S1区(S是survivor的缩写)、S2区。
在这里插入图片描述

Eden 是内存中的一个区域, 用来分配新创建的对象。通常会有多个线程同时创建多个对象, 所以 Eden 区被划分为 多个 (Thread Local Allocation Buffer, 简称 TLAB)。通过这种缓冲区划分,大部分对象直接由 JVM 在对应线程的 TLAB 中分配, 避免与其他线程的同步操作。 如果 TLAB 中没有足够的内存空间, 就会在共享 Eden 区(shared Eden space)之中分配。如果共享 Eden 区也没有足 够的空间, 就会触发一次 年轻代 GC 来释放内存空间。如果 GC之后 Eden 区依然没有足够的空闲内存区域, 则对象 就会被分配到老年代空间(Old Generation)。 当 Eden 区进行垃圾收集时, GC将所有从 root 可达的对象过一遍, 并标记为存活对象。

Eden 区的旁边是两个 , 称为 from 空间 和 to 空间 。需要着重强调的的是, 任意时刻总有一个存活区是空 的(empty)。 空的那个存活区用于在下一次年轻代 GC 时存放收集的对象。年轻代中所有的存活对象(包括 Edenq 区和非空的那个 “from” 存活区)都会被复制到 ”to“ 存活区。GC 过程完成后, ”to“ 区有对象,而 ‘from’ 区里没有对象。两者的角色进行正 好切换 。存活的对象会在两个存活区之间复制多次, 直到某些对象的存活 时间达到一定的阀值。分代理论假设, 存活超过一定 时间的对象很可能会继续存活更长时间。 这类“ 年老” 的对象因此被 提升(promoted )到老年代。提升的时候, 存活区的对象不再是复制到另一个存活区,而是 迁移到老年代, 并在老年代一直驻留, 直到变为不可达对象。 为了确定一个对象是否“足够老”, 可以被提升(Promotion)到老年代,GC模块跟踪记录每个存活区对象存活的次数。 每次分代 GC 完成后,存活对象的年龄就会增长。当年龄超过提升阈值 (tenuring threshold), 就会被提升到老年代区域。

老年代

老年代 GC发生的频率比年轻代小很多。同时, 因为预期老年代中的对象大部分是存活的, 所以不再使用标记和复制 (Mark and Copy)算法。而是采用移动对象的方式来实现最小化内存碎片。老年代空间的清理算法通常是建立在不同 的基础上的。原则上,会执行以下这些步骤: 通过标志位(marked bit),标记所有通过 GC roots 可达的对象. 删除所有不可达对象 整理老年代空间中的内容,方法是将所有的存活对象复制,从老年代空间开始的地方,依次存放。

在 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

既然估算元数据所需空间那么复杂, 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、Major GC和Full GC

垃圾收集事件(Garbage Collection events)通常分为: 小型 GC(Minor GC) ­ 大型 GC(Major GC) ­ 和完全 GC(Full GC) 。

年轻代内存的垃圾收集事件称为 小型GC。

  1. 当 JVM 无法为新对象分配内存空间时总会触发 Minor GC,比如 Eden 区占满时。所以(新对象)分配频率越高, Minor GC 的频率就越高。
  2. Minor GC 事件实际上忽略了老年代。从老年代指向年轻代的引用都被认为是 GC Root。而从年轻代指向老年 代的引用在标记阶段全部被忽略。
  3. 与一般的认识相反, Minor GC 每次都会引起全线停顿(stop­the­world ), 暂停所有的应用线程。对大多数程序而 言,暂停时长基本上是可以忽略不计的, 因为 Eden 区的对象基本上都是垃圾, 也不怎么复制到存活区/老年代。 如果情况不是这样, 大部分新创建的对象不能被垃圾回收清理掉, 则 Minor GC的停顿就会持续更长的时间。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值