auto commit

This commit is contained in:
CyC2018
2020-11-17 00:32:18 +08:00
parent f5ad47b470
commit 7e61fc1360
380 changed files with 2371 additions and 46715 deletions

View File

@ -1,44 +1,25 @@
# Java 虚拟机
<!-- GFM-TOC -->
* [运行时数据区域](#一运行时数据区域)
* [程序计数器](#程序计数器)
* [Java 虚拟机栈](#java-虚拟机栈)
* [本地方法栈](#本地方法栈)
* [](#)
* [方法区](#方法区)
* [运行时常量池](#运行时常量池)
* [直接内存](#直接内存)
* [垃圾收集](#二垃圾收集)
* [判断一个对象是否可被回收](#判断一个对象是否可被回收)
* [引用类型](#引用类型)
* [垃圾收集算法](#垃圾收集算法)
* [垃圾收集器](#垃圾收集器)
* [内存分配与回收策略](#三内存分配与回收策略)
* [Minor GC Full GC](#minor-gc--full-gc)
* [内存分配策略](#内存分配策略)
* [Full GC 的触发条件](#full-gc-的触发条件)
* [类加载机制](#四类加载机制)
* [类的生命周期](#类的生命周期)
* [类加载过程](#类加载过程)
* [类初始化时机](#类初始化时机)
* [类与类加载器](#类与类加载器)
* [类加载器分类](#类加载器分类)
* [双亲委派模型](#双亲委派模型)
* [自定义类加载器实现](#自定义类加载器实现)
* [参考资料](#参考资料)
* [Java 虚拟机](#java-虚拟机)
* [运行时数据区域](#一运行时数据区域)
* [垃圾收集](#二垃圾收集)
* [内存分配与回收策略](#三内存分配与回收策略)
* [类加载机制](#四类加载机制)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
本文大部分内容参考 **周志明深入理解 Java 虚拟机** 想要深入学习的话请看原书
# 运行时数据区域
## 运行时数据区域
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5778d113-8e13-4c53-b5bf-801e58080b97.png" width="400px"> </div><br>
## 程序计数器
### 程序计数器
记录正在执行的虚拟机字节码指令的地址如果正在执行的是本地方法则为空
## Java 虚拟机栈
### Java 虚拟机栈
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表操作数栈常量池引用等信息从方法调用直至执行完成的过程对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程
@ -55,7 +36,7 @@ java -Xss2M HackTheJava
- 当线程请求的栈深度超过最大值会抛出 StackOverflowError 异常
- 栈进行动态扩展时如果无法申请到足够内存会抛出 OutOfMemoryError 异常
## 本地方法栈
### 本地方法栈
本地方法栈与 Java 虚拟机栈类似它们之间的区别只不过是本地方法栈为本地方法服务
@ -63,7 +44,7 @@ java -Xss2M HackTheJava
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/66a6899d-c6b0-4a47-8569-9d08f0baf86c.png" width="300px"> </div><br>
##
###
所有对象都在这里分配内存是垃圾收集的主要区域"GC 堆"
@ -80,7 +61,7 @@ java -Xss2M HackTheJava
java -Xms1M -Xmx2M HackTheJava
```
## 方法区
### 方法区
用于存放已被加载的类信息常量静态变量即时编译器编译后的代码等数据
@ -92,7 +73,7 @@ HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永
方法区是一个 JVM 规范永久代与元空间都是其一种实现方式 JDK 1.8 之后原来永久代的数据被分到了堆和元空间中元空间存储类的元信息静态变量和常量池等放入堆中
## 运行时常量池
### 运行时常量池
运行时常量池是方法区的一部分
@ -100,17 +81,17 @@ Class 文件中的常量池(编译器生成的字面量和符号引用)会
除了在编译期生成的常量还允许动态生成例如 String 类的 intern()
## 直接内存
### 直接内存
JDK 1.4 中新引入了 NIO 它可以使用 Native 函数库直接分配堆外内存然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作这样能在一些场景中显著提高性能因为避免了在堆内存和堆外内存来回拷贝数据
# 垃圾收集
## 垃圾收集
垃圾收集主要是针对堆和方法区进行程序计数器虚拟机栈和本地方法栈这三个区域属于线程私有的只存在于线程的生命周期内线程结束之后就会消失因此不需要对这三个区域进行垃圾回收
## 判断一个对象是否可被回收
### 判断一个对象是否可被回收
### 1. 引用计数算法
#### 1. 引用计数算法
为对象添加一个引用计数器当对象增加一个引用时计数器加 1引用失效时计数器减 1引用计数为 0 的对象可被回收
@ -135,7 +116,7 @@ public class Test {
在上述代码中a b 引用的对象实例互相持有了对象的引用因此当我们把对 a 对象与 b 对象的引用去除之后由于两个对象还存在互相之间的引用导致两个 Test 对象无法被回收
### 2. 可达性分析算法
#### 2. 可达性分析算法
GC Roots 为起始点进行搜索可达的对象都是存活的不可达的对象可被回收
@ -149,7 +130,7 @@ Java 虚拟机使用该算法来判断对象是否可被回收GC Roots 一般
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/83d909d2-3858-4fe1-8ff4-16471db0b180.png" width="350px"> </div><br>
### 3. 方法区的回收
#### 3. 方法区的回收
因为方法区主要存放永久代对象而永久代对象的回收率比新生代低很多所以在方法区上进行回收性价比不高
@ -163,19 +144,19 @@ Java 虚拟机使用该算法来判断对象是否可被回收GC Roots 一般
- 加载该类的 ClassLoader 已经被回收
- 该类对应的 Class 对象没有在任何地方被引用也就无法在任何地方通过反射访问该类方法
### 4. finalize()
#### 4. finalize()
类似 C++ 的析构函数用于关闭外部资源但是 try-finally 等方式可以做得更好并且该方法运行代价很高不确定性大无法保证各个对象的调用顺序因此最好不要使用
当一个对象可被回收时如果需要执行该对象的 finalize() 方法那么就有可能在该方法中让对象重新被引用从而实现自救自救只能进行一次如果回收的对象之前调用了 finalize() 方法自救后面回收时不会再调用该方法
## 引用类型
### 引用类型
无论是通过引用计数算法判断对象的引用数量还是通过可达性分析算法判断对象是否可达判定对象是否可被回收都与引用有关
Java 提供了四种强度不同的引用类型
### 1. 强引用
#### 1. 强引用
被强引用关联的对象不会被回收
@ -185,7 +166,7 @@ Java 提供了四种强度不同的引用类型。
Object obj = new Object();
```
### 2. 软引用
#### 2. 软引用
被软引用关联的对象只有在内存不够的情况下才会被回收
@ -197,7 +178,7 @@ SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联
```
### 3. 弱引用
#### 3. 弱引用
被弱引用关联的对象一定会被回收也就是说它只能存活到下一次垃圾回收发生之前
@ -209,7 +190,7 @@ WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
```
### 4. 虚引用
#### 4. 虚引用
又称为幽灵引用或者幻影引用一个对象是否有虚引用的存在不会对其生存时间造成影响也无法通过虚引用得到一个对象
@ -223,9 +204,9 @@ PhantomReference<Object> pf = new PhantomReference<Object>(obj, null);
obj = null;
```
## 垃圾收集算法
### 垃圾收集算法
### 1. 标记 - 清除
#### 1. 标记 - 清除
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/005b481b-502b-4e3f-985d-d043c2b330aa.png" width="400px"> </div><br>
@ -240,7 +221,7 @@ obj = null;
- 标记和清除过程效率都不高
- 会产生大量不连续的内存碎片导致无法给大对象分配内存
### 2. 标记 - 整理
#### 2. 标记 - 整理
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ccd773a5-ad38-4022-895c-7ac318f31437.png" width="400px"> </div><br>
@ -254,7 +235,7 @@ obj = null;
- 需要移动大量对象处理效率比较低
### 3. 复制
#### 3. 复制
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b2b77b9e-958c-4016-8ae5-9c6edd83871e.png" width="400px"> </div><br>
@ -266,7 +247,7 @@ obj = null;
HotSpot 虚拟机的 Eden Survivor 大小比例默认为 8:1保证了内存的利用率达到 90%如果每次回收有多于 10% 的对象存活那么一块 Survivor 就不够用了此时需要依赖于老年代进行空间分配担保也就是借用老年代的空间存储放不下的对象
### 4. 分代收集
#### 4. 分代收集
现在的商业虚拟机采用分代收集算法它根据对象存活周期将内存划分为几块不同块采用适当的收集算法
@ -275,7 +256,7 @@ HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1保证了内
- 新生代使用复制算法
- 老年代使用标记 - 清除 或者 标记 - 整理 算法
## 垃圾收集器
### 垃圾收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c625baa0-dde6-449e-93df-c3a67f2f430f.jpg" width=""/> </div><br>
@ -284,7 +265,7 @@ HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1保证了内
- 单线程与多线程单线程指的是垃圾收集器只使用一个线程而多线程使用多个线程
- 串行与并行串行指的是垃圾收集器与用户程序交替执行这意味着在执行垃圾收集的时候需要停顿用户程序并行指的是垃圾收集器和用户程序同时执行除了 CMS G1 之外其它垃圾收集器都是以串行的方式执行
### 1. Serial 收集器
#### 1. Serial 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg" width=""/> </div><br>
@ -296,7 +277,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
它是 Client 场景下的默认新生代收集器因为在该场景下内存一般来说不会很大它收集一两百兆垃圾的停顿时间可以控制在一百多毫秒以内只要不是太频繁这点停顿时间是可以接受的
### 2. ParNew 收集器
#### 2. ParNew 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg" width=""/> </div><br>
@ -304,7 +285,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
它是 Server 场景下默认的新生代收集器除了性能原因外主要是因为除了 Serial 收集器只有它能与 CMS 收集器配合使用
### 3. Parallel Scavenge 收集器
#### 3. Parallel Scavenge 收集器
ParNew 一样是多线程收集器
@ -316,7 +297,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
可以通过一个开关参数打开 GC 自适应的调节策略GC Ergonomics就不需要手工指定新生代的大小-XmnEden Survivor 区的比例晋升老年代对象年龄等细节参数了虚拟机会根据当前系统的运行情况收集性能监控信息动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量
### 4. Serial Old 收集器
#### 4. Serial Old 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg" width=""/> </div><br>
@ -325,7 +306,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
- JDK 1.5 以及之前版本Parallel Old 诞生以前中与 Parallel Scavenge 收集器搭配使用
- 作为 CMS 收集器的后备预案在并发收集发生 Concurrent Mode Failure 时使用
### 5. Parallel Old 收集器
#### 5. Parallel Old 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/278fe431-af88-4a95-a895-9c3b80117de3.jpg" width=""/> </div><br>
@ -333,7 +314,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
在注重吞吐量以及 CPU 资源敏感的场合都可以优先考虑 Parallel Scavenge Parallel Old 收集器
### 6. CMS 收集器
#### 6. CMS 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/62e77997-6957-4b68-8d12-bfd609bb2c68.jpg" width=""/> </div><br>
@ -354,7 +335,7 @@ CMSConcurrent Mark SweepMark Sweep 指的是标记 - 清除算法。
- 无法处理浮动垃圾可能出现 Concurrent Mode Failure浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾这部分垃圾只能到下一次 GC 时才能进行回收由于浮动垃圾的存在因此需要预留出一部分内存意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收如果预留的内存不够存放浮动垃圾就会出现 Concurrent Mode Failure这时虚拟机将临时启用 Serial Old 来替代 CMS
- 标记 - 清除算法导致的空间碎片往往出现老年代空间剩余但无法找到足够大连续空间来分配当前对象不得不提前触发一次 Full GC
### 7. G1 收集器
#### 7. G1 收集器
G1Garbage-First它是一款面向服务端应用的垃圾收集器在多 CPU 和大内存的场景下有很好的性能HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器
@ -384,21 +365,21 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
- 空间整合整体来看是基于标记 - 整理算法实现的收集器从局部两个 Region 之间上来看是基于复制算法实现的这意味着运行期间不会产生内存空间碎片
- 可预测的停顿能让使用者明确指定在一个长度为 M 毫秒的时间片段内消耗在 GC 上的时间不得超过 N 毫秒
# 内存分配与回收策略
## 内存分配与回收策略
## Minor GC Full GC
### Minor GC Full GC
- Minor GC回收新生代因为新生代对象存活时间很短因此 Minor GC 会频繁执行执行的速度一般也会比较快
- Full GC回收老年代和新生代老年代对象其存活时间长因此 Full GC 很少执行执行速度会比 Minor GC 慢很多
## 内存分配策略
### 内存分配策略
### 1. 对象优先在 Eden 分配
#### 1. 对象优先在 Eden 分配
大多数情况下对象在新生代 Eden 上分配 Eden 空间不够时发起 Minor GC
### 2. 大对象直接进入老年代
#### 2. 大对象直接进入老年代
大对象是指需要连续内存空间的对象最典型的大对象是那种很长的字符串以及数组
@ -406,41 +387,41 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
-XX:PretenureSizeThreshold大于此值的对象直接在老年代分配避免在 Eden Survivor 之间的大量内存复制
### 3. 长期存活的对象进入老年代
#### 3. 长期存活的对象进入老年代
为对象定义年龄计数器对象在 Eden 出生并经过 Minor GC 依然存活将移动到 Survivor 年龄就增加 1 增加到一定年龄则移动到老年代中
-XX:MaxTenuringThreshold 用来定义年龄的阈值
### 4. 动态对象年龄判定
#### 4. 动态对象年龄判定
虚拟机并不是永远要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半则年龄大于或等于该年龄的对象可以直接进入老年代无需等到 MaxTenuringThreshold 中要求的年龄
### 5. 空间分配担保
#### 5. 空间分配担保
在发生 Minor GC 之前虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间如果条件成立的话那么 Minor GC 可以确认是安全的
如果不成立的话虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小如果大于将尝试着进行一次 Minor GC如果小于或者 HandlePromotionFailure 的值不允许冒险那么就要进行一次 Full GC
## Full GC 的触发条件
### Full GC 的触发条件
对于 Minor GC其触发条件非常简单 Eden 空间满时就将触发一次 Minor GC Full GC 则相对复杂有以下条件
### 1. 调用 System.gc()
#### 1. 调用 System.gc()
只是建议虚拟机执行 Full GC但是虚拟机不一定真正去执行不建议使用这种方式而是让虚拟机管理内存
### 2. 老年代空间不足
#### 2. 老年代空间不足
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代长期存活的对象进入老年代等
为了避免以上原因引起的 Full GC应当尽量不要创建过大的对象以及数组除此之外可以通过 -Xmn 虚拟机参数调大新生代的大小让对象尽量在新生代被回收掉不进入老年代还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄让对象在新生代多存活一段时间
### 3. 空间分配担保失败
#### 3. 空间分配担保失败
使用复制算法的 Minor GC 需要老年代的内存空间作担保如果担保失败会执行一次 Full GC具体内容请参考上面的第 5 小节
### 4. JDK 1.7 及以前的永久代空间不足
#### 4. JDK 1.7 及以前的永久代空间不足
JDK 1.7 及以前HotSpot 虚拟机中的方法区是用永久代实现的永久代中存放的为一些 Class 的信息常量静态变量等数据
@ -448,15 +429,15 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
为避免以上原因引起的 Full GC可采用的方法为增大永久代空间或转为使用 CMS GC
### 5. Concurrent Mode Failure
#### 5. Concurrent Mode Failure
执行 CMS GC 的过程中同时有对象要放入老年代而此时老年代空间不足可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足便会报 Concurrent Mode Failure 错误并触发 Full GC
# 类加载机制
## 类加载机制
类是在运行期间第一次使用时动态加载的而不是一次性加载所有类因为如果一次性加载那么会占用很多的内存
## 类的生命周期
### 类的生命周期
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/335fe19c-4a76-45ab-9320-88c90d6a0d7e.png" width="600px"> </div><br>
@ -470,11 +451,11 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
- 使用Using
- 卸载Unloading
## 类加载过程
### 类加载过程
包含了加载验证准备解析和初始化这 5 个阶段
### 1. 加载
#### 1. 加载
加载是类加载的一个阶段注意不要混淆
@ -492,11 +473,11 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
- 运行时计算生成例如动态代理技术 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理类的二进制字节流
- 由其他文件生成例如由 JSP 文件生成对应的 Class
### 2. 验证
#### 2. 验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求并且不会危害虚拟机自身的安全
### 3. 准备
#### 3. 准备
类变量是被 static 修饰的变量准备阶段为类变量分配内存并设置初始值使用的是方法区的内存
@ -514,18 +495,18 @@ public static int value = 123;
public static final int value = 123;
```
### 4. 解析
#### 4. 解析
将常量池的符号引用替换为直接引用的过程
其中解析过程在某些情况下可以在初始化阶段之后再开始这是为了支持 Java 的动态绑定
### 5. 初始化
#### 5. 初始化
<div data="modify -->"></div>
初始化阶段才真正开始执行类中定义的 Java 程序代码初始化阶段是虚拟机执行类构造器 &lt;clinit>() 方法的过程在准备阶段类变量已经赋过一次系统要求的初始值而在初始化阶段根据程序员通过程序制定的主观计划去初始化类变量和其它资源
初始化阶段才真正开始执行类中定义的 Java 程序代码初始化阶段是虚拟机执行类构造器 &lt;clinit\>() 方法的过程在准备阶段类变量已经赋过一次系统要求的初始值而在初始化阶段根据程序员通过程序制定的主观计划去初始化类变量和其它资源
&lt;clinit>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的编译器收集的顺序由语句在源文件中出现的顺序决定特别注意的是静态语句块只能访问到定义在它之前的类变量定义在它之后的类变量只能赋值不能访问例如以下代码
&lt;clinit\>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的编译器收集的顺序由语句在源文件中出现的顺序决定特别注意的是静态语句块只能访问到定义在它之前的类变量定义在它之后的类变量只能赋值不能访问例如以下代码
```java
public class Test {
@ -537,7 +518,7 @@ public class Test {
}
```
由于父类的 &lt;clinit>() 方法先执行也就意味着父类中定义的静态语句块的执行要优先于子类例如以下代码
由于父类的 &lt;clinit\>() 方法先执行也就意味着父类中定义的静态语句块的执行要优先于子类例如以下代码
```java
static class Parent {
@ -556,13 +537,13 @@ public static void main(String[] args) {
}
```
接口中不可以使用静态语句块但仍然有类变量初始化的赋值操作因此接口与类一样都会生成 &lt;clinit>() 方法但接口与类不同的是执行接口的 &lt;clinit>() 方法不需要先执行父接口的 &lt;clinit>() 方法只有当父接口中定义的变量使用时父接口才会初始化另外接口的实现类在初始化时也一样不会执行接口的 &lt;clinit>() 方法
接口中不可以使用静态语句块但仍然有类变量初始化的赋值操作因此接口与类一样都会生成 &lt;clinit\>() 方法但接口与类不同的是执行接口的 &lt;clinit\>() 方法不需要先执行父接口的 &lt;clinit\>() 方法只有当父接口中定义的变量使用时父接口才会初始化另外接口的实现类在初始化时也一样不会执行接口的 &lt;clinit\>() 方法
虚拟机会保证一个类的 &lt;clinit>() 方法在多线程环境下被正确的加锁和同步如果多个线程同时初始化一个类只会有一个线程执行这个类的 &lt;clinit>() 方法其它线程都会阻塞等待直到活动线程执行 &lt;clinit>() 方法完毕如果在一个类的 &lt;clinit>() 方法中有耗时的操作就可能造成多个线程阻塞在实际过程中此种阻塞很隐蔽
虚拟机会保证一个类的 &lt;clinit\>() 方法在多线程环境下被正确的加锁和同步如果多个线程同时初始化一个类只会有一个线程执行这个类的 &lt;clinit\>() 方法其它线程都会阻塞等待直到活动线程执行 &lt;clinit\>() 方法完毕如果在一个类的 &lt;clinit\>() 方法中有耗时的操作就可能造成多个线程阻塞在实际过程中此种阻塞很隐蔽
## 类初始化时机
### 类初始化时机
### 1. 主动引用
#### 1. 主动引用
虚拟机规范中并没有强制约束何时进行加载但是规范严格规定了有且只有下列五种情况必须对类进行初始化加载验证准备都会随之发生
@ -576,7 +557,7 @@ public static void main(String[] args) {
- 当使用 JDK 1.7 的动态语言支持时如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄并且这个方法句柄所对应的类没有进行过初始化则需要先触发其初始化
### 2. 被动引用
#### 2. 被动引用
以上 5 种场景中的行为称为对一个类进行主动引用除此之外所有引用类的方式都不会触发初始化称为被动引用被动引用的常见例子包括
@ -598,13 +579,13 @@ SuperClass[] sca = new SuperClass[10];
System.out.println(ConstClass.HELLOWORLD);
```
## 类与类加载器
### 类与类加载器
两个类相等需要类本身相等并且使用同一个类加载器进行加载这是因为每一个类加载器都拥有一个独立的类名称空间
这里的相等包括类的 Class 对象的 equals() 方法isAssignableFrom() 方法isInstance() 方法的返回结果为 true也包括使用 instanceof 关键字做对象所属关系判定结果为 true
## 类加载器分类
### 类加载器分类
Java 虚拟机的角度来讲只存在以下两种不同的类加载器
@ -614,13 +595,13 @@ System.out.println(ConstClass.HELLOWORLD);
Java 开发人员的角度看类加载器可以划分得更细致一些
- 启动类加载器Bootstrap ClassLoader此类加载器负责将存放在 &lt;JRE_HOME>\lib 目录中的或者被 -Xbootclasspath 参数所指定的路径中的并且是虚拟机识别的仅按照文件名识别 rt.jar名字不符合的类库即使放在 lib 目录中也不会被加载类库加载到虚拟机内存中启动类加载器无法被 Java 程序直接引用用户在编写自定义类加载器时如果需要把加载请求委派给启动类加载器直接使用 null 代替即可
- 启动类加载器Bootstrap ClassLoader此类加载器负责将存放在 &lt;JRE_HOME\>\lib 目录中的或者被 -Xbootclasspath 参数所指定的路径中的并且是虚拟机识别的仅按照文件名识别 rt.jar名字不符合的类库即使放在 lib 目录中也不会被加载类库加载到虚拟机内存中启动类加载器无法被 Java 程序直接引用用户在编写自定义类加载器时如果需要把加载请求委派给启动类加载器直接使用 null 代替即可
- 扩展类加载器Extension ClassLoader这个类加载器是由 ExtClassLoadersun.misc.Launcher$ExtClassLoader实现的它负责将 &lt;JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中开发者可以直接使用扩展类加载器
- 扩展类加载器Extension ClassLoader这个类加载器是由 ExtClassLoadersun.misc.Launcher$ExtClassLoader实现的它负责将 &lt;JAVA_HOME\>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中开发者可以直接使用扩展类加载器
- 应用程序类加载器Application ClassLoader这个类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader实现的由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值因此一般称为系统类加载器它负责加载用户类路径ClassPath上所指定的类库开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器
## 双亲委派模型
### 双亲委派模型
应用程序是由三种类加载器互相配合从而实现类加载除此之外还可以加入自己定义的类加载器
@ -628,17 +609,17 @@ System.out.println(ConstClass.HELLOWORLD);
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/0dd2d40a-5b2b-4d45-b176-e75a4cd4bdbf.png" width="500px"> </div><br>
### 1. 工作过程
#### 1. 工作过程
一个类加载器首先将类加载请求转发到父类加载器只有当父类加载器无法完成时才尝试自己加载
### 2. 好处
#### 2. 好处
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系从而使得基础类得到统一
例如 java.lang.Object 存放在 rt.jar 如果编写另外一个 java.lang.Object 并放到 ClassPath 程序可以编译通过由于双亲委派模型的存在所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高这是因为 rt.jar 中的 Object 使用的是启动类加载器 ClassPath 中的 Object 使用的是应用程序类加载器rt.jar 中的 Object 优先级更高那么程序中所有的 Object 都是这个 Object
### 3. 实现
#### 3. 实现
以下是抽象类 java.lang.ClassLoader 的代码片段其中的 loadClass() 方法运行过程如下先检查类是否已经加载过如果没有则让父类加载器去加载当父类加载器加载失败时抛出 ClassNotFoundException此时尝试自己去加载
@ -686,7 +667,7 @@ public abstract class ClassLoader {
}
```
## 自定义类加载器实现
### 自定义类加载器实现
以下代码中的 FileSystemClassLoader 是自定义类加载器继承自 java.lang.ClassLoader用于加载文件系统上的类它首先根据类的全名在文件系统上查找类的字节代码文件.class 文件然后读取该文件内容最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例
@ -735,7 +716,7 @@ public class FileSystemClassLoader extends ClassLoader {
}
```
# 参考资料
## 参考资料
- 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
- [Chapter 2. The Structure of the Java Virtual Machine](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4)
@ -751,10 +732,3 @@ public class FileSystemClassLoader extends ClassLoader {
- [深入探讨 Java 类加载器](https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html#code6)
- [Guide to WeakHashMap in Java](http://www.baeldung.com/java-weakhashmap)
- [Tomcat example source code file (ConcurrentCache.java)](https://alvinalexander.com/java/jwarehouse/apache-tomcat-6.0.16/java/org/apache/el/util/ConcurrentCache.java.shtml)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>