diff --git a/notes/JVM.md b/notes/JVM.md index da815fc6..5054c6e6 100644 --- a/notes/JVM.md +++ b/notes/JVM.md @@ -1,75 +1,65 @@ -* [内存模型](#内存模型) - * [1. 程序计数器](#1-程序计数器) - * [2. Java 虚拟机栈](#2-java-虚拟机栈) - * [3. 本地方法栈](#3-本地方法栈) - * [4. Java 堆](#4-java-堆) - * [5. 方法区](#5-方法区) - * [6. 运行时常量池](#6-运行时常量池) - * [7. 直接内存](#7-直接内存) -* [垃圾收集](#垃圾收集) - * [1. 判断一个对象是否可回收](#1-判断一个对象是否可回收) - * [1.1 引用计数](#11-引用计数) - * [1.2 可达性](#12-可达性) - * [1.3 引用类型](#13-引用类型) - * [1.3 方法区的回收](#13-方法区的回收) - * [1.4 finalize()](#14-finalize) - * [2. 垃圾收集算法](#2-垃圾收集算法) - * [2.1 标记-清除算法](#21-标记-清除算法) - * [2.2 复制算法](#22-复制算法) - * [2.3 标记-整理算法](#23-标记-整理算法) - * [2.4 分代收集算法](#24-分代收集算法) - * [3. 垃圾收集器](#3-垃圾收集器) - * [3.1 Serial 收集器](#31-serial-收集器) - * [3.2 ParNew 收集器](#32-parnew-收集器) - * [3.3 Parallel Scavenge 收集器](#33-parallel-scavenge-收集器) - * [3.4 Serial Old 收集器](#34-serial-old-收集器) - * [3.5 Parallel Old 收集器](#35-parallel-old-收集器) - * [3.6 CMS 收集器](#36-cms-收集器) - * [3.7 G1 收集器](#37-g1-收集器) - * [3.8 七种垃圾收集器的比较](#38-七种垃圾收集器的比较) - * [4. 内存分配与回收策略](#4-内存分配与回收策略) - * [4.1 优先在 Eden 分配](#41-优先在-eden-分配) - * [4.2 大对象直接进入老年代](#42-大对象直接进入老年代) - * [4.3 长期存活的对象进入老年代](#43-长期存活的对象进入老年代) - * [4.4 动态对象年龄判定](#44-动态对象年龄判定) - * [4.5 空间分配担保](#45-空间分配担保) - * [5. Full GC 的触发条件](#5-full-gc-的触发条件) - * [5.1 调用 System.gc()](#51-调用-systemgc) - * [5.2 老年代空间不足](#52-老年代空间不足) - * [5.3 空间分配担保失败](#53-空间分配担保失败) - * [5.4 JDK 1.7 及以前的永久代空间不足](#54-jdk-17-及以前的永久代空间不足) - * [5.5 Concurrent Mode Failure](#55-concurrent-mode-failure) -* [类加载机制](#类加载机制) - * [1 类的生命周期](#1-类的生命周期) - * [2. 类初始化时机](#2-类初始化时机) - * [3. 类加载过程](#3-类加载过程) - * [3.1 加载](#31-加载) - * [3.2 验证](#32-验证) - * [3.3 准备](#33-准备) - * [3.4 解析](#34-解析) - * [3.5 初始化](#35-初始化) - * [4. 类加载器](#4-类加载器) - * [4.1 类与类加载器](#41-类与类加载器) - * [4.2 类加载器分类](#42-类加载器分类) - * [4.3 双亲委派模型](#43-双亲委派模型) -* [JVM 参数](#jvm-参数) +* [一、内存模型](#一内存模型) + * [程序计数器](#程序计数器) + * [Java 虚拟机栈](#java-虚拟机栈) + * [本地方法栈](#本地方法栈) + * [Java 堆](#java-堆) + * [方法区](#方法区) + * [运行时常量池](#运行时常量池) + * [直接内存](#直接内存) +* [二、垃圾收集](#二垃圾收集) + * [判断一个对象是否可回收](#判断一个对象是否可回收) + * [1. 引用计数](#1-引用计数) + * [2. 可达性](#2-可达性) + * [3. 引用类型](#3-引用类型) + * [4. 方法区的回收](#4-方法区的回收) + * [5. finalize()](#5-finalize) + * [垃圾收集算法](#垃圾收集算法) + * [1. 标记-清除算法](#1-标记-清除算法) + * [2. 复制算法](#2-复制算法) + * [3. 标记-整理算法](#3-标记-整理算法) + * [4. 分代收集算法](#4-分代收集算法) + * [垃圾收集器](#垃圾收集器) + * [1. Serial 收集器](#1-serial-收集器) + * [2. ParNew 收集器](#2-parnew-收集器) + * [3. Parallel Scavenge 收集器](#3-parallel-scavenge-收集器) + * [4. Serial Old 收集器](#4-serial-old-收集器) + * [5. Parallel Old 收集器](#5-parallel-old-收集器) + * [6. CMS 收集器](#6-cms-收集器) + * [7. G1 收集器](#7-g1-收集器) + * [8. 七种垃圾收集器的比较](#8-七种垃圾收集器的比较) + * [内存分配与回收策略](#内存分配与回收策略) + * [Full GC 的触发条件](#full-gc-的触发条件) +* [三、类加载机制](#三类加载机制) + * [类的生命周期](#类的生命周期) + * [类初始化时机](#类初始化时机) + * [类加载过程](#类加载过程) + * [1. 加载](#1-加载) + * [2. 验证](#2-验证) + * [3. 准备](#3-准备) + * [4. 解析](#4-解析) + * [5. 初始化](#5-初始化) + * [类加载器](#类加载器) + * [1. 类与类加载器](#1-类与类加载器) + * [2. 类加载器分类](#2-类加载器分类) + * [3. 双亲委派模型](#3-双亲委派模型) +* [四、JVM 参数](#四jvm-参数) * [GC 优化配置](#gc-优化配置) * [GC 类型设置](#gc-类型设置) -# 内存模型 +# 一、内存模型

注:白色区域为线程私有的,蓝色区域为线程共享的。 -## 1. 程序计数器 +## 程序计数器 记录正在执行的虚拟机字节码指令的地址(如果正在执行的是 Native 方法则为空)。 -## 2. Java 虚拟机栈 +## Java 虚拟机栈 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 @@ -78,11 +68,11 @@ 1. 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常; 2. 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。 -## 3. 本地方法栈 +## 本地方法栈 与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 -## 4. Java 堆 +## Java 堆 所有对象实例都在这里分配内存。 @@ -90,8 +80,7 @@ 不需要连续内存,可以通过 -Xmx 和 -Xms 来控制动态扩展内存大小,如果动态扩展失败会抛出 OutOfMemoryError 异常。 - -## 5. 方法区 +## 方法区 用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 @@ -99,7 +88,7 @@ 对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现,HotSpot 虚拟机把它当成永久代来进行垃圾回收。 -## 6. 运行时常量池 +## 运行时常量池 运行时常量池是方法区的一部分。 @@ -107,17 +96,17 @@ 在运行期间也可以用过 String 类的 intern() 方法将新的常量放入该区域。 -## 7. 直接内存 +## 直接内存 在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 -# 垃圾收集 +# 二、垃圾收集 程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。垃圾回收主要是针对 Java 堆和方法区进行。 -## 1. 判断一个对象是否可回收 +## 判断一个对象是否可回收 -### 1.1 引用计数 +### 1. 引用计数 给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。 @@ -128,7 +117,7 @@ objA.instance = objB; objB.instance = objA; ``` -### 1.2 可达性 +### 2. 可达性 通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是都是可用的,不可达的对象可被回收。 @@ -139,13 +128,13 @@ GC Roots 一般包含以下内容: 3. 方法区中的常量引用的对象 4. 本地方法栈中引用的对象 -### 1.3 引用类型 +### 3. 引用类型 无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。 Java 对引用的概念进行了扩充,引入四种强度不同的引用类型。 -#### 1.3.1 强引用 +#### 3.1 强引用 只要强引用存在,垃圾回收器永远不会回收调掉被引用的对象。 @@ -155,7 +144,7 @@ Java 对引用的概念进行了扩充,引入四种强度不同的引用类型 Object obj = new Object(); ``` -#### 1.3.2 软引用 +#### 3.2 软引用 用来描述一些还有用但是并非必需的对象。 @@ -170,7 +159,7 @@ Object obj = new Object(); SoftReference sf = new SoftReference(obj); ``` -#### 1.3.3 弱引用 +#### 3.3 弱引用 只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会被回收。 @@ -181,7 +170,7 @@ Object obj = new Object(); WeakReference wf = new WeakReference(obj); ``` -#### 1.3.4 虚引用 +#### 3.4 虚引用 又称为幽灵引用或者幻影引用.一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 @@ -192,7 +181,7 @@ Object obj = new Object(); PhantomReference pf = new PhantomReference(obj); ``` -### 1.3 方法区的回收 +### 4. 方法区的回收 因为方法区主要存放永久代对象,而永久代对象的回收率比新生代差很多,因此在方法区上进行回收性价比不高。 @@ -210,15 +199,15 @@ PhantomReference pf = new PhantomReference(obj); 在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGo 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能,以保证不会出现内存溢出。 -### 1.4 finalize() +### 5. finalize() finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。 当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。 -## 2. 垃圾收集算法 +## 垃圾收集算法 -### 2.1 标记-清除算法 +### 1. 标记-清除算法

@@ -231,7 +220,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。 之后的算法都是基于该算法进行改进。 -### 2.2 复制算法 +### 2. 复制算法

@@ -241,13 +230,13 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。 现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。 -### 2.3 标记-整理算法 +### 3. 标记-整理算法

让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 -### 2.4 分代收集算法 +### 4. 分代收集算法 现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。 @@ -256,13 +245,13 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。 1. 新生代使用:复制算法 2. 老年代使用:标记-清理 或者 标记-整理 算法。 -## 3. 垃圾收集器 +## 垃圾收集器

以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。 -### 3.1 Serial 收集器 +### 1. Serial 收集器

@@ -272,7 +261,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。 在 Client 应用场景中,分配给虚拟机管理的内存一般来说不会很大,该收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。 -### 3.2 ParNew 收集器 +### 2. ParNew 收集器

@@ -282,7 +271,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。 默认开始的线程数量与 CPU 数量相同,可以使用 -XX:ParallelGCThreads 参数来设置线程数。 -### 3.3 Parallel Scavenge 收集器 +### 3. Parallel Scavenge 收集器 是并行的多线程收集器。 @@ -294,7 +283,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。 还提供了一个参数 -XX:+UseAdaptiveSizePolicy,这是一个开关参数,打开参数后,就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种方式称为 GC 自适应的调节策略(GC Ergonomics)。自适应调节策略也是它与 ParNew 收集器的一个重要区别。 -### 3.4 Serial Old 收集器 +### 4. Serial Old 收集器

@@ -303,7 +292,7 @@ Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下 1. 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。 2. 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。 -### 3.5 Parallel Old 收集器 +### 5. Parallel Old 收集器

@@ -311,7 +300,7 @@ Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下 在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。 -### 3.6 CMS 收集器 +### 6. CMS 收集器

@@ -336,7 +325,7 @@ CMS(Concurrent Mark Sweep),从 Mark Sweep 可以知道它是基于标记- 3. 标记-清除算法导致的空间碎片,给大对象分配带来很大麻烦,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前出发一次 Full GC。 -### 3.7 G1 收集器 +### 7. G1 收集器

@@ -362,7 +351,7 @@ Region 不可能是孤立的,一个对象分配在某个 Region 中,可以 3. 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。 4. 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。 -### 3.8 七种垃圾收集器的比较 +### 8. 七种垃圾收集器的比较 | 收集器 | 串行、并行 or 并发 | 新生代 / 老年代 | 算法 | 目标 | 适用场景 | | --- | --- | --- | --- | --- | --- | @@ -374,11 +363,11 @@ Region 不可能是孤立的,一个对象分配在某个 Region 中,可以 | **CMS** | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或 B/S 系统服务端上的 Java 应用 | | **G1** | 并发 | both | 标记-整理 + 复制算法 | 响应速度优先 | 面向服务端应用,将来替换 CMS | -## 4. 内存分配与回收策略 +## 内存分配与回收策略 对象的内存分配,也就是在堆上分配。主要分配在新生代的 Eden 区上,少数情况下也可能直接分配在老年代中。 -### 4.1 优先在 Eden 分配 +#### 1. 优先在 Eden 分配 大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC; @@ -387,53 +376,53 @@ Region 不可能是孤立的,一个对象分配在某个 Region 中,可以 - Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。 - Full GC:发生在老年代上,老年代对象和新生代的相反,其存活时间长,因此 Full GC 很少执行,而且执行速度会比 Minor GC 慢很多。 -### 4.2 大对象直接进入老年代 +#### 2. 大对象直接进入老年代 大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。 提供 -XX:PretenureSizeThreshold 参数,大于此值的对象直接在老年代分配,避免在 Eden 区和 Survivor 区之间的大量内存复制; -### 4.3 长期存活的对象进入老年代 +#### 3. 长期存活的对象进入老年代 JVM 为对象定义年龄计数器,经过 Minor GC 依然存活,并且能被 Survivor 区容纳的,移被移到 Survivor 区,年龄就增加 1 岁,增加到一定年龄则移动到老年代中(默认 15 岁,通过 -XX:MaxTenuringThreshold 设置); -### 4.4 动态对象年龄判定 +#### 4. 动态对象年龄判定 JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无序等待 MaxTenuringThreshold 中要求的年龄。 -### 4.5 空间分配担保 +#### 5. 空间分配担保 在发生 Minor GC 之前,JVM 先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的;如果不成立的话 JVM 会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次 Full GC。 -## 5. Full GC 的触发条件 +## Full GC 的触发条件 对于 Minor GC,其触发条件非常简单,当 Eden 区空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件: -### 5.1 调用 System.gc() +#### 1. 调用 System.gc() 此方法的调用是建议 JVM 进行 Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加 Full GC 的频率,也即增加了间歇性停顿的次数。因此强烈建议能不使用此方法就不要使用,让虚拟机自己去管理它的内存。可通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc()。 -### 5.2 老年代空间不足 +#### 2. 老年代空间不足 老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等,当执行 Full GC 后空间仍然不足,则抛出 Java.lang.OutOfMemoryError。为避免以上原因引起的 Full GC,调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间以及不要创建过大的对象及数组。 -### 5.3 空间分配担保失败 +#### 3. 空间分配担保失败 使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果出现了 HandlePromotionFailure 担保失败,则会触发 Full GC。 -### 5.4 JDK 1.7 及以前的永久代空间不足 +#### 4. JDK 1.7 及以前的永久代空间不足 在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出 java.lang.OutOfMemoryError,为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。 -### 5.5 Concurrent Mode Failure +#### 5. Concurrent Mode Failure 执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(有时候“空间不足”是 CMS GC 时当前的浮动垃圾过多导致暂时性的空间不足触发 Full GC),便会报 Concurrent Mode Failure 错误,并触发 Full GC。 -# 类加载机制 +# 三、类加载机制 类是在运行期间动态加载的。 -## 1 类的生命周期 +## 类的生命周期

@@ -449,7 +438,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才 其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。 -## 2. 类初始化时机 +## 类初始化时机 虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生): @@ -483,11 +472,11 @@ SuperClass[] sca = new SuperClass[10]; System.out.println(ConstClass.HELLOWORLD); ``` -## 3. 类加载过程 +## 类加载过程 包含了加载、验证、准备、解析和初始化这 5 个阶段。 -### 3.1 加载 +### 1. 加载 加载是类加载的一个阶段,注意不要混淆。 @@ -506,29 +495,29 @@ System.out.println(ConstClass.HELLOWORLD); - 从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。 ... -### 3.2 验证 +### 2. 验证 确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 主要有以下 4 个阶段: -**1. 文件格式验证** +#### 1. 文件格式验证 验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。 -**2. 元数据验证** +#### 2. 元数据验证 对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。 -**3. 字节码验证** +#### 3. 字节码验证 通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。 -**4. 符号引用验证** +#### 4. 符号引用验证 发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。 -### 3.3 准备 +### 3. 准备 类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。 @@ -546,11 +535,11 @@ public static int value = 123; public static final int value = 123; ``` -### 3.4 解析 +### 4. 解析 将常量池的符号引用替换为直接引用的过程。 -### 3.5 初始化 +### 5. 初始化 初始化阶段才真正开始执行类中的定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 <clinit>() 方法的过程。 @@ -597,15 +586,15 @@ public static void main(String[] args) { - 虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个进程阻塞,在实际过程中此种阻塞很隐蔽。 -## 4. 类加载器 +## 类加载器 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流 ( 即字节码 )”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。 -### 4.1 类与类加载器 +### 1. 类与类加载器 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗而言:比较两个类是否“相等”(这里所指的“相等”,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof() 关键字做对象所属关系判定等情况),只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。 -### 4.2 类加载器分类 +### 2. 类加载器分类 从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器: @@ -619,21 +608,21 @@ public static void main(String[] args) { - 应用程序类加载器(Application ClassLoader) 这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 -### 4.3 双亲委派模型 +### 3. 双亲委派模型 应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。

-**工作过程** +#### 3.1 工作过程 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器,每一个层次的加载器都是如此,依次递归。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成此加载请求(它搜索范围中没有找到所需类)时,子加载器才会尝试自己加载。 -**好处** +#### 3.2 好处 使用双亲委派模型来组织类加载器之间的关系,使得 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 java.lang.Object,它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型,由各个类加载器自行加载的话,如果用户编写了一个称为java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将变得一片混乱。如果开发者尝试编写一个与 rt.jar 类库中已有类重名的 Java 类,将会发现可以正常编译,但是永远无法被加载运行。 -**实现** +#### 3.3 实现 ```java protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ @@ -660,7 +649,7 @@ protected synchronized Class loadClass(String name, boolean resolve) throws C } ``` -# JVM 参数 +# 四、JVM 参数 ## GC 优化配置