添加JVM前两章

This commit is contained in:
Dragon
2020-11-03 00:53:04 +08:00
parent b7cdb78d70
commit 1ccdbf79bf
3 changed files with 1396 additions and 1 deletions

View File

@@ -89,7 +89,11 @@ AQS剩余部门阻塞队列源码还在准备中。预计12月前可以写的
## JVM ## JVM
【TODO】 1、[JVM系列-第1章-JVM与Java体系结构](docs/Java/JVM/JVM系列-第1章-JVM与Java体系结构.md)
2、[JVM系列-第2章-类加载子系统](docs/Java/JVM/JVM系列-第2章-类加载子系统.md)
## 各版本新特性 ## 各版本新特性

View File

@@ -0,0 +1,574 @@
---
title: JVM系列-第1章-JVM与Java体系结构
tags:
- JVM
- 虚拟机
categories:
- JVM
keywords: JVM虚拟机。
description: JVM系列-第1章-JVM与Java体系结构。
cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/logo.png'
top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg'
abbrlink: 8c954c6
date: 2020-11-02 11:51:56
---
> 1、本系列博客主要是面向Java8的虚拟机。如有特殊说明会进行标注。
>
> 2、本系列博客主要参考**尚硅谷的JVM视频教程**,整理不易,所以图片打上了一些水印,还请读者见谅。后续可能会加上一些补充的东西。
>
> 3、尚硅谷的有些视频还不错PS不是广告毕竟看了人家比较好的教程得给人家打个call
>
> 4、转载请注明出处多谢~,希望大家一起能维护一个良好的开源环境。
第1章-JVM和Java体系架构
=====================
前言
--------
你是否也遇到过这些问题?
1. 运行着的线上系统突然卡死系统无法访问甚至直接OOM
2. 想解决线上JVM GC问题但却无从下手。
3. 新项目上线对各种JVM参数设置一脸茫然直接默认吧然后就JJ了。
4. 每次面试之前都要重新背一遍JVM的一些原理概念性的东西然而面试官却经常问你在实际项目中如何调优VM参数如何解决GC、OOM等问题一脸懵逼。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0001.png">
大部分Java开发人员除了会在项目中使用到与Java平台相关的各种高精尖技术对于Java技术的核心Java虚拟机了解甚少。
开发人员如何看待上层框架
---------
1. 一些有一定工作经验的开发人员打心眼儿里觉得SSM、微服务等上层技术才是重点基础技术并不重要这其实是一种本末倒置的“病态”。
2. 如果我们把核心类库的API比做数学公式的话那么Java虚拟机的知识就好比公式的推导过程。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0002.png">
- 计算机系统体系对我们来说越来越远,在不了解底层实现方式的前提下,通过高级语言很容易编写程序代码。但事实上计算机并不认识高级语言。
架构师每天都在思考什么?
---------
1. 应该如何让我的系统更快?
2. 如何避免系统出现瓶颈?
**知乎上有条帖子应该如何看招聘信息直通年薪50万+**
1. 参与现有系统的性能优化,重构,保证平台性能和稳定性
2. 根据业务场景和需求,决定技术方向,做技术选型
3. 能够独立架构和设计海量数据下高并发分布式解决方案,满足功能和非功能需求
4. 解决各类潜在系统风险,核心功能的架构与代码编写
5. 分析系统瓶颈,解决各种疑难杂症,性能调优等
我们为什么要学习JVM
-----------
1. 面试的需要BATJ、TMDPKQ等面试都爱问
2. 中高级程序员必备技能
- 项目管理、调优的需要
3. 追求极客的精神,
- 比如垃圾回收算法、JIT、底层原理
Java VS C++
-------------
1. 垃圾收集机制为我们打理了很多繁琐的工作大大提高了开发的效率但是垃圾收集也不是万能的懂得JVM内部的内存结构、工作机制是设计高扩展性应用和诊断运行时问题的基础也是Java工程师进阶的必备能力。
2. C++语言需要程序员自己来分配内存和回收内存对于高手来说可能更加舒服但是对于普通开发者如果技术实力不够很容易造成内存泄漏。而Java全部交给JVM进行内存分配和回收这也是一种趋势减少程序员的工作量。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0003.png">
## 什么人需要学JVM
1. 拥有一定开发经验的Java开发人员希望升职加薪
2. 软件设计师,架构师
3. 系统调优人员
4. 虚拟机爱好者JVM实践者
推荐及参考书籍
------
**官方文档**
**英文文档规范**https://docs.oracle.com/javase/specs/index.html
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0004.png">
**中文书籍:**
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0005.png">
> 周志明老师的这本书**非常推荐看**,不过只推荐看第三版,第三版较第二版更新了很多,个人觉得没必要再看第二版。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0006.png">
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0007.png">
TIOBE排行榜
-----------
**TIOBE 排行榜**https://www.tiobe.com/tiobe-index/
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0008.png">
- 世界上没有最好的编程语言,只有最适用于具体应用场景的编程语言。
- 目前网上一直流传Java被pythongo撼动Java第一的地位。学习者不需要太担心Java强大的生态圈也不是说是朝夕之间可以被撼动的。
Java生态圈
----------
Java是目前应用最为广泛的软件开发平台之一。随着Java以及Java社区的不断壮大Java 也早已不再是简简单单的一门计算机语言了,它更是一个平台、一种文化、一个社区。
1. 作为一个平台Java虚拟机扮演着举足轻重的作用
* Groovy、Scala、JRuby、Kotlin等都是Java平台的一部分
2. 作为一种文化Java几乎成为了“开源”的代名词。
* 第三方开源软件和框架。如Tomcat、StrutsMyBatisSpring等。
* 就连JDK和JVM自身也有不少开源的实现如openJDK、Harmony。
3. 作为一个社区Java拥有全世界最多的技术拥护者和开源社区支持有数不清的论坛和资料。从桌面应用软件、嵌入式开发到企业级应用、后台服务器、中间件都可以看到Java的身影。其应用形式之复杂、参与人数之众多也令人咋舌。
Java-跨平台的语言
------------
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0009.png">
JVM-跨语言的平台
------
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0010.png">
1. 随着Java7的正式发布Java虚拟机的设计者们通过JSR-292规范基本实现在Java虚拟机平台上运行非Java语言编写的程序。
2. Java虚拟机根本不关心运行在其内部的程序到底是使用何种编程语言编写的它只关心“字节码”文件。也就是说Java虚拟机拥有语言无关性并不会单纯地与Java语言“终身绑定”只要其他编程语言的编译结果满足并包含Java虚拟机的内部指令集、符号表以及其他的辅助信息它就是一个有效的字节码文件就能够被虚拟机所识别并装载运行。
- Java不是最强大的语言但是JVM是最强大的虚拟机
1. 我们平时说的java字节码指的是用java语言编译成的字节码。准确的说任何能在jvm平台上执行的字节码格式都是一样的。所以应该统称为jvm字节码。
2. 不同的编译器可以编译出相同的字节码文件字节码文件也可以在不同的JVM上运行。
3. Java虚拟机与Java语言并没有必然的联系它只与特定的二进制文件格式——Class文件格式所关联Class文件中包含了Java虚拟机指令集或者称为字节码、Bytecodes和符号表还有一些其他辅助信息。
多语言混合编程
----------
1. Java平台上的多语言混合编程正成为主流通过特定领域的语言去解决特定领域的问题是当前软件开发应对日趋复杂的项目需求的一个方向。
2. 试想一下在一个项目之中并行处理用Clojure语言编写展示层使用JRuby/Rails中间层则是Java每个应用层都将使用不同的编程语言来完成而且接口对每一层的开发者都是透明的各种语言之间的交互不存在任何困难就像使用自己语言的原生API一样方便因为它们最终都运行在一个虚拟机之上。
3. 对这些运行于Java虚拟机之上、Java之外的语言来自系统级的、底层的支持正在迅速增强以JSR-292为核心的一系列项目和功能改进如DaVinci Machine项目、Nashorn引擎、InvokeDynamic指令、java.lang.invoke包等推动Java虚拟机从“Java语言的虚拟机”向 “多语言虚拟机”的方向发展。
如何真正搞懂JVM
-----------
1. Java虚拟机非常复杂要想真正理解它的工作原理最好的方式就是自己动手编写一个
2. 自己动手写一个Java虚拟机难吗
3. 天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0011.png">
Java发展重大事件
------------
* 1990年在Sun计算机公司中由Patrick Naughton、MikeSheridan及James Gosling领导的小组Green Team开发出的新的程序语言命名为Oak后期命名为Java
* 1995年Sun正式发布Java和HotJava产品Java首次公开亮相。
* 1996年1月23日Sun Microsystems发布了JDK 1.0。
* 1998年JDK1.2版本发布。同时Sun发布了JSP/Servlet、EJB规范以及将Java分成了J2EE、J2SE和J2ME。这表明了Java开始向企业、桌面应用和移动设备应用3大领域挺进。
* 2000年JDK1.3发布Java HotSpot Virtual Machine正式发布成为Java的默认虚拟机。
* 2002年JDK1.4发布古老的Classic虚拟机退出历史舞台。
* 2003年年底Java平台的scala正式发布同年Groovy也加入了Java阵营。
* 2004年JDK1.5发布。同时JDK1.5改名为JavaSE5.0。
* 2006年JDK6发布。同年Java开源并建立了OpenJDK。顺理成章Hotspot虚拟机也成为了OpenJDK中的默认虚拟机。
* 2007年Java平台迎来了新伙伴Clojure。
* 2008年oracle收购了BEA得到了JRockit虚拟机。
* 2009年Twitter宣布把后台大部分程序从Ruby迁移到Scala这是Java平台的又一次大规模应用。
* 2010年Oracle收购了Sun获得Java商标和最真价值的HotSpot虚拟机。此时Oracle拥有市场占用率最高的两款虚拟机HotSpot和JRockit并计划在未来对它们进行整合HotRockit。JCP组织管理Java语言
* 2011年JDK7发布。在JDK1.7u4中正式启用了新的垃圾回收器G1。
* **2017年JDK9发布。将G1设置为默认GC替代CMS**
* 同年IBM的J9开源形成了现在的Open J9社区
* 2018年Android的Java侵权案判决Google赔偿Oracle计88亿美元
* 同年Oracle宣告JavagE成为历史名词JDBC、JMS、Servlet赠予Eclipse基金会
* **同年JDK11发布LTS版本的JDK发布革命性的ZGC调整JDK授权许可**
* 2019年JDK12发布加入RedHat领导开发的Shenandoah GC
## Open JDK和Oracle JDK
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0012.png">
- 在JDK11之前Oracle JDK中还会存在一些Open JDK中没有的闭源的功能。但在JDK11中我们可以认为Open JDK和Oracle JDK代码实质上已经达到完全一致的程度了。
- 主要的区别就是两者更新周期不一样
虚拟机
--------
### 虚拟机概念
- 所谓虚拟机Virtual Machine就是一台虚拟的计算机。它是一款软件用来执行一系列虚拟计算机指令。大体上虚拟机可以分为系统虚拟机和程序虚拟机。
- 大名鼎鼎的Virtual BoxVMware就属于系统虚拟机它们完全是对物理计算机硬件的仿真(模拟),提供了一个可运行完整操作系统的软件平台。
+ 程序虚拟机的典型代表就是Java虚拟机它专门为执行单个计算机程序而设计在Java虚拟机中执行的指令我们称为Java字节码指令。
- 无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。
### Java虚拟机
1. Java虚拟机是一台执行Java字节码的虚拟计算机它拥有独立的运行机制其运行的Java字节码也未必由Java语言编译而成。
2. JVM平台的各种语言可以共享Java虚拟机带来的跨平台性、优秀的垃圾回器以及可靠的即时编译器。
3. **Java技术的核心就是Java虚拟机**JVMJava Virtual Machine因为所有的Java程序都运行在Java虚拟机内部。
**作用:**
Java虚拟机就是二进制字节码的运行环境负责装载字节码到其内部解释/编译为对应平台上的机器指令执行。每一条Java指令Java虚拟机规范中都有详细定义如怎么取操作数怎么处理操作数处理结果放在哪里。
**特点:**
1. 一次编译,到处运行
2. 自动内存管理
3. 自动垃圾回收功能
JVM的位置
----------
JVM是运行在操作系统之上的它与硬件没有直接的交互
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0013.png">
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0014.png">
JVM的整体结构
-------------
1. HotSpot VM是目前市面上高性能虚拟机的代表作之一。
2. 它采用解释器与即时编译器并存的架构。
3. 在今天Java程序的运行性能早已脱胎换骨已经达到了可以和C/C++程序一较高下的地步。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0015.png">
Java代码执行流程
--------------
凡是能生成被Java虚拟机所能解释、运行的字节码文件那么理论上我们就可以自己设计一套语言了
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_001/0016.png">
JVM的架构模型
-----------
Java编译器输入的指令流基本上是一种**基于栈的指令集架构**,另外一种指令集架构则是**基于寄存器的指令集架构**。具体来说:这两种架构之间的区别:
### 基于栈的指令集架构
基于栈式架构的特点:
1. 设计和实现更简单,适用于资源受限的系统;
2. 避开了寄存器的分配难题:使用零地址指令方式分配
3. 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器容易实现
4. 不需要硬件支持,可移植性更好,更好实现跨平台
### 基于寄存器的指令级架构
基于寄存器架构的特点:
1. 典型的应用是x86的二进制指令集比如传统的PC以及Android的Davlik虚拟机。
2. 指令集架构则完全依赖硬件,与硬件的耦合度高,可移植性差
3. 性能优秀和执行更高效
4. 花费更少的指令去完成一项操作
5. 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主
### 两种架构的举例
同样执行2+3这种逻辑操作其指令分别如下
* **基于栈的计算流程以Java虚拟机为例**
```java
iconst_2 //常量2入栈
istore_1
iconst_3 // 常量3入栈
istore_2
iload_1
iload_2
iadd //常量2/3出栈执行相加
istore_0 // 结果5入栈
```
8个指令
* **而基于寄存器的计算流程**
```java
mov eax,2 //将eax寄存器的值设为1
add eax,3 //使eax寄存器的值加3
```
2个指令
> 具体后面会讲
### JVM架构总结
1. **由于跨平台性的设计Java的指令都是根据栈来设计的**。不同平台CPU架构不同所以不能设计为基于寄存器的。栈的优点跨平台指令集小编译器容易实现缺点是性能比寄存器差一些。
2. 时至今日尽管嵌入式平台已经不是Java程序的主流运行平台了准确来说应该是HotSpot VM的宿主环境已经不局限于嵌入式平台了那么为什么不将架构更换为基于寄存器的架构呢
- 因为基于栈的架构跨平台性好、指令集小,虽然相对于基于寄存器的架构来说,基于栈的架构编译得到的指令更多,执行性能也不如基于寄存器的架构好,但考虑到其跨平台性与移植性,我们还是选用栈的架构
JVM的生命周期
-----------
### 虚拟机的启动
Java虚拟机的启动是通过引导类加载器bootstrap class loader创建一个初始类initial class来完成的这个类是由虚拟机的具体实现指定的。
### 虚拟机的执行
1. 一个运行中的Java虚拟机有着一个清晰的任务执行Java程序
2. 程序开始执行时他才运行,程序结束时他就停止
3. **执行一个所谓的Java程序的时候真真正正在执行的是一个叫做Java虚拟机的进程**
### 虚拟机的退出
**有如下的几种情况:**
1. 程序正常执行结束
2. 程序在执行过程中遇到了异常或错误而异常终止
3. 由于操作系统用现错误而导致Java虚拟机进程终止
4. 某线程调用Runtime类或System类的exit()方法或Runtime类的halt()方法并且Java安全管理器也允许这次exit()或halt()操作。
5. 除此之外JNIJava Native Interface规范描述了用JNI Invocation API来加载或卸载 Java虚拟机时Java虚拟机的退出情况。
JVM发展历程
-----------
### Sun Classic VM
1. 早在1996年Java1.0版本的时候Sun公司发布了一款名为sun classic VM的Java虚拟机它同时也是**世界上第一款商用Java虚拟机**JDK1.4时完全被淘汰。
2. 这款虚拟机内部只提供解释器,没有即时编译器,因此效率比较低。【即时编译器会把热点代码的本地机器指令缓存起来,那么以后使用热点代码的时候,效率就比较高】
3. 如果使用JIT编译器就需要进行外挂。但是一旦使用了JIT编译器JIT就会接管虚拟机的执行系统。解释器就不再工作解释器和编译器不能配合工作。
- 我们将字节码指令翻译成机器指令也是需要花时间的如果只使用JIT就需要把所有字节码指令都翻译成机器指令就会导致翻译时间过长也就是说在程序刚启动的时候等待时间会很长。
- 而解释器就是走到哪,解释到哪。
4. 现在Hotspot内置了此虚拟机。
### Exact VM
1. 为了解决上一个虚拟机问题jdk1.2时Sun提供了此虚拟机。
2. Exact Memory Management准确式内存管理
* 也可以叫Non-Conservative/Accurate Memory Management
* 虚拟机可以知道内存中某个位置的数据具体是什么类型。
3. 具备现代高性能虚拟机的维形
* 热点探测(寻找出热点代码进行缓存)
* 编译器与解释器混合工作模式
4. 只在Solaris平台短暂使用其他平台上还是classic vm英雄气短终被Hotspot虚拟机替换
### HotSpot VM重点
1. HotSpot历史
* 最初由一家名为“Longview Technologies”的小公司设计
* 1997年此公司被Sun收购2009年Sun公司被甲骨文收购。
* JDK1.3时HotSpot VM成为默认虚拟机
2. 目前**Hotspot占有绝对的市场地位称霸武林**。
* 不管是现在仍在广泛使用的JDK6还是使用比例较多的JDK8中默认的虚拟机都是HotSpot
* Sun/oracle JDK和openJDK的默认虚拟机
* 因此本课程中默认介绍的虚拟机都是HotSpot相关机制也主要是指HotSpot的GC机制。比如其他两个商用虚机都没有方法区的概念
3. 从服务器、桌面到移动端、嵌入式都有应用。
4. 名称中的HotSpot指的就是它的热点代码探测技术。
* 通过计数器找到最具编译价值代码,触发即时编译或栈上替换
* 通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡
### JRockit商用三大虚拟机之一
1. 专注于服务器端应用它可以不太关注程序启动速度因此JRockit内部不包含解析器实现全部代码都靠即时编译器编译后执行。
2. 大量的行业基准测试显示JRockit JVM是世界上最快的JVM使用JRockit产品客户已经体验到了显著的性能提高一些超过了70%和硬件成本的减少达50%)。
3. 优势全面的Java运行时解决方案组合
* JRockit面向延迟敏感型应用的解决方案JRockit Real Time提供以毫秒或微秒级的JVM响应时间适合财务、军事指挥、电信网络的需要
* Mission Control服务套件它是一组以极低的开销来监控、管理和分析生产环境中的应用程序的工具。
4. 2008年JRockit被Oracle收购。
5. Oracle表达了整合两大优秀虚拟机的工作大致在JDK8中完成。整合的方式是在HotSpot的基础上移植JRockit的优秀特性。
6. 高斯林:目前就职于谷歌,研究人工智能和水下机器人
### IBM的J9商用三大虚拟机之一
1. 全称IBM Technology for Java Virtual Machine简称IT4J内部代号J9
2. 市场定位与HotSpot接近服务器端、桌面应用、嵌入式等多用途VM广泛用于IBM的各种Java产品。
3. 目前有影响力的三大商用虚拟机之一也号称是世界上最快的Java虚拟机。
4. 2017年左右IBM发布了开源J9VM命名为openJ9交给Eclipse基金会管理也称为Eclipse OpenJ9
5. OpenJDK -> 是JDK开源了包括了虚拟机
### KVM和CDC/CLDC Hotspot
1. Oracle在Java ME产品线上的两款虚拟机为CDC/CLDC HotSpot Implementation VM
2. KVMKilobyte是CLDC-HI早期产品
3. 目前移动领域地位尴尬智能机被Android和iOS二分天下。
4. KVM简单、轻量、高度可移植面向更低端的设备上还维持自己的一片市场
* 智能控制器、传感器
* 老人手机、经济欠发达地区的功能手机
5. 所有的虚拟机的原则:一次编译,到处运行。
### Azul VM
1. 前面三大“高性能Java虚拟机”使用在**通用硬件平台上**
2. 这里Azul VW和BEA Liquid VM是与**特定硬件平台绑定**、软硬件配合的专有虚拟机高性能Java虚拟机中的战斗机。
3. Azul VM是Azul Systems公司在HotSpot基础上进行大量改进运行于Azul Systems公司的专有硬件Vega系统上的Java虚拟机。
4. 每个Azul VM实例都可以管理至少数十个CPU和数百GB内存的硬件资源并提供在巨大内存范围内实现可控的GC时间的垃圾收集器、专有硬件优化的线程调度等优秀特性。
5. 2010年Azul Systems公司开始从硬件转向软件发布了自己的Zing JVM可以在通用x86平台上提供接近于Vega系统的特性。
### Liquid VM
1. 高性能Java虚拟机中的战斗机。
2. BEA公司开发的直接运行在自家Hypervisor系统上
3. Liquid VM即是现在的JRockit VEVirtual Edition。**Liquid VM不需要操作系统的支持或者说它自己本身实现了一个专用操作系统的必要功能如线程调度、文件系统、网络支持等**。
5. 随着JRockit虚拟机终止开发Liquid vM项目也停止了。
### Apache Marmony
1. Apache也曾经推出过与JDK1.5和JDK1.6兼容的Java运行平台Apache Harmony。
2. 它是IElf和Intel联合开发的开源JVM受到同样开源的Open JDK的压制Sun坚决不让Harmony获得JCP认证最终于2011年退役IBM转而参与OpenJDK
3. 虽然目前并没有Apache Harmony被大规模商用的案例但是它的Java类库代码吸纳进了Android SDK。
### Micorsoft JVM
1. 微软为了在IE3浏览器中支持Java Applets开发了Microsoft JVM。
2. 只能在window平台下运行。但确是当时Windows下性能最好的Java VM。
3. 1997年Sun以侵犯商标、不正当竞争罪名指控微软成功赔了Sun很多钱。微软WindowsXP SP3中抹掉了其VM。现在Windows上安装的jdk都是HotSpot。
### Taobao JVM
1. 由AliJVM团队发布。阿里国内使用Java最强大的公司覆盖云计算、金融、物流、电商等众多领域需要解决高并发、高可用、分布式的复合问题。有大量的开源产品。
2. **基于OpenJDK开发了自己的定制版本AlibabaJDK**简称AJDK。是整个阿里Java体系的基石。
3. 基于OpenJDK Hotspot VM发布的国内第一个优化、深度定制且开源的高性能服务器版Java虚拟机。
* 创新的GCIHGCinvisible heap技术实现了off-heap**即将生命周期较长的Java对象从heap中移到heap之外并且GC不能管理GCIH内部的Java对象以此达到降低GC的回收频率和提升GC的回收效率的目的**。
* GCIH中的**对象还能够在多个Java虚拟机进程中实现共享**
* 使用crc32指令实现JvM intrinsic降低JNI的调用开销
* PMU hardware的Java profiling tool和诊断协助功能
* 针对大数据场景的ZenGC
4. taobao vm应用在阿里产品上性能高**硬件严重依赖inte1的cpu损失了兼容性但提高了性能**
- 目前已经在淘宝、天猫上线把Oracle官方JvM版本全部替换了。
### Dalvik VM
1. 谷歌开发的应用于Android系统并在Android2.2中提供了JIT发展迅猛。
2. **Dalvik VM只能称作虚拟机而不能称作“Java虚拟机”**,它没有遵循 Java虚拟机规范
3. 不能直接执行Java的Class文件
4. 基于寄存器架构不是jvm的栈架构。
5. 执行的是编译以后的dexDalvik Executable文件。执行效率比较高。
- 它执行的dexDalvik Executable文件可以通过class文件转化而来使用Java语法编写应用程序可以直接使用大部分的Java API等。
7. Android 5.0使用支持提前编译Ahead of Time CompilationAoT的ART VM替换Dalvik VM。
### Graal VM未来虚拟机
1. 2018年4月Oracle Labs公开了GraalvM号称 “**Run Programs Faster Anywhere**”勃勃野心。与1995年java的”write oncerun anywhere"遥相呼应。
2. GraalVM在HotSpot VM基础上增强而成的**跨语言全栈虚拟机,可以作为“任何语言”**的运行平台使用。语言包括Java、Scala、Groovy、KotlinC、C++、Javascript、Ruby、Python、R等
3. 支持不同语言中混用对方的接口和对象,支持这些语言使用已经编写好的本地库文件
4. 工作原理是将这些语言的源代码或源代码编译后的中间格式通过解释器转换为能被Graal VM接受的中间表示。Graal VM提供Truffle工具集快速构建面向一种新语言的解释器。在运行时还能进行即时编译优化获得比原生编译器更优秀的执行效率。
5. **如果说HotSpot有一天真的被取代Graalvm希望最大**。但是Java的软件生态没有丝毫变化。
### 总结
具体JVM的内存结构其实取决于其实现不同厂商的JVM或者同一厂商发布的不同版本都有可能存在一定差异。主要以Oracle HotSpot VM为默认虚拟机。

