JVM上篇:类加载子系统
JVM类加载
1.内存结构概述
- 类加载器子系统
- Loading阶段,加载class文件
- Linking阶段
- 验证
- 准备
- 解析
- Initialization阶段,初始化
- 运行时数据区
- 方法区,把需要引用的类的字节码文件都会加载到方法区,存放类的信息,方法信息等等
- 堆(heap)区,创建java对象的主体都分配到堆空间中
- 虚拟机栈,每个线程一份
- pc寄存器(程序计数器),每个线程一份
- 本地方法栈,本地方法接口调用,使用本地方法栈
- 执行引擎
- 解释器
- 即时编译器
- 垃圾回收期
2.类加载子系统概述
类加载器子系统的作用
ClassLoader负责从文件系统或者从网络中加载Class文件,至于它是否可以运行,有Execution Engine来决定。
加载类到哪里?
加载的类的信息存放在内存空间的“方法区”,除了类的信息外,方法区还会存放运行时的常量的映射等信息。
3.类的加载过程
2.1加载
- 通过类的全类名获取Class文件的二进制流
- 将加载进来的字节流转化为方法区运行时的数据结构
- 在内存中生成一个这个类的对象Class对象,作为方法区这个类的各种数据的访问入口
2.2Linking
2.2.1验证(Verify)
- 确保Class文件的字节流中的信息正确,保证类加载的正确性
- 主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
- 例如下面一个元数据的验证,java字节码文件初始文件都是CA FE BA BE
2.2.2准备(Prepare)
- 为类变量分配内存,并设置类变量的默认初始值,即零值
- final修饰的常量,在编译的时候就会分配值了
- 不会对实例变量进行初始化,类变量会分配在方法区中,实例变量会随着对象一起分配到堆空间中
2.2.3解析(Resolve)
将常量池内的符号引用,转化为直接引用。
例:Object类的引用
2.3初始化(Initlization)
初始化阶段就是执行类构造器方法<clinit>()的过程,他和类的构造器<init>()是不一样的。
此方法是javac编译器自动收集类中的所有静态变量的赋值动作和静态代码块中的语句合并而来
执行顺序按照编写的代码的顺序执行
<init>为类的构造器方法,任何一个类声明以后,内部至少存在一个类的构造器
若被加载的类具有父类,JVM会保证子类的<clinit>()执行之前,父类的<clinit>()已经执行完毕
举例:
定义父类
public class initTest {
public static int A = 1;
static {
A=2;
}
}
定义子类
public class Son extends initTest {
public static int B =A;
public static void main(String[] args) {
System.out.println(B);
}
}
编译运行
查看字节码文件,可以看到加载B的时候先调了父类的clinit,加载了A
虚拟机必须保证一个类的<clinit>()方法在多线程的情况下被同步加锁
举例:
编写被调用的class
public class initTest {
static {
//这里使用循环,目的是为了卡住<clinit>()方法,让别的线程等待
if (true){
System.out.println(Thread.currentThread().getName()+"进来了");
while (true){
}
}
}
}
编写两个线程,两个线程都加载initTest,因为对于JVM来说,同一个类只会被加载一次,加载以后类信息等存放在方法区中
public class Son {
public static void main(String[] args) {
Runnable r = () ->{
System.out.println(Thread.currentThread().getName()+"开始");
initTest initTest = new initTest();
System.out.println(Thread.currentThread().getName()+"结束");
}; Thread t1 = new Thread(r,"线程1");
Thread t2 = new Thread(r,"线程2"); t1.start();
t2.start();
}
}
编译运行,第一个加载initTest的线程会进去,而另一个加载initTest的线程则会在后面等待
4.类加载器的分类
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader),和自定义类加载器(User-Defined ClassLoader)
JVM将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
可能你会疑惑,拓展类加载器,和系统类加载器是什么类型,其实他们都是派生于ClassLoader类的,JVM均视为自定义类加载器
引导类加载器,拓展类加载器,系统类加载器,用户自定义加载器,这四者的关系是包含关系。不是子父继承的关系
3.1虚拟机自带的加载器
3.1.1引导类加载器(Bootstrap ClassLoader)
- 这个类加载器使用c/c++实现,嵌套与JVM内部
- 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resource.jar或者 sun.boot.class.path 路径下的内容),用于提供JVM自身需要的类
- 它并不继承与java.lang.ClassLoader,c语言编写,无父加载器
- 它用来加载拓展类加载器和系统类加载器(应用程序类加载器),并指定Bootstrap为他们的父加载器
- 出于安全考虑,Bootstrap ClassLoader只加载包名为java,javax,sun等开头的类
3.1.2拓展类加载器(Extension ClassLoader)
- java语言编写,为sun.misc.Launcher的一个内部类
- 派生于ClassLoader类
- 父加载器为引导类加载器
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK安装目录的jre/lib/ext子目录下加载类库,如果用户创建的JAR放在此目录下,也会由拓展类加载器自动加载。
3.1.3系统类加载器(应用程序类加载器 AppClassLoader)
- java语言编写,为sun.misc.Launcher的内部类
- 派生于ClassLoader类
- 父类加载器为拓展类加载器
- 它负责加载环境遍历classpath或者是系统属性java.class.path指定路径下的类库
- 该类加载器是程序中默认的类加载器,一般来说,我们编写的Java应用的类都是由它来完成加载
- 通过ClassLoader#getSystemClassLoader()的方法,可以获取到该类加载器
举例:
import sun.misc.Launcher;
import java.net.URL;
import java.security.Provider;
public class ClassLoaderTestFyp {
public static void main(String[] args) {
// 获取引导类加载器加载的路径
System.out.println("====================引导类加载器加载的路径==========================");
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(urL.toExternalForm());
}
//从上面的路径中随意选择一个类,看一下这个类的类加载器是什么
System.out.println("====================获取引导类加载器===============================");
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader); //应该为null,因为引导类加载器是c和c++编写,我们无法获取到
// 拓展类加载器
System.out.println("====================拓展类加载器加载的路径==========================");
String extDirs = System.getProperty("java.ext.dirs");
System.out.println(extDirs);
// 系统类加载器classpath
System.out.println("====================系统类加载器加载的路径==========================");
String classpath = System.getProperty("java.class.path");
for (String s : classpath.split(";")) {
System.out.println(s);
}
}
}
3.1.4用户自定义类加载器
为什么要自定义类的加载器:
- 隔离加载类,(主要解决不同版本jar包的版本冲突)
- 修改类加载的方式
- 扩展加载源
- 防止源码泄露
自定义类加载器实现步骤:
- 继承java.lang.ClassLoader抽象类
- 在jdk1.2之后,建议把自定义的类加载器逻辑写在findclass当中
- 如果没有太过于复杂的需求,可以继承URLClassLoader,可以避免自己去读取字节码流的方式,避免自己写URLClassLoader
5.ClassLoader介绍
ClassLoader是一个抽象类,其后所有的类加载器都继承自ClassLoader(除引导类加载器)
获取ClassLoader的途径
- 方式一:获取当前ClassLoader
clazz.getClassLoader()
- 方式二:获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader()
- 方式三:获取系统的ClassLoader
ClassLoader.getSystemClassLoader()
- 方式四:获取调用者的ClassLoader
DriverManager.getCallerClassLoader()
6.双亲委派机制
Java虚拟机对class文件采用的按需加载的方式,也就是说当需要使用此类的时候,才会把这个类的class文件加载到内存,生成class对象。且,在加载某个类的class文件的时候,Java虚拟机采用的是双亲委派模式,即把请求交给父类加载器处理,它是一种任务委派模式。
工作原理
- 如果一个类加载器收到了类加载的请求,它并不会先自己去加载这个类,而是把这个请求委托给父类的加载器去执行
- 如果父类加载器还存在其父类加载器,则依次向上委托,请求最终将到达顶层的引导类加载器
- 如果父类加载器可以完成类加载的任务,则就成功返回,如果父类加载器无法完成类加载的任务,则子加载器才会自己尝试去加载类,这就是双亲委派模式。
举例:
当我们加载jdbc.jar包用于实现数据库连接的时候,首先我们要知道的是jdbc.jar是基于SPI接口进行实现的,所以在加载的时候会进行双亲委派,从引导类加载器加载SPI核心的类,然后再加载SPI接口类,接着在进行反向委派,通过线程上下文类加载器进行实现类jdbc.jar的加载。
优势
避免类的重复加载
保护程序安全,防止核心的API被随意的修改
举例:假设我们自己实现了一个java.lang.String,如果给这个类写个main方法,去运行,是不行的,因为当类加载器收到类加载请求的时候会向上委托,会加载核心的String类,而核心的String类无此方法,所以会报错为方法找不到,这也称为沙箱安全机制。
7.其他
如何判断两个Class对象是否相等
在JVM中表示两个Class对象是否为同一个类,有两个必要的条件
- 类的全类名必须相同
- 加载这个类的ClassLoader必须相同
也就是说,即使两个类对象来自同一个class文件,但是加载他们的类加载器不同,那这两个对象也是不想等的。
8.类的主动使用和被动使用
java程序对类的使用方式分为:主动使用和被动使用
主动使用,又分为7种情况:
- 创建类的实例
- 访问某个类或接口的静态变量,或者对这个静态变量赋值。
- 调用类的静态方法
- 反射
- 初始化一个类的子类
- Java虚拟机启动的时候被标明为启动类的类
- JDK 7 开始提供的动态语言的支持
除了以上7种情况,其他使用java类的方式,都看作是对类的被动使用,不会导致类的初始化。
JVM上篇:类加载子系统的更多相关文章
- JVM解毒——类加载子系统
带着问题,尤其是面试问题的学习才是最高效的.加油,奥利给! 点赞+收藏 就学会系列,文章收录在 GitHub JavaEgg ,N线互联网开发必备技能兵器谱 直击面试 看你简历写得熟悉JVM,那你说说 ...
- jvm (一)jvm结构 & 类加载 & 双亲委托模型
参考文档: jvm内幕-java虚拟机详解:http://www.importnew.com/17770.html 常量池:https://www.jianshu.com/p/c7f47de2ee80 ...
- 【JVM之内存与垃圾回收篇】类加载子系统
类加载子系统 概述 完整图如下: 如果自己想手写一个 Java 虚拟机的话,主要考虑哪些结构呢? 类加载器 执行引擎 类加载器子系统作用 类加载器子系统负责从文件系统或者网络中加载 Class 文件, ...
- Java JVM——2.类加载器子系统
概述 类加载器子系统在Java JVM中的位置 类加载器子系统的具体实现 类加载器子系统的作用 ① 负责从文件系统或者网络中加载.class文件,Class 文件在文件开头有特定的文件标识. ② Cl ...
- JVM笔记 -- 来,教你类加载子系统
类加载子系统 类文件首先需要经过类加载子系统,进行加载,进类信息等加载到运行时数据区,生成Klass的实例. 在类加载子系统中有以下3个阶段操作(广义上的加载): 加载阶段 Bootstrap Cla ...
- <JVM上篇:内存与垃圾回收篇>02-类加载子系统
笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...
- JVM_02 类加载子系统
JVM细节版架构图 本文针对Class Loader SubSystem这一块展开讲解类加载子系统的工作流程 类加载子系统作用 1.类加载子系统负责从文件系统或者网络中加载class文件,class文 ...
- JVM虚拟机 类加载过程与类加载器
目录 前言 类的生命周期 类加载过程 加载 连接 验证 准备 解析 初始化 类加载器 三大类加载器 双亲委派模型 概念 为什么要使用双亲委派模型 源码分析 反双亲委派模型 参考 前言 类装载器子系统是 ...
- JVM 的执行子系统
JVM 的执行子系统. 一.Class类文件结构 1. JVM的平台无关性 与平台无关性是建立在操作系统上,虚拟机厂商提供了许多可以运行在各种不同平台的虚拟机,它们都可以载入和执行字节码,从而实现程序 ...
随机推荐
- MyBatis加强(1)~myBatis对象关系映射(多对一关系、一对多关系)、延迟/懒加载
一.myBatis对象关系映射(多对一关系.一对多关系) 1.多对一关系: ---例子:多个员工同属于一个部门. (1)myBatis发送 额外SQL: ■ 案例:员工表通过 dept_id 关联 部 ...
- Python中处理日期时间库的使用方法
常用的库有time.datetime.其中datetime库是对time库的封装,所以使用起来更加便捷.date是指日期时间(年月日)处理,time往往更加细小的单位(小时分秒等)的时间处理. 一.d ...
- JVM学习五:性能监控工具
一.系统性能监控 系统性能工具用于确定系统运行的整体状态,基本定位问题所在. Linux – uptime • 系统时间 • 运行时间 n 例子中为7分钟 • 连接数 n 每一个终端算一个连接 • 1 ...
- npm 查看一个包的版本信息
有了npm 我们能够简单的一段代码就下载我们需要的包,但是包是不断更新的, 所以我们要关注包的版本信息: 现在,假设我们需要 jquery ,但是jquery现在有很多版本,我们如何通过npm查看呢? ...
- 源码推荐 VVebo剥离的TableView绘制
源码推荐 VVebo剥离的TableView绘制 https://github.com/johnil/VVeboTableViewDemo 此项目由VVebo剥离,希望你能通过这个demo看到我是如何 ...
- LVS+Keepalived 高可用群集部署
LVS+Keepalived 高可用群集部署 1.LVS+Keepalived 高可用群集概述 2.LVS+Keepalived高可用群集部署 1.LVS+Keepalived 高可用群集概述: LV ...
- MSTP多生成树协议
MSTP多生成树协议 目录 MSTP多生成树协议 1.MSTP(Multiple Spanning Tree Protocol)概述 2.STP.RSTP.PVST的应用缺陷 3.MSTP的主要特点 ...
- Sublime Python3编译环境修改
http://blog.csdn.net/qq_33304418/article/details/63337602 添加编译环境python3.6 Tools -> Build Syst ...
- 深入MySQL(二):MySQL的数据类型
前言 对于MySQL中的数据类型的选择,不同的数据类型看起来可能是相同的效果,但是其实很多时候天差地别. 本章从MySQL中的常用类型出发,结合类型选择的常见错误,贯彻MySQL的常用类型选择. 常用 ...
- DBLink的使用(从A库使用SQL查询B库的数据)
DBLink的使用 情景:今天我需要从A数据库查询B数据库的数据,进行一些数据比对和联合查询的操作. 所以用到的DBLink,在此记录一下使用流程,希望能够帮助下一个小白,一步到位的解决问题. 一句话 ...