前言
体能状态先于精神状态,习惯先于决心,聚焦先于喜好。
本文基于 HotSpot 虚拟机
本文基于 HotSpot 虚拟机
JVM 内存模型中的堆(Heap)
JVM 内存模型中的堆是整个JVM中占用内存最大的一块。
堆分为新生代和老年代
堆的内存空间可以被细分为新生代和老年代,新生代中的对象有着“朝生夕死”的特征,老年代中的对象则大概率的会"持久"的被应用调用。
新生代对象达到某些条件(比如新生代垃圾回收次数超过默认的15次,或同年龄对象超过Survivor一半)可以进入老年代。
超过阀值大小的大对象直接进入老年代。
新生代 和 Minor GC
在HotSpot 中,新生代又被细分为一个 Eden 和 两个Survivor,一个 Eden :一个 Survivor=8:1,即新生代的内存空间会被分为10份,Eden占8份, 两个Survivor各占一份。两个Survivor又被细分为 Survivor From 和 Survivor To
新生代空间有限,当条件满足时会触发新生代的垃圾回收——Minor GC,存活下来的对象放入 Survivor To,连同之前 Survivor From 依旧存活且没有进入老年代的对象一起存到 Survivor To,接着, Eden和Survivor From 会被置空, Survivor From和 Survivor To 互换角色,等待下一次垃圾回收。
老年代和 Major GC/Full GC
老年代空间也是有限的,老年代的垃圾回收叫做 Major GC/FullGC。
Major GC 和 FullGC 一个意思。
Minor GC 和 Full GC(Major GC) 的关系
新生带的 Minor GC 非常频繁,并且一般回收速度也很快。
老年代的 Full GC 一般会比新生代的 Minor GC 慢10倍以上。
Full GC 一般至少伴随一次 Minor GC ,但不是绝对的,比如 Paralle Scavenge 收集器的收集策略就有直接进行 Major GC(Full GC)的策略。
永久代和Major GC/Full GC
永久带是 HotSpot 虚拟机对JVM 规范中方法区的实现,
HotSpot JDK 对方法区的实现在JDK1.7以及之前被叫做永久代,其内存的物理位置和Java Heap(Java 堆)在一起,从JDK1.8开始,永久代被取消,取而代之的被称为元数据,其位置为操作系统的物理内存,比如你的计算机是4G内存,则方法区的实现大小理论上就是4G,而不是之前JVM的永久代的大小了.
在永久代和Java 堆在一起的时候,Full GC也会连带负责 永久代的垃圾回收。
HotSpot JVM 中字符串常量池 是 运行时常量池中特殊的存在
每一个类都有一个运行时常量池,而字符串类型为了实现存储效率最大化,字符串常量池是全局通用的
HotSpot JVM 常量池存储位置的变化
Jdk版本 | 类常量 | 运行时常量池 | 字符串常量池 | 垃圾回收 |
---|---|---|---|---|
1.6及之前 | 非堆(No-heap)-永久代 | 非堆(No-heap)-永久代 | 非堆(No-heap)-永久代 | Full Gc 回收永久代 |
1.7 | 非堆(No-heap)-永久代 | heap(堆) | heap(堆) | Full Gc 回收永久代 |
1.8 | 元数据(宿主机物理内存) | heap(堆) | heap(堆) |
对象已死吗?
计数器法
为每一个对象的应用次数进行统计,当引用次数为0时,表示对象已死,在垃圾回收时可以回收该对象占用的空间——无法解决循环引用问题。
可达性分析法:HotSpot虚拟机使用该方法判断对象存活性
GC Roots 对象引用的表示存活,GC Roots 对象都不引用的表示对象已死,而已进行回收。
GC Roots 对象包含:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 本地方法栈中JNI(即一般说的Native方法)引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
垃圾回收器
垃圾回收器也分为新生代的垃圾回收器和老年代的垃圾回收器搭配使用,直到 G1出现——G1是不区分新生代和永久代的。
G1 在 JDK 6u14 出现实验版本。
并行(Parallel)和并发(Concurrent)
垃圾回收器分为单线程和多线程。
其中,多线程垃圾回收器又可以分为并发和并行,并行的时候,用户的任务线程依旧是等待的,并发的则是用户线程和垃圾回收线程交替进行或在不同CPU进行。
stop the world 的意思是用户任务完全暂停等待垃圾回收。
单线程-stop the world
并行-stop the world
并发-不用stop the wolrd,CMS 的并发标记和并发清除不会 stop the world
没有任何一个垃圾回收器可以完全避免 stop the world ,包括G1,G1支持并发和并行和 CMS 很像。
新生代和垃圾回收器
新生代垃圾回收算法
新生代的对象有着“朝生夕逝”的特点,所以每次垃圾回收后“存活”下来的对象不多,因此使用的是复制算法,即将Eden区和from区的对象复制到to区。
新生代垃圾回收器种类
新生代的垃圾回收器有 Serial、ParNew、Parallel Scavenge
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
名称 | 出现版本 | 多线程 | 回收算法 | 搭配老年代回收器 | stop the world | 其他 |
---|---|---|---|---|---|---|
Serial | 自JDK诞生 | 否 | 复制算法 | CMS、Serial Old | 是 | |
ParNew | 1.3.1 | 是 | 复制算法 | CMS、Serial Old | 是 | |
Parallel Scavenge | 1.4.0 | 并行 | 复制算法 | Parallel Old | 是 | 吞吐量 |
老年代和垃圾回收器
JDK 11 出现新的垃圾回收器 ZGC
https://developer.aliyun.com/article/726110
老年代垃圾回收器算法
老年代的对象大概率会继续存活下去,所以复制算法就不使用了,但是总是会有对象被清除的,虽然每次清楚的不如新生代多。
老年代存在两种垃圾回收算法,第一种是标记-整理算法(Serial Old),即老年代清理完毕后依旧存活的对象从新整齐的放到一起,使他们之间不留空隙。第二种是标记-清除算法(CMS),即清理完毕之后不再整理,这样做会造成内存随便化,会造成一定程度内存空间的浪费。
CMS 和 Serial Old 可以同时存在。
老年代垃圾回收器种类
CMS、Serial Old(MSC)、Parallel Old
名称 | 出现版本 | 多线程 | 回收算法 | 搭配新生代回收器 | stop the world | 其他 |
---|---|---|---|---|---|---|
Serial Old | 自JDK诞生 | 否 | 标记-整理算法 | Serial、ParNew、Parallel Scavenge | 是 | 可以搭配CMS |
CMS | 1.5 | 是 | 标记-清除算法 | Serial、ParNew | 部分不是 | 可搭配Serial Old;分为初始标记(单线程)、并发标记(并发)、重新标记(并行)、并发清除(并发);初始和重新依旧stop the world |
Parallel Old | 1.6 | 并行 | 标记-整理算法 | Parallel Scavenge | 是 |
JVM 配置垃圾回收器
查看垃圾回收器
JVM 指定垃圾回收器
可以看到一个命令就可以搭配指定新生代和老年代的垃圾回收器。
-XX:+UseSerialGC,虚拟机运行在Client模式下的默认值,Serial+Serial Old。
-XX:+UseParNewGC,ParNew+Serial Old,在JDK1.8被废弃,在JDK1.7还可以使用。
-XX:+UseConcMarkSweepGC,ParNew+CMS+Serial Old。
-XX:+UseParallelGC,虚拟机运行在Server模式下的默认值,Parallel Scavenge+Serial Old(PS Mark Sweep)。
-XX:+UseParallelOldGC,Parallel Scavenge+Parallel Old。
-XX:+UseG1GC,G1+G1。
-XX:+UseZGC ,使用 ZGC 垃圾回收器
参考资料
[1]、《深入理解Java虚拟机》
[2]、https://blog.youkuaiyun.com/weixin_39723544/article/details/86693575
[3]、https://blog.youkuaiyun.com/shi2huang/article/details/80085193