Java垃圾回收(一)
本文最后更新于 826 天前,其中的信息可能已经有所发展或是发生改变。

GC

一、定义

1. 什么是垃圾

没有引用的对象(注意Java引用分为强软弱虚)

2. 怎么找到垃圾

  • 引用计数法(Reference Counting): 虽然循环引用的问题可通过 Recycler 算法解决,但是在多线程环境下,引用计数变更也要进行昂贵的同步操作,性能较低,早期的编程语言会采用此算法。
  • 可达性分析,又称引用链法(Tracing GC): 从 GC Root 开始进行对象搜索,可以被搜索到的对象即为可达对象,此时还不足以判断对象是否存活/死亡,需要经过多次标记才能更加准确地确定,整个连通图之外的对象便可以作为垃圾被回收掉。目前 Java 中主流的虚拟机均采用此算法
    • GC Root(线程栈变量、静态变量、常量池、JNI指针)

引用计数法是可以处理循环引用问题的——美团技术

二、收集算法

自从有自动内存管理出现之时就有的一些收集算法,不同的收集器也是在不同场景下进行组合。

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

回收过程主要分为两个阶段,第一阶段为追踪(Tracing)阶段,即从 GC Root 开始遍历对象图,并标记(Mark)所遇到的每个对象,第二阶段为清除(Sweep)阶段,即回收器检查堆中每一个对象,并将所有未被标记的对象进行回收,整个过程不会发生对象移动(STW)。整个算法在不同的实现中会使用三色抽象(Tricolour Abstraction)、位图标记(BitMap)等技术来提高算法的效率,存活对象较多时较高效。位置不连续 产生碎片 效率偏低(两遍扫描)

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

这个算法的主要目的就是解决在非移动式回收器中都会存在的碎片化问题,也分为两个阶段,第一阶段与 Mark-Sweep 类似,第二阶段则会对存活对象按照整理顺序(Compaction Order)进行整理。主要实现有双指针(Two-Finger)回收算法、滑动回收(Lisp2)算法和引线整理(Threaded Compaction)算法等。没有碎片,效率偏低(两遍扫描,指针需要调整)

3. Copying(复制)

将空间分为两个大小相同的 From 和 To 两个半区,同一时间只会使用其中一个,每次进行回收时将一个半区的存活对象通过复制的方式转移到另一个半区。有递归(Robert R. Fenichel 和 Jerome C. Yochelson提出)和迭代(Cheney 提出)算法,以及解决了前两者递归栈、缓存行等问题的近似优先搜索算法。复制算法可以通过碰撞指针的方式进行快速地分配内存,但是也存在着空间利用率不高的缺点,另外就是存活对象比较大时复制的成本比较高。没有碎片,浪费空间

虽然 compaction 与 copying 都涉及移动对象,但取决于具体算法,compaction 可能要先计算一次对象的目标地址,然后修正指针,最后再移动对象。copying 则可以把这几件事情合为一体来做,所以可以快一些。另外,还需要留意 GC 带来的开销不能只看 Collector 的耗时,还得看 Allocator 。如果能保证内存没碎片,分配就可以用 pointer bumping 方式,只需要挪一个指针就完成了分配,非常快。而如果内存有碎片就得用 freelist 之类的方式管理,分配速度通常会慢一些。

三、收集器

目前在 Hotspot VM 中主要有分代收集和分区收集两大类,具体可以看下面的这个图,不过未来会逐渐向分区收集发展。除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型,G1是逻辑分代,物理不分代,除此之外不仅逻辑分代,而且物理分代

进入老年代的年龄,pspo为15,cms为6

1. 分代收集器

  • Serial 单线程,发生STW,业务现场不能工作

  • Parallel Scavenge 多线程(线程太多会产生线程切换消耗),发生STW

  • ParNew: 一款多线程的收集器,采用复制算法,主要工作在 Young 区,可以通过 -XX:ParallelGCThreads 参数来控制收集的线程数,整个过程都是 STW 的,常与 CMS 组合使用。

  • CMS: concurrent mark sweep 并发标记清楚,以获取最短回收停顿时间为目标,采用“标记-清除”算法,分 4 大步进行垃圾收集,其中初始标记和重新标记会 STW ,多数应用于互联网站或者 B/S 系统的服务器端上,JDK9 被标记弃用,JDK14 被删除,详情可见 JEP 363
    带来的问题:内存碎片到达一定程度,会对老年代进行serial回收

2. 分区收集器

  • G1: 一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能地满足垃圾收集暂停时间的要求。
  • ZGC: JDK11 中推出的一款低延迟垃圾回收器,适用于大内存低延迟服务的内存管理和回收,SPECjbb 2015 基准测试,在 128G 的大堆下,最大停顿时间才 1.68 ms,停顿时间远胜于 G1 和 CMS。
  • Shenandoah: 由 Red Hat 的一个团队负责开发,与 G1 类似,基于 Region 设计的垃圾收集器,但不需要 Remember Set 或者 Card Table 来记录跨 Region 引用,停顿时间和堆的大小没有任何关系。停顿时间与 ZGC 接近,下图为与 CMS 和 G1 等收集器的 benchmark。

四、JVM内存模型

1. 新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace

  1. 永久代 元数据 - Class
  2. 永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)
  3. 字符串常量 1.7 - 永久代,1.8 - 堆
  4. MethodArea逻辑概念 - 永久代、元数据

2. 新生代 = Eden + 2个suvivor区

  1. YGC回收之后,大多数的对象会被回收,活着的进入s0
  2. 再次YGC,活着的对象eden + s0 -> s1
  3. 再次YGC,eden + s1 -> s0
  4. 年龄足够 -> 老年代 (15 CMS 6)
  5. s区装不下 -> 老年代
  6. 为什么是8:1:1?
    基于统计学,一般年轻代能回收90%的对象。

3. 老年代

  1. 顽固分子
  2. 老年代满了FGC Full GC

4. GC Tuning (调优)

  1. 尽量减少FGC
  2. MinorGC = YGC
  3. MajorGC = FGC

5. 堆内存逻辑分区(适用于分代垃圾收集器)

image-20210218151004507

  1. 新生代:老年代=1:2

  2. 新生代大量死去,少量存活,采用复制算法

  3. 老年代存活率高,回收较少,采用MC或MS

五、其他

对象分配到栈

对象的作用于仅在方法内时,如果放到堆里,方法执行完后,还需要GC来回收。
直接分配到栈上,方法执行完,方法出栈,对象销毁,减少GC压力。

作者:Yuyy
博客:https://yuyy.info
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