JVM类加载过程详细分析
双亲委派加载模型
为什么需要双亲委派加载模型
主要是为了安全,避免用户恶意加载破坏JVM
正常运行的字节码文件,比如说加载一个自己写的java.util.HashMap.class
。这样就有可能造成包冲突问题。
类加载器种类
- 启动类加载器:用于加载
jdk
中rt.jar
的字节码文件 - 扩展类加载器:用于加载
jdk
中/jre/lib/ext
文件夹下的字节码文件 - 应用程序类加载器:加载
classPath
下的字节码文件 - 自定义类加载器:用户在程序中自己定义的加载器
源码分析
1、ClassLoader.loadClass()
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 加锁
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
// 如果这个Class对象还没有被加载,下面就准备加载
if (c == null) {
long t0 = System.nanoTime();
try {
// 查看当前类加载器有没有父类加载器
if (parent != null) {
// 父类加载器来加载字节码文件
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 如果父类加载器也没有加载这个Class对象,就由自己来加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
不遵守双亲委派加载模型的例子
双亲委派加载模型仅仅是一个约定,后面实现类加载器时,是可以不遵守这个约定。ClassLoader
是在JDK1.0
的时候就设计好的,而双亲委派加载模型在JDK1.2
引入的。所以,有些机制是没有遵守这个约定的。比如:Service Provider Interface
机制的JDBC
就没有遵守这个约定。
1、为什么JDBC
无法遵守这个约定?
JDBC
是SPI
机制的一个例子,JDK
定义了java.sql.Connection
核心接口,后续MySQL
、Oracle
为其提供实现类。在运行中是通过java.sql.DriverManager
来获取指定实现类的实例。这里需要明白三个问题:
java.sql.DriverManager
是在rt.jar
中,由核心类加载器加载的;- 第三方所提供
Collection
的实现类都是在classpath
中; - 类中方法想加载新的字节码文件时,其初始类加载器就是当前这个类的定义类加载器;
也就是说当JVM
在java.sql.DriverManager
类的getConnection()
方法中获取Collection
实现类的字节码时,当前类的定义类加载器是启动类加载器,而按照约定启动类加载器是不允许加载classpath
下的字节码。所以,JDBC
就无法遵守这个约定。
2、JDBC
是如何解决上面的问题的?
为了解决这个,java
在线程中放入一个类加载器Thread.currentThread().getContextClassLoader()
;而这个类加载器可以是随意的。比如你想加载classpath
包下的字节码文件,只需要设置当前线程的类加载器为应用程序类加载器即可。
JVM
类加载过程
JVM
本质的工作就是读取字节码文件、执行字节码文件中的指令。其中JVM
将读取字节码文件的过程称为JVM
类加载过程。
JVM
读取的字节码文件将放在方法区里;
JVM
类加载机制分为五个部分:加载、验证、准备、解析、初始化。如下图所示:
一、Loading
:加载
这一步是将JVM
外的字节码文件加载到JVM
内部方法区中的Class
对象。
JVM
可以通过几种方式来加载外部的字节码文件?
- 从本地读字节码文件;
- 从网络读取字节码文件;
- 通过动态生成的字节码文件;
初始类加载器和定义类加载器
由于双亲委派加载模型的存在,一个Class
对象的初始类加载器initiating class loader
和定义类加载器defining class loader
有可能不是同一个。
- 初始类加载器:它是指让
JVM
加载这个字节码文件 - 定义类加载器:它是真正调用
defineClass
方法,将字节码转换成Class
对象
java
在判断instanceof
时,只有类名、defining class loader
都相等,才表示是同一个类的实例。
Class.getClassLoader()
得到的是定义类加载器
相关实验代码
1、验证使用不同ClassLoader
加载字节码文件
// 这种方法是不遵守双亲委派加载模型的约定
public class ClassLoaderLoading {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 这个Class对象是由当前方法的类加载器加载
Class c1 = MiniJVM.class;
Class c2 = new MyClassLoader().loadClass("com.github.hcsp.MiniJVM");
// 使用c2创建一个MiniJVM实例
Object o = c2.getConstructor().newInstance();
System.out.println(o instanceof MiniJVM);
MiniJVM demo = (MiniJVM) o;
}
private static class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.contains("MiniJVM")) {
try {
byte[] bytes = Files.readAllBytes(new File("target/classes/com/github/hcsp/MiniJVM.class").toPath());
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
return super.loadClass(name);
}
}
}
}
2、实现一个遵守双亲委派加载模型的类加载器
public class ClassLoaderLoading {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = MiniJVM.class;
Class c2 = new MyClassLoader(ClassLoader.getSystemClassLoader()).loadClass("com.github.hcsp.MiniJVM");
System.out.println("c2 = " + c2);
}
private static class MyClassLoader extends ClassLoader {
public MyClassLoader(ClassLoader systemClassLoader) {
super(systemClassLoader);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 加载你想让这个类加载器加载的字节码文件
if (name.contains("MiniJVM")) {
try {
byte[] bytes = Files.readAllBytes(new File("target/classes/com/github/hcsp/MiniJVM.class").toPath());
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
// 其他的字节码文件交由父类加载器加载
return super.loadClass(name);
}
}
}
}
二、Linking
:链接
当一个.java
文件编译成.class
文件时,里面含有一个符号引用,比如/java/utils/HashMap
。Linking
是指将这符号引用与具体的class
对象链接起来。
每个字节码结构都有一个运行时常量池,它会存储每个符号引用和所对应的具体对象,以此实现链接。
Verification
:验证字节码的正确性Preparation
:为static
成员赋默认初始值Resolution
:解析当前字节码里包含的其他符号引用
三、Initializing
执行初始化方法。比如下面的四个虚拟机指令:new
、getstatic
、putstatic
、invokestatic
JVM类加载过程详细分析的更多相关文章
- JVM类加载过程学习总结
JVM类加载过程学习总结 先不说JVM类加载的原理,先看实例: NormalTest类,包含了一个静态代码块,执行的任务就是打印一句话. /** * 在正常类加载条件下,看静态代码块是否会执行 * @ ...
- 【深入Java虚拟机】一 JVM类加载过程
首先Throws(抛出)几个自己学习过程中一直疑惑的问题: 1.什么是类加载?什么时候进行类加载? 2.什么是类初始化?什么时候进行类初始化? 3.什么时候会为变量分配内存? 4.什么时候会为变量赋默 ...
- JVM类加载过程与双亲委派模型
类加载过程 类加载过程为JVM将类描述数据从.class文件中加载到内存,并对数据进行解析和初始化,最终形成被JVM直接使用的Java类型.包含: 加载:获取该类的二进制字节流,将字节流代表的静态存储 ...
- 【搞定Jvm面试】 面试官:谈谈 JVM 类加载过程是怎样的?
类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? 系统加载 Class 类型的文件主要三步:加载->连接->初始化.连接过程 ...
- 三、JVM — 类加载过程
类加载过程 加载 验证 准备 解析 初始化 类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? 系统加载 Class 类型的文件主要三步: ...
- [jvm] -- 类加载过程篇
类加载过程 系统加载 Class 类型的文件主要三步 加载 通过全类名获取定义此类的二进制字节流 将字节流所代表的静态存储结构转换为方法区的运行时数据结构 在内存中生成一个代表该类的 Class对象, ...
- JVM 类加载过程
类从加载到虚拟机到卸载,它的整个生命周期包括:加载(Loading),验证(Validation),准备(Preparation),解析(Resolution),初始化(Initialization) ...
- ORACLE实例恢复过程详细分析--使用dump、BBED等多种工具结合分析
---友情提示,内容较多,可以从博文左上的+目录选择小节方便阅读. 实验思路: --实验相关TRACE文件:http://download.csdn.net/detail/q947817003/6 ...
- 面试题:JVM类加载机制详解(一)JVM类加载过程 背1
首先Throws(抛出)几个自己学习过程中一直疑惑的问题: 1.什么是类加载?什么时候进行类加载? 2.什么是类初始化?什么时候进行类初始化? 3.什么时候会为变量分配内存? 4.什么时候会为变量赋默 ...
随机推荐
- vue-cli实现最简单的全选、全不选、反选功能。不容错过呦!!!!!话不多说直接上代码。。。。
<template> <div class="hello"> <input type="button" name="al ...
- jupyternotebook安装
本篇阅读目录 一.Jupyter notebook环境安装 二.爬虫介绍 回到顶部 一.Jupyter notebook环境安装 1.Anaconda 以及 安装步骤 Anaconda指的是一个开源的 ...
- 超强图文|并发编程【等待/通知机制】就是这个feel~
你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...
- Hadoop集群搭建(三)~centos6.8网络配置
安装完centos之后,进入系统,进行网络配置.主要分为五个部分: 修改虚拟机网络编辑器:配置Winodws访问虚拟机:配置centos网卡:通过网络名访问虚拟机配置网络服务. (一)虚拟机网络编辑器 ...
- 玩转控件:扩展Dev中SimpleButton
何为扩展,顾名思义,就是在原有控件属性.事件的基础上拓展自己需要或实用的属性.事件等等.或者可以理解为,现有的控件已经不能完全满足我(的需求)了.好的扩展会使控件更加完善,实用,好用.不好的扩展,说白 ...
- Uniapp使用GoEasy实现websocket实时通讯
Uniapp作为近来最火的移动端开发技术,一套代码,可以打包成Android/iOS app和各种平台的小程序,可谓是没有最方便只有更方便. GoEasy上架DCloud Uniapp插件市场已经有一 ...
- python关于字典如何格式化地写入文件之中
1.python关于字典如何式化地写入文件之中 如何写入:https://blog.csdn.net/qq_15642411/article/details/79943741 (推荐使用json包) ...
- Hibernate一对多
例如:一个用户可以对应多个订单 但一个订单只能对应一个用户 1.创建实体类 2.编写映射文件 <?xml version="1.0"?> <!DOCTYPE h ...
- hbase 面试问题汇总
一.Hbase的六大特点: (1).表大:一个表可以有数亿行,上百万列. (2).无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态增加,同一个表中的不同行的可以有截然不同的列. (3) ...
- 五分钟学Java:一篇文章带你搞懂spring全家桶套餐
原创声明 本文首发于微信公众号[程序员黄小斜] 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 本文思维导图 什么是Spring,为什么你要学习spring? 你第一次接触spring框架是在 ...