收集器

This commit is contained in:
luoxiang 2019-12-23 21:48:00 +08:00
parent fd57db4403
commit 4b43da7b93
4 changed files with 1 additions and 21 deletions

View File

@ -78,7 +78,6 @@ Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode) # 使用的是H
<div align="center"> <img src="../pictures/java虚拟机运行时数据区.png"/> </div> <div align="center"> <img src="../pictures/java虚拟机运行时数据区.png"/> </div>
### 2.1 程序计数器 ### 2.1 程序计数器
程序计数器Program Counter Register是一块较小的内存空间它可以看做是当前线程所执行的字节码的行号指示器。字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令分支、循环、跳转、异常处理、线程恢复等基础功能都需要该计数器来完成。每条线程都拥有一个独立的程序计数器各条线程之间的计数器互不影响独立存储。 程序计数器Program Counter Register是一块较小的内存空间它可以看做是当前线程所执行的字节码的行号指示器。字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令分支、循环、跳转、异常处理、线程恢复等基础功能都需要该计数器来完成。每条线程都拥有一个独立的程序计数器各条线程之间的计数器互不影响独立存储。
@ -123,7 +122,6 @@ Java 堆Java Heap是虚拟机所管理的最大一块的内存空间
<div align="center"> <img src="../pictures/jvm_指针碰撞.png"/> </div> <div align="center"> <img src="../pictures/jvm_指针碰撞.png"/> </div>
+ **空闲列表**:如果 Java 堆不是规整的,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,哪些是不可用的。在进行内存分配时,只需要从该列表中选取出一块足够的内存空间划分给对象实例即可。 + **空闲列表**:如果 Java 堆不是规整的,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,哪些是不可用的。在进行内存分配时,只需要从该列表中选取出一块足够的内存空间划分给对象实例即可。
> 注Java 堆是否规整取决于其采用的垃圾收集器是否带有空间压缩整理能力,后文将会介绍。 > 注Java 堆是否规整取决于其采用的垃圾收集器是否带有空间压缩整理能力,后文将会介绍。
@ -172,13 +170,11 @@ Java 堆Java Heap是虚拟机所管理的最大一块的内存空间
<div align="center"> <img src="../pictures/jvm_通过句柄访问对象.png"/> </div> <div align="center"> <img src="../pictures/jvm_通过句柄访问对象.png"/> </div>
通过直接指针访问对象: 通过直接指针访问对象:
<div align="center"> <img src="../pictures/jvm_通过直接指针访问对象.png"/> </div> <div align="center"> <img src="../pictures/jvm_通过直接指针访问对象.png"/> </div>
句柄访问的优点在于对象移动时(垃圾收集时移动对象是非常普遍的行为)只需要改变句柄中实例数据的指针,而 `reference` 本生并不需要修改;指针访问则反之,由于其 `reference` 中存储的直接就是对象地址,所以当对象移动时, `reference` 需要被修改。但针对只需要访问对象本身的场景指针访问则可以减少一次定位开销。由于对象访问是一项非常频繁的操作所以这类减少的效果会非常显著基于这个原因HotSpot 主要使用的是指针访问的方式。 句柄访问的优点在于对象移动时(垃圾收集时移动对象是非常普遍的行为)只需要改变句柄中实例数据的指针,而 `reference` 本生并不需要修改;指针访问则反之,由于其 `reference` 中存储的直接就是对象地址,所以当对象移动时, `reference` 需要被修改。但针对只需要访问对象本身的场景指针访问则可以减少一次定位开销。由于对象访问是一项非常频繁的操作所以这类减少的效果会非常显著基于这个原因HotSpot 主要使用的是指针访问的方式。
## 四、垃圾收集算法 ## 四、垃圾收集算法
@ -210,7 +206,6 @@ System.gc();
<div align="center"> <img src="../pictures/jvm_可达性分析.png"/> </div> <div align="center"> <img src="../pictures/jvm_可达性分析.png"/> </div>
在 Java 语言中,固定可作为 `GC Roots` 的对象包括以下几种: 在 Java 语言中,固定可作为 `GC Roots` 的对象包括以下几种:
+ 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等; + 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等;
@ -272,7 +267,6 @@ System.gc();
<div align="center"> <img src="../pictures/jvm_标记清除算法.png"/> </div> <div align="center"> <img src="../pictures/jvm_标记清除算法.png"/> </div>
它主要有以下两个缺点: 它主要有以下两个缺点:
+ 执行效率不稳定:如果 Java 堆上包含大量需要回收的对象,则需要进行大量标记和清除动作; + 执行效率不稳定:如果 Java 堆上包含大量需要回收的对象,则需要进行大量标记和清除动作;
@ -288,7 +282,6 @@ System.gc();
<div align="center"> <img src="../pictures/jvm_标记复制算法.png"/> </div> <div align="center"> <img src="../pictures/jvm_标记复制算法.png"/> </div>
基于新生代 “朝生夕灭” 的特点,大多数虚拟机都不会按照 1:1 的比例来进行内存划分,例如 HotSpot 虚拟机会将内存空间划分为一块较大的 `Eden` 和 两块较小的 `Survivor` 空间,它们之间的比例是 8:1:1 。 每次分配时只会使用 `Eden` 和其中的一块 `Survivor` ,发生垃圾回收时,只需要将存活的对象一次性复制到另外一块 `Survivor` 上,这样只有 10% 的内存空间会被浪费掉。当 `Survivor` 空间不足以容纳一次 `Minor GC` 时,此时由其他内存区域(通常是老年代)来进行分配担保。 基于新生代 “朝生夕灭” 的特点,大多数虚拟机都不会按照 1:1 的比例来进行内存划分,例如 HotSpot 虚拟机会将内存空间划分为一块较大的 `Eden` 和 两块较小的 `Survivor` 空间,它们之间的比例是 8:1:1 。 每次分配时只会使用 `Eden` 和其中的一块 `Survivor` ,发生垃圾回收时,只需要将存活的对象一次性复制到另外一块 `Survivor` 上,这样只有 10% 的内存空间会被浪费掉。当 `Survivor` 空间不足以容纳一次 `Minor GC` 时,此时由其他内存区域(通常是老年代)来进行分配担保。
#### 5. 标记-整理算法 #### 5. 标记-整理算法
@ -298,7 +291,6 @@ System.gc();
<div align="center"> <img src="../pictures/jvm_标记移动算法.png"/> </div> <div align="center"> <img src="../pictures/jvm_标记移动算法.png"/> </div>
## 五、经典垃圾收集器 ## 五、经典垃圾收集器
并行与并发是并发编程中的专有名词,在谈论垃圾收集器的上下文语境中,它们的含义如下: 并行与并发是并发编程中的专有名词,在谈论垃圾收集器的上下文语境中,它们的含义如下:
@ -311,8 +303,7 @@ HotSpot 虚拟机中一共存在七款经典的垃圾收集器:
<div align="center"> <img src="../pictures/jvm_hotspot_垃圾收集器.png"/> </div> <div align="center"> <img src="../pictures/jvm_hotspot_垃圾收集器.png"/> </div>
> 注:收集器之间存在连线,则代表它们可以搭配使用。
### 5.1 Serial 收集器 ### 5.1 Serial 收集器
@ -321,7 +312,6 @@ Serial 收集器是最基础、历史最悠久的收集器,它是一个单线
<div align="center"> <img src="../pictures/jvm_收集器1.png"/> </div> <div align="center"> <img src="../pictures/jvm_收集器1.png"/> </div>
### 5.2 ParNew 收集器 ### 5.2 ParNew 收集器
他是 Serial 收集器的多线程版本,可以使用多条线程进行垃圾回收: 他是 Serial 收集器的多线程版本,可以使用多条线程进行垃圾回收:
@ -329,7 +319,6 @@ Serial 收集器是最基础、历史最悠久的收集器,它是一个单线
<div align="center"> <img src="../pictures/jvm_收集器2.png"/> </div> <div align="center"> <img src="../pictures/jvm_收集器2.png"/> </div>
### 5.3 Parallel Scavenge 收集器 ### 5.3 Parallel Scavenge 收集器
Parallel Scavenge 也是新生代收集器,基于 标记-复制 算法进行实现,它的目标是达到一个可控的吞吐量。这里的吞吐量指的是处理器运行用户代码的时间与处理器总消耗时间的比值: Parallel Scavenge 也是新生代收集器,基于 标记-复制 算法进行实现,它的目标是达到一个可控的吞吐量。这里的吞吐量指的是处理器运行用户代码的时间与处理器总消耗时间的比值:
@ -348,7 +337,6 @@ Parallel Scavenge 收集器提供两个参数用于精确控制吞吐量:
<div align="center"> <img src="../pictures/jvm_收集器1.png"/> </div> <div align="center"> <img src="../pictures/jvm_收集器1.png"/> </div>
### 5.5 Paralled Old 收集器 ### 5.5 Paralled Old 收集器
Paralled Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,采用 标记-整理 算法实现: Paralled Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,采用 标记-整理 算法实现:
@ -356,7 +344,6 @@ Paralled Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程
<div align="center"> <img src="../pictures/jvm_收集器3.png"/> </div> <div align="center"> <img src="../pictures/jvm_收集器3.png"/> </div>
### 5.6 CMS 收集器 ### 5.6 CMS 收集器
CMSConcurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器基于 标记-清除 算法实现,整个收集过程分为以下四个阶段: CMSConcurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器基于 标记-清除 算法实现,整个收集过程分为以下四个阶段:
@ -369,7 +356,6 @@ CMSConcurrent Mark Sweep收集器是一种以获取最短回收停顿时
<div align="center"> <img src="../pictures/jvm_收集器4.png"/> </div> <div align="center"> <img src="../pictures/jvm_收集器4.png"/> </div>
其优点在于耗时长的 并发标记 和 并发清除 阶段都不需要暂停用户线程,因此其停顿时间较短,其主要缺点如下: 其优点在于耗时长的 并发标记 和 并发清除 阶段都不需要暂停用户线程,因此其停顿时间较短,其主要缺点如下:
+ 由于涉及并发操作,因此对处理器资源比较敏感。 + 由于涉及并发操作,因此对处理器资源比较敏感。
@ -383,7 +369,6 @@ Garbage First简称 G1是一款面向服务端的垃圾收集器也是
<div align="center"> <img src="../pictures/G1_内存布局.png"/> </div> <div align="center"> <img src="../pictures/G1_内存布局.png"/> </div>
上面还有一些 Region 使用 H 进行标注,它代表 Humongous表示这些 Region 用于存储大对象humongous objectH-obj即大小大于等于 region 一半的对象。G1 收集器的运行大致可以分为以下四个步骤: 上面还有一些 Region 使用 H 进行标注,它代表 Humongous表示这些 Region 用于存储大对象humongous objectH-obj即大小大于等于 region 一半的对象。G1 收集器的运行大致可以分为以下四个步骤:
1. **初始标记 (Inital Marking)**:标记 `GC Roots` 能直接关联到的对象,并且修改 TAMSTop at Mark Start指针的值让下一阶段用户线程并发运行时能够正确的在 Reigin 中分配新对象。G1 为每一个 Reigin 都设计了两个名为 TAMS 的指针,新分配的对象必须位于这两个指针位置以上,位于这两个指针位置以上的对象默认被隐式标记为存活的,不会纳入回收范围; 1. **初始标记 (Inital Marking)**:标记 `GC Roots` 能直接关联到的对象,并且修改 TAMSTop at Mark Start指针的值让下一阶段用户线程并发运行时能够正确的在 Reigin 中分配新对象。G1 为每一个 Reigin 都设计了两个名为 TAMS 的指针,新分配的对象必须位于这两个指针位置以上,位于这两个指针位置以上的对象默认被隐式标记为存活的,不会纳入回收范围;
@ -394,7 +379,6 @@ Garbage First简称 G1是一款面向服务端的垃圾收集器也是
<div align="center"> <img src="../pictures/jvm_收集器5.png"/> </div> <div align="center"> <img src="../pictures/jvm_收集器5.png"/> </div>
### 5.8 内存分配原则 ### 5.8 内存分配原则
#### 1. 对象优先在 Eden 分配 #### 1. 对象优先在 Eden 分配
@ -428,7 +412,6 @@ Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数
<div align="center"> <img src="../pictures/jvm_类的生命周期.png"/> </div> <div align="center"> <img src="../pictures/jvm_类的生命周期.png"/> </div>
《Java 虚拟机规范》严格规定了有且只有六种情况必须立即对类进行初始化: 《Java 虚拟机规范》严格规定了有且只有六种情况必须立即对类进行初始化:
1. 遇到 `new``getstatic``putstatic``invokestatic` 这四条字节码指令,如果类型进行过初始化,则需要先触发其进行初始化,能够生成这四条指令码的典型 Java 代码场景有: 1. 遇到 `new``getstatic``putstatic``invokestatic` 这四条字节码指令,如果类型进行过初始化,则需要先触发其进行初始化,能够生成这四条指令码的典型 Java 代码场景有:
@ -508,7 +491,6 @@ JDK 9 之前的 Java 应用都是由这三种类加载器相互配合来完成
<div align="center"> <img src="../pictures/jvm_双亲委派模型.png"/> </div> <div align="center"> <img src="../pictures/jvm_双亲委派模型.png"/> </div>
上图所示的各种类加载器之间的层次关系被称为类加载器的 “双亲委派模型”,“双亲委派模型” 要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,需要注意的是这里的加载器之间的父子关系一般不是以继承关系来实现的,而是使用组合关系来复用父类加载器的代码。 上图所示的各种类加载器之间的层次关系被称为类加载器的 “双亲委派模型”,“双亲委派模型” 要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,需要注意的是这里的加载器之间的父子关系一般不是以继承关系来实现的,而是使用组合关系来复用父类加载器的代码。
双亲委派模型的工作过程如下:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。基于双亲委派模型可以保证程序中的类在各种类加载器环境中都是同一个类,否则就有可能出现一个程序中存在两个不同的 `java.lang.Object` 的情况。 双亲委派模型的工作过程如下:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。基于双亲委派模型可以保证程序中的类在各种类加载器环境中都是同一个类,否则就有可能出现一个程序中存在两个不同的 `java.lang.Object` 的情况。
@ -524,7 +506,6 @@ JDK 9 之后为了适应模块化的发展,类加载器做了如下变化:
<div align="center"> <img src="../pictures/jvm_jdk9_双亲委派模型.png"/> </div> <div align="center"> <img src="../pictures/jvm_jdk9_双亲委派模型.png"/> </div>
## 七、程序编译 ## 七、程序编译
### 7.1 编译器分类 ### 7.1 编译器分类
@ -558,7 +539,6 @@ JDK 9 之后为了适应模块化的发展,类加载器做了如下变化:
<div align="center"> <img src="../pictures/分层编译的交互关系.png"/> </div> <div align="center"> <img src="../pictures/分层编译的交互关系.png"/> </div>
实施分层编译后,解释器、客户端编译器和服务端编译器就会同时工作,可以用客户端编译器获取更高的编译速度、用服务端编译器来获取更好的编译质量。 实施分层编译后,解释器、客户端编译器和服务端编译器就会同时工作,可以用客户端编译器获取更高的编译速度、用服务端编译器来获取更好的编译质量。
### 7.4 热点探测 ### 7.4 热点探测

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 16 KiB