Java 面试复习_7
2019-5-30
作者:水不要鱼
(注:能力有限,如有说错,请指正!)
如果说垃圾收集算法是接口,那垃圾收集器就是接口的实现了。由于现在 JVM 上一般都会使用分代垃圾回收机制,也就是将堆进一步分为
新生代和老年代,根据不同区域的特点进一步选择具体的垃圾回收算法和垃圾回收器,一般来说新生代使用的是复制算法
,老年代使用的是
标记清除算法
或标记整理算法
,同理,针对不同的区域也会有不同的垃圾收集器。目前在 JVM 中有以下几种常用的垃圾收集器:
- Serial(用于新生代)/ Serial Old(用于老年代)
- Parallel Scavenge(用于新生代)/ Parallel Old(用于老年代)
- ParNew(用于新生代)
- CMS(用于老年代)
- G1(新生代和老年代都能用)
不同的业务对于垃圾收集的要求不一样,你可以组合不同的垃圾收集器,比如新生代选择一个,老年代选择另外一个,都可以,但是有些垃圾收集器是不可以一起组合使用的。
不管怎么组合,我们要知道没有完美的垃圾收集器,只有适合的垃圾收集器,所以我们只聊它们的特点,不对比谁好谁坏。
- Serial(用于新生代)/ Serial Old(用于老年代)
从名字就能看出来,这是两个单线程的垃圾收集器。其实应该叫一个,因为两个是一样的,只是针对的区域不一样。
这两个垃圾收集器的“单线程”不仅仅是说收集垃圾的时候是单线程,还指整个垃圾收集的过程都是单线程,也就意味在执行垃圾收集的时候,
用户线程是不会执行的,业务就会暂时不可用,这段不可用的时期被称为 STW(Stop The World)。Serial 是针对新生代
的,使用复制算法
来收集垃圾,
而 Serial Old 是针对老年代
的,使用标记整理
来收集垃圾。
- Parallel Scavenge(用于新生代)/ Parallel Old(用于老年代)
这两个垃圾收集器和 Serial / Serial 很像,只是使用多线程来收集垃圾,而且更多的是关注吞吐量,也就是用户 CPU 时间比值。
上面说过垃圾收集器会存在一个 STW 时间段,就是说在收集垃圾的时候不能执行用户代码,而这两个垃圾收集器虽然是多线程的,但是还是有
STW 这个停顿时间段存在。Parallel Scavenge 是针对新生代
的,使用复制算法
来收集垃圾,而 Parallel Old 是针对老年代
的,
使用标记整理
来收集垃圾。
- ParNew(用于新生代)
这个垃圾收集器和 Serial / Serial 很像,只是它也是多线程的,和 Parallel Scavenge / Parallel Old 也很像,只是它能和 CMS 垃圾收集器配合使用。
上面说过,ParNew 是针对新生代的,而且不存在 ParNew Old 之类的针对老年代的收集器,所以它可以和 CMS 这个老年代收集器配合使用算是补偿吧。
它使用的垃圾收集算法是复制算法
。
- CMS(用于老年代)
这是个针对老年代的收集器,而且关注点是最短回收停顿时间,也就是说希望 STW 时间尽可能短。它是多线程的,而且和之前的收集器都不一样的是
它使用标记清除算法
,这就意味着它收集之后的内存空间是不连续的,如果这时候需要分配一个大对象,可能就需要进行一次整理甚至是执行 Full GC 了。
另外,这个收集器有一个很神奇的地方在于,它允许用户线程同时进行,也就是业务在执行的时候同时执行 GC !这个看起来很神奇,实际上不完全是好事。
首先,业务在执行的时候同时执行 GC 意味着要将 CPU 时间分一部分出来给 GC,如果业务需要的 CPU 资源比较多,就可能导致资源出现争用的问题。
再一个就是,由于在 GC 时程序还在执行,这就有可能还会产生垃圾,这部分新产生的垃圾就被称为浮动垃圾。这就说明这个 GC 没办法针对一整个区域,
如果 GC 清理占用的内存使得程序内存不足,就会启动 Serial Old 来清理了。
这个垃圾收集器有如下几个过程:
- 初始标记(这个部分存在 STW)
- 并发标记
- 重新标记(这个部分存在 STW)
- 并发清除
- G1(新生代和老年代都能用)
这个多线程而且老少通吃(新生代和老年代)的垃圾收集器号称最新高科技收集器,并且打算替换 CMS 收集器。它将整个区域分为多个 Region 区域,
整体使用标记整理算法
收集垃圾,而对于两个 Region 是使用复制算法
来收集垃圾的,但都是可以整理出完整的连续内存空间的算法。
它分为以下几个阶段:
- 初始标记(这个部分存在 STW)
- 并发标记
- 最终标记(这个部分存在 STW)
- 筛选回收(这个部分存在 STW)
对象在内存上的分配策略
主要有几个分配策略:
- 对象优先在 Eden 区分配
之前说过,对象不一定在堆上,也有可能在栈上分配,这是由于 JIT 编译器使用变量逃逸进行优化导致的情况。
但是绝大多数情况都是在堆上分配,而且优先在 Eden 区进行分配。
- 大对象和长期存活的对象直接进入老年代