双亲委派加载模型

为什么需要双亲委派加载模型

主要是为了安全,避免用户恶意加载破坏JVM正常运行的字节码文件,比如说加载一个自己写的java.util.HashMap.class。这样就有可能造成包冲突问题。

类加载器种类

  • 启动类加载器:用于加载jdkrt.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无法遵守这个约定?

JDBCSPI机制的一个例子,JDK定义了java.sql.Connection核心接口,后续MySQLOracle为其提供实现类。在运行中是通过java.sql.DriverManager来获取指定实现类的实例。这里需要明白三个问题:

  • java.sql.DriverManager是在rt.jar中,由核心类加载器加载的;
  • 第三方所提供Collection的实现类都是在classpath中;
  • 类中方法想加载新的字节码文件时,其初始类加载器就是当前这个类的定义类加载器;

也就是说当JVMjava.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/HashMapLinking是指将这符号引用与具体的class对象链接起来。

每个字节码结构都有一个运行时常量池,它会存储每个符号引用和所对应的具体对象,以此实现链接。

  • Verification:验证字节码的正确性
  • Preparation:为static成员赋默认初始值
  • Resolution:解析当前字节码里包含的其他符号引用

三、Initializing

执行初始化方法。比如下面的四个虚拟机指令:newgetstaticputstaticinvokestatic

原博客地址

JVM类加载过程详细分析的更多相关文章

  1. JVM类加载过程学习总结

    JVM类加载过程学习总结 先不说JVM类加载的原理,先看实例: NormalTest类,包含了一个静态代码块,执行的任务就是打印一句话. /** * 在正常类加载条件下,看静态代码块是否会执行 * @ ...

  2. 【深入Java虚拟机】一 JVM类加载过程

    首先Throws(抛出)几个自己学习过程中一直疑惑的问题: 1.什么是类加载?什么时候进行类加载? 2.什么是类初始化?什么时候进行类初始化? 3.什么时候会为变量分配内存? 4.什么时候会为变量赋默 ...

  3. JVM类加载过程与双亲委派模型

    类加载过程 类加载过程为JVM将类描述数据从.class文件中加载到内存,并对数据进行解析和初始化,最终形成被JVM直接使用的Java类型.包含: 加载:获取该类的二进制字节流,将字节流代表的静态存储 ...

  4. 【搞定Jvm面试】 面试官:谈谈 JVM 类加载过程是怎样的?

    类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? 系统加载 Class 类型的文件主要三步:加载->连接->初始化.连接过程 ...

  5. 三、JVM — 类加载过程

    类加载过程 加载 验证 准备 解析 初始化 类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? 系统加载 Class 类型的文件主要三步: ...

  6. [jvm] -- 类加载过程篇

    类加载过程 系统加载 Class 类型的文件主要三步 加载 通过全类名获取定义此类的二进制字节流 将字节流所代表的静态存储结构转换为方法区的运行时数据结构 在内存中生成一个代表该类的 Class对象, ...

  7. JVM 类加载过程

    类从加载到虚拟机到卸载,它的整个生命周期包括:加载(Loading),验证(Validation),准备(Preparation),解析(Resolution),初始化(Initialization) ...

  8. ORACLE实例恢复过程详细分析--使用dump、BBED等多种工具结合分析

    ---友情提示,内容较多,可以从博文左上的+目录选择小节方便阅读.  实验思路:  --实验相关TRACE文件:http://download.csdn.net/detail/q947817003/6 ...

  9. 面试题:JVM类加载机制详解(一)JVM类加载过程 背1

    首先Throws(抛出)几个自己学习过程中一直疑惑的问题: 1.什么是类加载?什么时候进行类加载? 2.什么是类初始化?什么时候进行类初始化? 3.什么时候会为变量分配内存? 4.什么时候会为变量赋默 ...

随机推荐

  1. Head First设计模式——蝇量和解释器模式

    蝇量 蝇量模式:如果让某个类的一个实例能用来提供许多“虚拟实例”,就使用蝇量模式. 在一个设计房子的平台中,周围要加上一些树,树有一个坐标XY坐标位置,而且可以根据树的年龄动态将自己绘制出来.如果我们 ...

  2. 2019-分享数百个 HT 工业互联网 2D 3D 可视化应用案例分享

    继<分享数百个 HT 工业互联网 2D 3D 可视化应用案例>2018 篇,图扑软件定义 2018 为国内工业互联网可视化的元年后,2019 年里我们与各行业客户进行了更深度合作,拓展了H ...

  3. R中的Regex

    Description grep.grepl.regexpr.gregexpr和regexec在字符向量的每个元素中搜索与参数模式匹配的参数:它们在结果的格式和详细程度上有所不同. sub和gsub分 ...

  4. WEB应用之http协议和httpd闲聊

    什么是web?在日常生活中我们常常听到web这个词,它到底是什么呢?今天我们来聊一聊web应用http协议:相信生活在如今互联网时代的我们,http这个协议应该对我们不是很陌生吧!比如双十一双十二我们 ...

  5. 代码备份 | 博客侧边栏公告(支持HTML代码)(支持JS代码)

    博客侧边栏公告(支持HTML代码)(支持JS代码) <div id='btnList'> <a class="ivu-btn ivu-btn-primary" h ...

  6. 使用 C# 和 OpenGL (SharpGL) 实现的一个简易画图版

    原文地址:https://billc.io/2019/10/fpainter/ 计算机图形学的第一个大作业是用 OpenGL 或 DirectX3d 实现一个平面的画图,应当具备直线和圆形的功能.正好 ...

  7. DOTNET CORE源码分析之ServiceDescriptor

    ServiceDescriptor在.net core中的作用就是DI中注入服务元素的描述.每一个元素核心内容部分包括需要注入的服务元素的类型ServiceType,它对应的接口(如果有的话)Impl ...

  8. 使用Servlet和JSp在浏览器上实现对数据库表的增删改查(新手)

    第一步:用户输入网址进入一个登陆界面. 里面要有账号密码输入. 登陆界面链接到登陆的Servlet类中. Servlet类 --> 1.接收参数(账户密码)  2.调用DAO层的 SQL语句 验 ...

  9. java-接口(新手)

    //创建的一个包名. package jiekou; //接口方法. //创建一个接口并且起名字. public interface JK { //抽象的返回值.(具体功能未定义,需要自己定义) ab ...

  10. centOS6.5桌面版用不了中文输入法解决方案

    1:centos6.5中   系统->首选项->输入法中选择“使用iBus(推荐)”,点击首选输入法n遍,没有任何效果. 2.我也弄了很多种方式包括用 yum install " ...