如何判断对象已死?
常见方法有 引用计数法 和 可达性分析法。
引用计数法
每一个对象都维护一个引用计数器,当该对象被引用的时候,计数器加1,当失效时,计数器减1,当该对象没有被任何对象引用时,计数器为0,这时候认定为垃圾对象。
主流的Java虚拟机没有采用 引用计数法 来管理内存,其主要原因在于它很难解决对象之间相互循环引用的问题。
可达性分析法
Java 判断对象是否存活使用的是 可达性分析法。
基本思路是通过一系列成为 “GC Root” 的对象作为起点,从这些起点开始向下搜索,搜索走过的路径称之为“引用链”,当一个对象到 GC Root 没有任何引用链相连时,即 GC Root 到该对象不可达,则认为该对象不可用。
什么对象可以作为 GC Root ?
- 虚拟机栈(栈用的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI 引用的对象
Eclipse documentation Garbage Collection Roots
在可达性分析之后不可达的对象会立即判定为死亡吗?
如果一个对象被判定为不可达,这时候会再判断一下该对象是否有必要执行finalize()方法,如果没有该方法,或者方法已经被执行过,则没有必要被执行,直接标记为死亡,否则会把该对象放入 F-Queue 队列。如果该方法正常执行,则标记为死亡;如果在该方法内又重新把自己与引用链上的任何对象建立关联,则该对象会重新复活。
常见GC算法
当判断出对象是否存活之后,接下来就开始进行垃圾回收(GC)。常见的 GC 算法有 拷贝 (copying)、标记-清除 (mark-sweep)、标记-整理 (mark-compact)。
拷贝 (copying)
拷贝算法的实现是将内存按块划分,进行一轮标记之后,会把存活的对象拷贝到另一块区域,原来的区域则一次性全部清除掉。
程序中大部分对象是“朝生夕死”的对象,大概有98%左右,复制的时候只用复制2%左右的对象到另一个空间,所以复制算法是一种效率很高的算法;复制的时候,对象新空间也是按照顺序排列的,没有内存碎片的问题。
HotSpot中,拷贝算法常用在 Eden→Survivor、Survivor1 ↔ Survivor2 之间相互拷贝、和 整个年轻代(Eden+Survivor)→ 老年代之间的复制。
标记-清除 (mark-sweep)
该算法是把被标记为死亡的对象直接回收掉,导致结果就是会产生大量的内存缝隙。
如果新产生的对象大小小于缝隙的大小,则直接存放在缝隙空间中;如果新产生的对象大小很大,所有缝隙空间都不够分配,则该对象可能会创建失败,最常见的错误表现就是 OutOfMemory (OOM)。
标记-整理 (mark-compact)
复制算法的缺点是当对象存活率很高(例如老年代、持久代)的时候,该算法的效率就会变得很低,可以想象一下标记了一遍大部分对象都没有死亡,可能就需要复制大量的对象到新的空间;该算法还需要准备一份目标空间用于复制操作。
标记清除上面已经说过,会产生大量的内存缝隙,新产生的对象不好分配新的空间。
标记整理并不是直接直接清理掉死亡的对象,而是把存活的对象位置向前移动(相当于对缝隙进行压缩),最后把后面的大块空间一次性清理掉,避免缝隙的产生。其优点也是其缺点,移动对象进行空间压缩成本是很高的。
JVM 为什么需要分代回收?
了解过上面几种常见 GC 算法之后,就会了解过,实际上每一种算法都有优点和缺点。分代实际上是根据对象存活周期的不同进行内存的划分,这样就可以根据不同年代的特点采用最适合的垃圾回收算法。
拓展阅读 Java虚拟机学习 - 垃圾收集算法
HotSpot 的垃圾收集器
HotSpot 的垃圾收集器有 Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1。
Serial
- Java 1.3.1 之前,新生代唯一的收集器
- 单线程,适用于只有一个CPU的情况
- Stop-the-World
- 在新生代采用“复制算法”
- 在老年代使用“标记整理算法”(Serial Old)
- 在client 模式下是很好的选择,是client模式下新生代的默认收集器
- 单CPU下由于没有线程交互的开销,效率会更高
- 收集几十兆到一两百兆的新生代空间,可以控制在几十毫秒最高一百毫秒以内
client、server 模式如何切换?
启动参数指定 -server 或者 -client 即可,x64位虚拟机只支持 -server
server 模式下默认的垃圾收集器是什么?
新生代:Parallel Scavenge ,使用 复制算法
老年代:Serial Old,使用 标记整理算法
如何查看默认的垃圾收集器是什么?
方法1 -XX:+PrintFlagsFinal
”=”表示参数的默认值,而”:=” 表明了参数被用户或者JVM赋值了。
$ java -XX:+PrintFlagsFinal -version | grep ":="
......
bool UseCompressedClassPointers := true {lp64_product}
bool UseCompressedOops := true {lp64_product}
bool UseParallelGC := true {product}
......
方法2 -XX:+PrintCommandLineFlags
$ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
ParNew
- ParNew 是 Serial 的多线程版本,其它特点和 Serial 完全一样
- 非真正意义上的并发,回收的时候还是要挂起用户线程
- 单CPU下还没有 Serial 效果好
- server 模式下首选的新生代收集器
Parallel Scavenge
- 目标是:吞吐量优先,且可调可控
- 更高效的利用 CPU 时间,尽快完成执行任务,适合后台运算而不需要太多交互的场景
吞吐量(throughput)?
吞吐量 = 运行用户代码时间/(运行用户代码时间 + GC时间)
虚拟机运行了100分钟,有一分钟在垃圾回收,那么 吞吐量为 99%
Parallel Scavenge 常见参数有哪些?
-XX:MaxGCPauseMillis=n
并行收集最大暂停时间
-XX:GCTimeRatio=n
GC时间占程序时间的百分比,公式为1/(1+n)
-XX:ParallelGCThreads=n
并行收集线程数
-XX:+UseAdaptiveSizePolicy
自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等;设置该选项之后,就不要再设置 -Xmn、-XX:SurvivorRatio、-XX:MaxTenuringThreshold 等选项了,但是可以设置 -Xms、-Xmx规定一下整个堆的大小
Serial Old
- Serial 的老年代版本,不同在于使用的算法是 标记-整理
- client 模式下会使用该收集器
- server 模式下 JDK1.5之前与Parallel Scavenge 配合使用,JDK1.6之后可以和Parallel Old配合使用
- CMS 的后备方案,CMS产生大量碎片之后可能会失败,如果失败会使用 Serial Old 进垃圾整理
Parallel Old
- Parallel Scavenge 的老年代版本,不同在于使用的算法是 标记-整理
- 到 JDK6 才有该收集器,在这之前,如果新生代使用 Parallel Scavenge,老年代只能使用Serial Old,因为Parallel Scavenge无法和CMS搭配使用
- 所以JDK6之后吞吐量优先收集器才变的名副其实,因为原来 PS+SO方案 由于Serial Old性能低下会拖累Parallel Scavenge,有时可能还没有 ParNew+CMS给力
为什么Parallel Scavenge无法和CMS搭配使用?
Parallel Scavenge没有使用原本HotSpot其它GC通用的那个GC框架,所以不能跟使用了那个框架的CMS搭配使用。就这么简单,不是什么算法/技术上的不兼容,纯粹是人为造成的
CMS
- 目标是:尽量缩短垃圾回收时间和用户线程的停顿时间
- 严格意义上第一款并发垃圾回收器
- 主要场景在 互联网 B/S 架构上
- 使用标记清除算法
- 步骤
5.1 初始标记:STW、快;GC Root 能直接关联的对象
5.2 并发标记:并发;GC Root Tracing 的过程
5.3 重新标记:STW、快;修复并发标记阶段 用户线程运行时变动的对象
5.4 并发清除:并发 - 因为整个过程中耗时最长的 “并发标记”和“并发清除”是和用户线程并发执行的,所以可认为CMS回收器是和用户线程并发执行的
并发 和 并行 的区别?
并发(concurrent)原指 同一段时间内交替执行,这里偏向于 用户线程和GC线程同时交替执行,或者并行运行在不同的CPU上;
并行(parallel)原指 同一时刻同时执行,这里偏向于 多个GC线程并行,用户线程等待。
CMS 有哪些缺点?
- 对CPU资源非常敏感,其默认启动的回收线程数是 (ParallelGCThreads+3)/4,CPU 数量不足时,对程序的影响会变的很大,如CPU有2个,那么(2+3)/4=1,即有50%的CPU资源用于垃圾回收;
- CMS无法处理浮动垃圾即与应用并发执行时 新产生的垃圾,所以触发CMS并不是在老年代满的时候,而是有个阈值,1.5默认是68%,1.6之后是92%,使用空间到达该比例就会触发CMS,在回收期间新产生的对象无法分配内存时,CMS会失败,进而使用后备方案Serial Old;
- 除了产生浮动垃圾之外,还会产生大量的碎片,碎片过多,会给分配大对象带来麻烦,无法分配时就不得不执行Full GC
Major GC 和 Full GC 是一个概念吗?
基本上可以认为是的。
Major GC 主要描述的是在老年代的内存回收,触发老年代垃圾回收的其中一个原因是新生代在垃圾后将到达一定年龄的对象复制到老年代,实际上可以看出Major GC产生的之前多伴随着 Minor GC 的产生。
我们不应该去关心到底应该是叫 Major GC 还是 Full GC,这些术语无论是在 JVM 规范还是在垃圾收集研究论文中都没有正式的定义,大家应该关注当前的 GC 是否停止了所有应用程序的线程,还是能够并发的处理而不用停掉应用程序的线程。
CMS 常见参数有哪些?
-XX:ConcGCThreads=n
并发收集线程数,如果没有设置则为(ParallelGCThreads + 3)/4
-XX:CMSFullGCsBeforeCompaction
运行多少次CMS GC以后对内存空间进行压缩、整理
-XX:+UseCMSCompactAtFullCollection
可以对年老代的压缩,默认是打开的
-XX:CMSInitiatingOccupancyFraction
设置执行GC的阈值,如75,则占用空间到达75%的时候开始GC
G1
- JDK7u4 开始达到商业标准
- 目标是 替换掉 JDK5 中发布的 CMS 收集器
- 特点
3.1 并行与并发,充分利用CPU资源,减少停顿时间
3.2 分代回收,不需要其它收集器配合即可管理整个堆,通过不同的方式处理不同的代,以优化回收效果
3.3 局部看在Region之间是采用的标记-复制,从整体来看是标记-整理,所以运行期间不会产生内存碎片
3.4 G1除了追求低停顿外,还建立了和预测的停顿时间模型,使用者可以指定:停顿时间不得超过多少毫秒 - G1 的内存布局是将堆划分成多个大小相等的Region,分代变成了逻辑概念,它们可能是一部分Region的集合
G1GC 选项列表
这里有一些 Monica 提到的命令用于调整 G1GC。
-XX:G1HeapRegionSize=n
值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域-XX:MaxGCPauseMillis=200
GC可以暂停多长时间。 这只是个建议值,G1GC 暂停的时间会尽可能比这个值短。默认值是 200 毫秒-XX:G1HeapWastePercent=10
设置您愿意浪费的堆百分比。 垃圾越大, GC 在新区域分配对象时越快,而不是试图将这些空间浪费掉
参数搭配
Serial
-XX:UseSerialGC = Serial + Serial Old
ParNew
-XX:UseParNewGC = ParNew + Serial Old
-XX:UseConcMarkSweepGC = ParNew + CMS + Serial Old
Parallel Scavenge
-XX:UseParallelGC = Parallel Scavenge + Serial Old
-XX:UseParallelOldGC = Parallel Scavenge + Parallel Old
Parallel Old
-XX:UseParallelOldGC = Parallel Scavenge + Parallel Old
CMS
-XX:UseConcMarkSweepGC = ParNew + CMS + Serial Old
G1 GC
-XX:+UseG1GC
总结与划分
混乱的叫法
- Serial [ˈsɪriəl]:串行化收集器、DefNew(默认新生代收集器)
- ParNew:并行新生代收集器(Parallel New)
- Parallel Scavenge [ˈpærəˌlɛl] [ˈskævəndʒ] :PSYoungGen、PSScavenge、并行清除收集器
- Serial Old:串行化老年代收集器
- Parallel Old:PSMarkSweep、PSCompact、并行老年代收集器
- CMS:Concurrent Mark Sweep、并发标记扫描、Concurrent Low Pause Collector、并发低停顿收集器
- G1:Garbage First、垃圾优先收集器
按照回收位置分类
- 新生代
- Serial
- ParNew
- Parallel Scavenge
- G1
- 老年代
- Serial Old
- Parallel Old
- CMS
- G1
拓展阅读
Our Collectors
我们的垃圾收集器
虚拟机调优
JVM 垃圾回收算法
JVM实用参数(六) 吞吐量收集器
JVM实用参数(七)CMS收集器
《深入理解Java虚拟机:JVM高级特性与最佳实践》勘误
JVM之几种垃圾收集器简单介绍
本文主要是 深入理解Java虚拟机 第3章垃圾收集器与内存分配策略 的总结笔记