主要是为了安全,避免用户恶意加载破坏JVM
正常运行的字节码文件,好比说加载一个本身写的java.util.HashMap.class
。这样就有可能形成包冲突问题。java
jdk
中rt.jar
的字节码文件jdk
中/jre/lib/ext
文件夹下的字节码文件classPath
下的字节码文件一、ClassLoader.loadClass()
git
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
就没有遵照这个约定。github
一、为何JDBC
没法遵照这个约定?
JDBC
是SPI
机制的一个例子,JDK
定义了java.sql.Connection
核心接口,后续MySQL
、Oracle
为其提供实现类。在运行中是经过java.sql.DriverManager
来获取指定实现类的实例。这里须要明白三个问题:sql
java.sql.DriverManager
是在rt.jar
中,由核心类加载器加载的;Collection
的实现类都是在classpath
中;也就是说当JVM
在java.sql.DriverManager
类的getConnection()
方法中获取Collection
实现类的字节码时,当前类的定义类加载器是启动类加载器,而按照约定启动类加载器是不容许加载classpath
下的字节码。因此,JDBC
就没法遵照这个约定。安全
二、JDBC
是如何解决上面的问题的?
为了解决这个,java
在线程中放入一个类加载器Thread.currentThread().getContextClassLoader()
;而这个类加载器能够是随意的。好比你想加载classpath
包下的字节码文件,只须要设置当前线程的类加载器为应用程序类加载器便可。网络
JVM
类加载过程JVM
本质的工做就是读取字节码文件、执行字节码文件中的指令。其中JVM
将读取字节码文件的过程称为JVM
类加载过程。app
JVM
读取的字节码文件将放在方法区里;jvm
JVM
类加载机制分为五个部分:加载、验证、准备、解析、初始化。以下图所示:
ide
Loading
:加载这一步是将JVM
外的字节码文件加载到JVM
内部方法区中的Class
对象。源码分析
JVM
能够经过几种方式来加载外部的字节码文件?因为双亲委派加载模型的存在,一个Class
对象的初始类加载器initiating class loader
和定义类加载器defining class loader
有可能不是同一个。
JVM
加载这个字节码文件defineClass
方法,将字节码转换成Class
对象java
在判断instanceof
时,只有类名、defining class loader
都相等,才表示是同一个类的实例。
Class.getClassLoader()
获得的是定义类加载器
一、验证使用不一样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); } } } }
二、实现一个遵照双亲委派加载模型的类加载器
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