View File

@@ -0,0 +1,817 @@
---
title: JVM系列-第2章-类加载子系统
tags:
- JVM
- 虚拟机
categories:
- JVM
keywords: JVM虚拟机。
description: JVM系列-第2章-类加载子系统。
cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/logo.png'
top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg'
abbrlink: 2e0079af
date: 2020-11-02 21:31:58
---
第2章-类加载子系统
============
内存结构概述
--------
### 简图
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0001.png">
### 详细图
英文版
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0002.jpg">
中文版
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0003.jpg">
注意方法区只有HotSpot虚拟机有J9JRockit都没有
如果自己想手写一个Java虚拟机的话主要考虑哪些结构呢
1. 类加载器
2. 执行引擎
类加载器子系统
--------
**类加载器子系统作用:**
1. 类加载器子系统负责从文件系统或者网络中加载Class文件class文件在文件开头有特定的文件标识。
2. ClassLoader只负责class文件的加载至于它是否可以运行则由Execution Engine决定。
3. **加载的类信息存放于一块称为方法区的内存空间**。除了类的信息外方法区中还会存放运行时常量池信息可能还包括字符串字面量和数字常量这部分常量信息是Class文件中常量池部分的内存映射
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0004.png">
## 类加载器ClassLoader角色
1. class file在下图中就是Car.class文件存在于本地硬盘上可以理解为设计师画在纸上的模板而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出n个一模一样的实例。
2. class file加载到JVM中被称为DNA元数据模板在下图中就是内存中的Car Class放在方法区。
3. 在.class文件>JVM>最终成为元数据模板此过程就要一个运输工具类装载器Class Loader扮演一个快递员的角色。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0005.png">
类加载过程
-------
### 概述
```java
public class HelloLoader {
public static void main(String[] args) {
System.out.println("谢谢ClassLoader加载我....");
System.out.println("你的大恩大德,我下辈子再报!");
}
}
```
它的加载过程是怎么样的呢?
* 执行 main() 方法静态方法就需要先加载main方法所在类 HelloLoader
* 加载成功,则进行链接、初始化等操作。完成后调用 HelloLoader 类中的静态方法 main
* 加载失败则抛出异常
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0006.png">
完整的流程图如下所示:
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0007.png">
### 加载阶段
**加载:**
1. 通过一个类的全限定名获取定义此类的二进制字节流
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3. **在内存中生成一个代表这个类的java.lang.Class对象**,作为方法区这个类的各种数据的访问入口
**加载class文件的方式**
1. 从本地系统中直接加载
2. 通过网络获取典型场景Web Applet
3. 从zip压缩包中读取成为日后jar、war格式的基础
4. 运行时计算生成,使用最多的是:动态代理技术
5. 由其他文件生成典型场景JSP应用从专有数据库中提取.class文件比较少见
6. 从加密文件中获取典型的防Class文件被反编译的保护措施
### 链接阶段
链接分为三个子阶段:验证 -> 准备 -> 解析
#### 验证(Verify)
1. 目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求保证被加载类的正确性不会危害虚拟机自身安全
2. 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
**举例**
使用 BinaryViewer软件查看字节码文件其开头均为 CAFE BABE ,如果出现不合法的字节码文件,那么将会验证不通过。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0008.png">
#### 准备(Prepare)
1. 为类变量static变量分配内存并且设置该类变量的默认初始值即零值
2. 这里不包含用final修饰的static因为final在编译的时候就会分配好了默认值准备阶段会显式初始化
3. 注意这里不会为实例变量分配初始化类变量会分配在方法区中而实例变量是会随着对象一起分配到Java堆中
**举例**
代码变量a在准备阶段会赋初始值但不是1而是0在初始化阶段会被赋值为 1
```java
public class HelloApp {
private static int a = 1;//preparea = 0 ---> initial : a = 1
public static void main(String[] args) {
System.out.println(a);
}
}
```
#### 解析(Resolve)
1. **将常量池内的符号引用转换为直接引用的过程**
2. 事实上解析操作往往会伴随着JVM在执行完初始化之后再执行
3. 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
4. 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT Class info、CONSTANT Fieldref info、CONSTANT Methodref info等
**符号引用**
* 反编译 class 文件后可以查看符号引用
![image-20200727155223643](image/aHR0cDovL2hleWdvLm9zcy1jbi1zaGFuZ2hhaS5hbGl5dW5jcy5jb20vaW1hZ2VzL2ltYWdlLTIwMjAwNzI3MTU1MjIzNjQzLnBuZw)
### 初始化阶段
#### 类的初始化时机
1. 创建类的实例
2. 访问某个类或接口的静态变量,或者对该静态变量赋值
3. 调用类的静态方法
4. 反射比如Class.forName(“com.atguigu.Test”)
5. 初始化一个类的子类
6. Java虚拟机启动时被标明为启动类的类
7. JDK7开始提供的动态语言支持java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF putStatic、REF_invokeStatic句柄对应的类没有初始化则初始化
除了以上七种情况其他使用Java类的方式都被看作是对类的被动使用都不会导致类的初始化即不会执行初始化阶段不会调用 clinit() 方法和 init() 方法)
### clinit()
1. 初始化阶段就是执行类构造器方法`<clinit>()`的过程
2. 此方法不需定义是javac编译器自动收集类中的所有**类变量**的赋值动作和静态代码块中的语句合并而来。也就是说当我们代码中包含static变量的时候就会有clinit方法
3. `<clinit>()`方法中的指令按语句在源文件中出现的顺序执行
4. `<clinit>()`不同于类的构造器。(关联:构造器是虚拟机视角下的`<init>()`
5. 若该类具有父类JVM会保证子类的`<clinit>()`执行前,父类的`<clinit>()`已经执行完毕
6. 虚拟机必须保证一个类的`<clinit>()`方法在多线程下被同步加锁
> IDEA 中安装 JClassLib Bytecode viewer 插件,可以很方便的看字节码。安装过程可以自行百度
#### 123说明
**举例1有static变量**
查看下面这个代码的字节码,可以发现有一个`<clinit>()`方法。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0009.png">
```java
public class ClassInitTest {
private static int num = 1;
static{
num = 2;
number = 20;
System.out.println(num);
//System.out.println(number);//报错:非法的前向引用。
}
/**
* 1、linking之prepare: number = 0 --> initial: 20 --> 10
* 2、这里因为静态代码块出现在声明变量语句前面所以之前被准备阶段为0的number变量会
* 首先被初始化为20再接着被初始化成10这也是面试时常考的问题哦
*
*/
private static int number = 10;
public static void main(String[] args) {
System.out.println(ClassInitTest.num);//2
System.out.println(ClassInitTest.number);//10
}
}
```
<clint字节码>
```java
0 iconst_1
1 putstatic #3 <com/atguigu/java/ClassInitTest.num>
4 iconst_2
5 putstatic #3 <com/atguigu/java/ClassInitTest.num>
8 bipush 20 //先赋20
10 putstatic #5 <com/atguigu/java/ClassInitTest.number>
13 getstatic #2 <java/lang/System.out>
16 getstatic #3 <com/atguigu/java/ClassInitTest.num>
19 invokevirtual #4 <java/io/PrintStream.println>
22 bipush 10 //再赋10
24 putstatic #5 <com/atguigu/java/ClassInitTest.number>
27 return
```
当我们代码中包含static变量的时候就会有clinit方法
**举例2无 static 变量**
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0010.png">
加上之后就有了
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0011.png">
#### 4说明
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0012.png">
在构造器中:
* 先将类变量 a 赋值为 10
* 再将局部变量赋值为 20
#### 5说明
若该类具有父类JVM会保证子类的`<clinit>()`执行前,父类的`<clinit>()`已经执行完毕
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0013.png">
如上代码,加载流程如下:
* 首先,执行 main() 方法需要加载 ClinitTest1 类
* 获取 Son.B 静态变量,需要加载 Son 类
* Son 类的父类是 Father 类,所以需要先执行 Father 类的加载,再执行 Son 类的加载
#### 6说明
虚拟机必须保证一个类的`<clinit>()`方法在多线程下被同步加锁
```java
public class DeadThreadTest {
public static void main(String[] args) {
Runnable r = () -> {
System.out.println(Thread.currentThread().getName() + "开始");
DeadThread dead = new DeadThread();
System.out.println(Thread.currentThread().getName() + "结束");
};
Thread t1 = new Thread(r,"线程1");
Thread t2 = new Thread(r,"线程2");
t1.start();
t2.start();
}
}
class DeadThread{
static{
if(true){
System.out.println(Thread.currentThread().getName() + "初始化当前类");
while(true){
}
}
}
}
```
输出结果:
```
线程2开始
线程1开始
线程2初始化当前类
/然后程序卡死了
```
程序卡死,分析原因:
* 两个线程同时去加载 DeadThread 类,而 DeadThread 类中静态代码块中有一处死循环
* 先加载 DeadThread 类的线程抢到了同步锁,然后在类的静态代码块中执行死循环,而另一个线程在等待同步锁的释放
* 所以无论哪个线程先执行 DeadThread 类的加载,另外一个类也不会继续执行。(一个类只会被加载一次)
类加载器的分类
---------
### 概述
1. JVM严格来讲支持两种类型的类加载器 。分别为引导类加载器Bootstrap ClassLoader和自定义类加载器User-Defined ClassLoader
2. 从概念上来讲自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器但是Java虚拟机规范却没有这么定义而是**将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器**
3. 无论类加载器的类型如何划分在程序中我们最常见的类加载器始终只有3个如下所示
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0014.png">
**ExtClassLoader**
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0015.png">
**AppClassLoader**
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0016.png">
```java
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取其上层:扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d
//获取其上层:获取不到引导类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null
//对于用户自定义类来说:默认使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null
}
}
```
* 我们尝试获取引导类加载器,获取到的值为 null ,这并不代表引导类加载器不存在,**因为引导类加载器右 C/C++ 语言,我们获取不到**
* 两次获取系统类加载器的值都相同sun.misc.Launcher$AppClassLoader@18b4aac2 ,这说明**系统类加载器是全局唯一的**
### 虚拟机自带的加载器
#### 启动类加载器
> **启动类加载器引导类加载器Bootstrap ClassLoader**
1. 这个类加载使用C/C++语言实现的嵌套在JVM内部
2. 它用来加载Java的核心库JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容用于提供JVM自身需要的类
3. 并不继承自java.lang.ClassLoader没有父加载器
4. 加载扩展类和应用程序类加载器,并作为他们的父类加载器
5. 出于安全考虑Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
#### 扩展类加载器
> **扩展类加载器Extension ClassLoader**
1. Java语言编写由sun.misc.Launcher$ExtClassLoader实现
2. 派生于ClassLoader类
3. 父类加载器为启动类加载器
4. 从java.ext.dirs系统属性所指定的目录中加载类库或从JDK的安装目录的jre/lib/ext子目录扩展目录下加载类库。如果用户创建的JAR放在此目录下也会自动由扩展类加载器加载
#### 系统类加载器
> **应用程序类加载器也称为系统类加载器AppClassLoader**
1. Java语言编写由sun.misc.LaunchersAppClassLoader实现
2. 派生于ClassLoader类
3. 父类加载器为扩展类加载器
4. 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
5. 该类加载是程序中默认的类加载器一般来说Java应用的类都是由它来完成加载
6. 通过classLoader.getSystemclassLoader()方法可以获取到该类加载器
```java
public class ClassLoaderTest1 {
public static void main(String[] args) {
System.out.println("**********启动类加载器**************");
//获取BootstrapClassLoader能够加载的api的路径
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL element : urLs) {
System.out.println(element.toExternalForm());
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader);
System.out.println("***********扩展类加载器*************");
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
System.out.println(path);
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
ClassLoader classLoader1 = CurveDB.class.getClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d
}
}
```
**输出结果**
```java
**********启动类加载器**************
file:/C:/Program%20Files/Java/jdk1.8.0_131/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_131/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_131/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_131/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_131/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_131/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_131/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_131/jre/classes
null
***********扩展类加载器*************
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
sun.misc.Launcher$ExtClassLoader@29453f44
```
### 用户自定义类加载器
#### 什么时候需要自定义类加载器?
在Java的日常应用程序开发中类的加载几乎是由上述3种类加载器相互配合执行的在必要时我们还可以自定义类加载器来定制类的加载方式。那为什么还需要自定义类加载器
1. 隔离加载类比如说我假设现在Spring框架和RocketMQ有包名路径完全一样的类类名也一样这个时候类就冲突了。不过一般的主流框架和中间件都会自定义类加载器实现不同的框架中间价之间是隔离的
2. 修改类加载的方式
3. 扩展加载源(还可以考虑从数据库中加载类,路由器等等不同的地方)
4. 防止源码泄漏(对字节码文件进行解密,自己用的时候通过自定义类加载器来对其进行解密)
#### 如何自定义类加载器?
1. 开发人员可以通过继承抽象类java.lang.ClassLoader类的方式实现自己的类加载器以满足一些特殊的需求
2. 在JDK1.2之前在自定义类加载器时总会去继承ClassLoader类并重写loadClass()方法从而实现自定义的类加载类但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法而是建议把自定义的类加载逻辑写在findclass()方法中
3. 在编写自定义类加载器时如果没有太过于复杂的需求可以直接继承URIClassLoader类这样就可以避免自己去编写findclass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。
**代码示例**
```java
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] result = getClassFromCustomPath(name);
if (result == null) {
throw new FileNotFoundException();
} else {
//defineClass和findClass搭配使用
return defineClass(name, result, 0, result.length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
throw new ClassNotFoundException(name);
}
//自定义流的获取方式
private byte[] getClassFromCustomPath(String name) {
//从自定义路径中加载指定类:细节略
//如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
return null;
}
public static void main(String[] args) {
CustomClassLoader customClassLoader = new CustomClassLoader();
try {
Class<?> clazz = Class.forName("One", true, customClassLoader);
Object obj = clazz.newInstance();
System.out.println(obj.getClass().getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
### 关于ClassLoader
> **ClassLoader 类介绍**
ClassLoader类它是一个抽象类其后所有的类加载器都继承自ClassLoader不包括启动类加载器
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0017.png">
sun.misc.Launcher 它是一个java虚拟机的入口应用
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0018.png">
#### 获取ClassLoader途径
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0019.png">
```java
public class ClassLoaderTest2 {
public static void main(String[] args) {
try {
//1.
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader);
//2.
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader1);
//3.
ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent();
System.out.println(classLoader2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
```
输出结果:
```
null
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
Process finished with exit code 0
```
双亲委派机制
--------
### 双亲委派机制原理
Java虚拟机对class文件采用的是**按需加载**的方式也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时Java虚拟机采用的是双亲委派模式即把请求交由父类处理它是一种任务委派模式
1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
3. 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
4. 父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0020.png">
### 双亲委派机制代码演示
#### 举例1
1、我们自己建立一个 java.lang.String 类,写上 static 代码块
```java
public class String {
//
static{
System.out.println("我是自定义的String类的静态代码块");
}
}
```
2、在另外的程序中加载 String 类,看看加载的 String 类是 JDK 自带的 String 类,还是我们自己编写的 String 类
```java
public class StringTest {
public static void main(String[] args) {
java.lang.String str = new java.lang.String();
System.out.println("hello,atguigu.com");
StringTest test = new StringTest();
System.out.println(test.getClass().getClassLoader());
}
}
```
输出结果:
```
hello,atguigu.com
sun.misc.Launcher$AppClassLoader@18b4aac2
```
程序并没有输出我们静态代码块中的内容,可见仍然加载的是 JDK 自带的 String 类。
把刚刚的类改一下
```java
package java.lang;
public class String {
//
static{
System.out.println("我是自定义的String类的静态代码块");
}
//错误: 在类 java.lang.String 中找不到 main 方法
public static void main(String[] args) {
System.out.println("hello,String");
}
}
```
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0021.png">
由于双亲委派机制一直找父类所以最后找到了Bootstrap ClassLoaderBootstrap ClassLoader找到的是 JDK 自带的 String 类在那个String类中并没有 main() 方法,所以就报了上面的错误。
#### 举例2
```java
package java.lang;
public class ShkStart {
public static void main(String[] args) {
System.out.println("hello!");
}
}
```
输出结果:
```java
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main"
Process finished with exit code 1
```
即使类名没有重复也禁止使用java.lang这种包名。这是一种保护机制
#### 举例3
当我们加载jdbc.jar 用于实现数据库连接的时候
1. 我们现在程序中需要用到SPI接口而SPI接口属于rt.jar包中Java核心api
2. 然后使用双清委派机制引导类加载器把rt.jar包加载进来而rt.jar包中的SPI存在一些接口接口我们就需要具体的实现类了
3. 具体的实现类就涉及到了某些第三方的jar包了比如我们加载SPI的实现类jdbc.jar包【首先我们需要知道的是 jdbc.jar是基于SPI接口进行实现的】
4. 第三方的jar包中的类属于系统类加载器来加载
5. 从这里面就可以看到SPI核心接口由引导类加载器来加载SPI具体实现类由系统类加载器来加载
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_002/0022.png">
### 双亲委派机制优势
通过上面的例子,我们可以知道,双亲机制可以
1. 避免类的重复加载
2. 保护程序安全防止核心API被随意篡改
- 自定义类自定义java.lang.String 没有被加载。
- 自定义类java.lang.ShkStart报错阻止创建 java.lang开头的类
沙箱安全机制
--------
1. 自定义String类时在加载自定义String类的时候会率先使用引导类加载器加载而引导类加载器在加载的过程中会先加载jdk自带的文件rt.jar包中java.lang.String.class报错信息说没有main方法就是因为加载的是rt.jar包中的String类。
2. 这样可以保证对java核心源代码的保护这就是沙箱安全机制。
其他
----
### 如何判断两个class对象是否相同
在JVM中表示两个class对象是否为同一个类存在两个必要条件
1. 类的完整类名必须一致,包括包名
2. **加载这个类的ClassLoader指ClassLoader实例对象必须相同**
3. 换句话说在JVM中即使这两个类对象class对象来源同一个Class文件被同一个虚拟机所加载但只要加载它们的ClassLoader实例对象不同那么这两个类对象也是不相等的
### 对类加载器的引用
1. JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的
2. **如果一个类型是由用户类加载器加载的那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中**
3. 当解析一个类型到另一个类型的引用的时候JVM需要保证这两个类型的类加载器是相同的后面讲