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的平台无关性 与平台无关性是建立在操作系统上,虚拟机厂商提供了许多可以运行在各种不同平台的虚拟机,它们都可以载入和执行字节码,从而实现程序 ...
随机推荐
- Linux配置zookeeper 和zookeeper简单介绍
一.zookeeper介绍? 一.zookeeper 简单介绍? 1.什么是集群? // 很多台服务器保持连接通讯状态,并且所有的服务器做同一件事就称之为集群 2.什么是zookeeper? 注册中心 ...
- NumPy 初学者指南中文第三版·翻译完成
原文:NumPy: Beginner's Guide - Third Edition 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远. 在线阅 ...
- 记项目中ES6+gulp+angularjs里的问题
AngualrJs中可用来注入的有三种类型,service.factory.provider,这三种写法不样,用法也都不一样.其中,service只实例化一次,其实就是单例模式的思想.无论我们在什么地 ...
- 【VUE】vue中遍历数组和对象
一.遍历对象 对象数据 cities:{ "A":[{ "id": 56, "spell": "aba", " ...
- json解析出现:java.lang.ClassCastException: net.sf.ezmorph.bean.MorphDynaBean cannot be cast to XXX
感谢大佬:https://blog.csdn.net/one_ink/article/details/99817676 一.出错原因 当我们利用json解析中的toBean方法时,如果它的属性里面包含 ...
- Java.lang.Integer类中toString(int i, int radix)的具体实现
Java.lang.Integer.toString(int i,int radix)方法可以实现将一个int类型的10进制的数据转换为指定进制的数据. api文档中介绍: 返回第二个参数指定的基数中 ...
- HDOJ acm steps 3.1.1
(都是递推求值,呵呵,好开心- - ) 今天又是在自习室通宵(文明玩的停不下来了) 游戏玩完想想该水题了,于是打开了HDOJ的ACM STEPS(这是个好东西,就像他的名字,一步步来的) 2.3.x貌 ...
- shell——wait与多进程并发
在脚本里用&后台打开多个子进程,用wait命令可以使这些子进程并行执行. 例1: fun1(){ while true do echo 1 sleep 1 done } fun2(){ whi ...
- JabRef
# JabRef 下载 https://www.fosshub.com/JabRef.html # JabRef 安装 自己更改下目录直接安装接可以了 # 新建自己的库, 然后点击保存就可以了. # ...
- redis(二)-----redis基本数据类型之字符串
Redis的全称是REmote Dictionary Server,它主要提供了5种数据结构:字符串.哈希.列表.集合.有序集合,同时在字符串的基础之上演变 出了位图(Bitmaps)和HyperLo ...