class装载验证流程html
加载java
装载类的第一个阶段 取得类的二进制流 转为方法区数据结构 在Java堆中生成对应的java.lang.Class对象
连接mysql
验证sql
准备数据库
解析设计模式
初始化数组
执行类构造器<clinit> static变量 赋值语句 static{}语句 子类的<clinit>调用前保证父类的<clinit>被调用 <clinit>是线程安全的
什么是类装载器ClassLoader安全
ClassLoader是一个抽象类 ClassLoader的实例将读入Java字节码将类装载到JVM中 ClassLoader能够定制,知足不一样的字节码流获取方式 ClassLoader负责类装载过程当中的加载阶段
JDK中ClassLoader默认设计模式数据结构
方法app
ClassLoader的重要方法 public Class<?> loadClass(String name) throws ClassNotFoundException 载入并返回一个Class protected final Class<?> defineClass(byte[] b, int off, int len) 定义一个类,不公开调用 protected Class<?> findClass(String name) throws ClassNotFoundException loadClass回调该方法,自定义ClassLoader的推荐作法 protected final Class<?> findLoadedClass(String name) 寻找已经加载的类
分类
BootStrap ClassLoader (启动ClassLoader) Extension ClassLoader (扩展ClassLoader) App ClassLoader (应用ClassLoader/系统ClassLoader) Custom ClassLoader(自定义ClassLoader) 每一个ClassLoader都有一个Parent做为父亲
协同工做
测试类加载顺序
public class FindClassOrder { public static void main(String args[]){ HelloLoader loader=new HelloLoader(); loader.print(); } }
public class HelloLoader { public void print(){ System.out.println("I am in apploader"); } }
直接运行以上代码: I am in apploader
加上参数 -Xbootclasspath/a:/Users/heliming/IdeaProjects/democloud/jvm/src/main/java
//编译这个java文件的class文件放入/Users/heliming/IdeaProjects/democloud/jvm/src/main/java目录 public class HelloLoader { public void print(){ System.out.println("I am in bootloader"); } }
I am in bootloader 此时AppLoader中不会加载HelloLoader I am in apploader 在classpath中却没有加载 说明类加载是从上往下的
测试查找类的时候,是从下往上的
强制在apploader中加载
/** * description: https://www.cnblogs.com/cl-rr/p/9081817.html defineClass()方法更多的是用来加载再也不classes下的文件,或者是在AOP时覆盖原来类的字节码,须要注意的是,对于同名类使用2次及以上defineClass()回抛出异常。 * * @author: dawn.he QQ: 905845006 * @email: dawn.he@cloudwise.com * @email: 905845006@qq.com * @date: 2019/9/24 6:22 PM */ //package com.zejian.classloader; import java.io.*; import java.lang.reflect.Method; /** * Created by zejian on 2017/6/21. * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] */ public class FileClassLoader extends ClassLoader { private long lastTime; private String rootDir; public FileClassLoader(String rootDir) { this.rootDir = rootDir; } /** * 编写findClass方法的逻辑 * @param name * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 获取类的class文件字节数组 byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { //直接生成class对象 return defineClass(name, classData, 0, classData.length); } } /** * 编写获取class文件并转换为字节码流的逻辑 * @param className * @return */ private byte[] getClassData(String className) { // 读取类文件的字节 String path = classNameToPath(className); try { InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; // 读取类文件的字节码 while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } /** * 类文件的彻底路径 * @param className * @return */ private String classNameToPath(String className) { return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; } public static void main(String[] args) throws ClassNotFoundException { // String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/"; String rootDir="/Users/heliming/IdeaProjects/democloud/jvm/target/classes/"; //建立自定义文件类加载器 FileClassLoader loader = new FileClassLoader(rootDir); try { //加载指定的class文件 // Class<?> object1=loader.loadClass("com.zejian.classloader.DemoObj"); Class<?> object1=loader.loadClass("HelloLoader"); Object o = object1.newInstance(); Method method=o.getClass().getDeclaredMethod("print", null); method.invoke(o, null); //输出结果:I am in apploader } catch (Exception e) { e.printStackTrace(); } } }
打印:I am in apploader
在查找类的时候,先在底层的Loader查找,是从下往上的。Apploader能找到,就不会去上层加载器加载
替换掉上边的main函数,测试findClass只能加载一次
public static void main(String[] args) throws ClassNotFoundException { String rootDir = "/Users/heliming/IdeaProjects/democloud/jvm/target/classes/"; //建立自定义文件类加载器 FileClassLoader loader = new FileClassLoader(rootDir); FileClassLoader loader2 = new FileClassLoader(rootDir); try { Class<?> object1 = loader.loadClass("HelloLoader"); Object o = object1.newInstance(); Method method = o.getClass().getDeclaredMethod("print", null); method.invoke(o, null); Class<?> object2 = loader2.loadClass("HelloLoader"); o = object1.newInstance(); method = o.getClass().getDeclaredMethod("print", null); method.invoke(o, null); System.out.println("loadClass->obj1:" + object1.hashCode()); System.out.println("loadClass->obj2:" + object2.hashCode()); //加载指定的class文件,直接调用findClass(),绕过检测机制,建立不一样class对象。 Class<?> object3 = loader.findClass("HelloLoader"); //findClass只能加载一次 若是再次加载就会报错重复加载类 //Class<?> object5 = loader.findClass("HelloLoader"); Class<?> object4 = loader2.findClass("HelloLoader"); System.out.println("loadClass->obj3:" + object3.hashCode()); System.out.println("loadClass->obj4:" + object4.hashCode()); /** * 输出结果: * loadClass->obj1:644117698 loadClass->obj2:644117698 findClass->obj3:723074861 findClass->obj4:895328852 */ } catch (Exception e) { e.printStackTrace(); } }
双亲委派模式的问题
解决:
Thread. setContextClassLoader() 上下文加载器 是一个角色 用以解决顶层ClassLoader没法访问底层ClassLoader的类的问题 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例
从图可知rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,因为SPI中的类常常须要调用外部实现类的方法,而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)没法经过Bootstrap类加载器加载,所以只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相关类使用。显然这种线程上下文类加载器的加载方式破坏了“双亲委派模型”,它在执行过程当中抛弃双亲委派加载链模式,使程序能够逆向使用类加载器,固然这也使得Java类加载器变得更加灵活。为了进一步证明这种场景,不妨看看DriverManager类的源码,DriverManager是Java核心rt.jar包中的类,该类用来管理不一样数据库的实现驱动即Driver,它们都实现了Java核心包中的java.sql.Driver接口,如mysql驱动包中的com.mysql.jdbc.Driver
,这里主要看看如何加载外部实现类,在DriverManager初始化时会执行以下代码
//DriverManager是Java核心包rt.jar的类 public class DriverManager { //省略没必要要的代码 static { loadInitialDrivers();//执行该方法 println("JDBC DriverManager initialized"); } //loadInitialDrivers方法 private static void loadInitialDrivers() { sun.misc.Providers() AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { //加载外部的Driver的实现类 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); //省略没必要要的代码...... } }); }
在DriverManager类初始化时执行了loadInitialDrivers()方法,在该方法中经过ServiceLoader.load(Driver.class);
去加载外部实现的驱动类,ServiceLoader类会去读取mysql的jdbc.jar下META-INF文件的内容,以下所示
而com.mysql.jdbc.Driver继承类以下:
public class Driver extends com.mysql.cj.jdbc.Driver { public Driver() throws SQLException { super(); } static { System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. " + "The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary."); } }
从注释能够看出日常咱们使用com.mysql.jdbc.Driver
已被丢弃了,取而代之的是com.mysql.cj.jdbc.Driver
,也就是说官方再也不建议咱们使用以下代码注册mysql驱动
//不建议使用该方式注册驱动类 Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8"; // 经过java库获取数据库链接 Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
而是直接去掉注册步骤,以下便可
String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8"; // 经过java库获取数据库链接 Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
这样ServiceLoader会帮助咱们处理一切,并最终经过load()方法加载,看看load()方法实现
public static <S> ServiceLoader<S> load(Class<S> service) { //经过线程上下文类加载器加载 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
很明显了确实经过线程上下文类加载器加载的,实际上核心包的SPI类对外部实现类的加载都是基于线程上下文类加载器执行的,经过这种方式实现了Java核心代码内部去调用外部实现类。咱们知道线程上下文类加载器默认状况下就是AppClassLoader,那为何不直接经过getSystemClassLoader()获取类加载器来加载classpath路径下的类的呢?实际上是可行的,但这种直接使用getSystemClassLoader()方法获取AppClassLoader加载类有一个缺点,那就是代码部署到不一样服务时会出现问题,如把代码部署到Java Web应用服务或者EJB之类的服务将会出问题,由于这些服务使用的线程上下文类加载器并不是AppClassLoader,而是Java Web应用服自家的类加载器,类加载器不一样。,因此咱们应用该少用getSystemClassLoader()。总之不一样的服务使用的可能默认ClassLoader是不一样的,但使用线程上下文类加载器总能获取到与当前程序执行相同的ClassLoader,从而避免没必要要的问题。ok~.关于线程上下文类加载器暂且聊到这,前面阐述的DriverManager类,你们能够自行看看源码,相信会有更多的体会,另外关于ServiceLoader本篇并无过多的阐述,毕竟咱们主题是类加载器,但ServiceLoader是个很不错的解耦机制,你们能够自行查阅其相关用法。
打破常规模式 and 热替换
双亲模式的破坏 双亲模式是默认的模式,但不是必须这么作 Tomcat的WebappClassLoader 就会先加载本身的Class,找不到再委托parent OSGi的ClassLoader造成网状结构,根据须要自由加载Class
在java目录下
javac Worker.java 启动main函数 修改Worker.java 再次javac Worker.java 输出改变了
HelloMain.java
import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class HelloMain { private URLClassLoader classLoader; private Object worker; private long lastTime; // private String classDir="/Users/heliming/IdeaProjects/democloud/jvm/target/classes/"; private String classDir="/Users/heliming/IdeaProjects/democloud/jvm/src/main/java/"; public static void main(String[] args) throws Exception { HelloMain helloMain=new HelloMain(); helloMain.execute(); } private void execute() throws Exception { while(true){ //监测是否须要加载 if(checkIsNeedLoad()){ System.out.println("检测到新版本,准备从新加载"); reload(); System.out.println("从新加载完成"); } //一秒 invokeMethod(); Thread.sleep(1000); } } private void invokeMethod() throws Exception { //经过反射方式调用 //使用反射的主要缘由是:不想Work被appclassloader加载, // 若是被appclassloader加载的话,再经过自定义加载器加载会有点问题 Method method=worker.getClass().getDeclaredMethod("sayHello", null); method.invoke(worker, null); } private void reload() throws Exception { classLoader = new MyClassLoader(new URL[] { new URL( "file:"+classDir)}); worker = classLoader.loadClass("Worker") .newInstance(); System.out.println(worker.getClass()); } private boolean checkIsNeedLoad() { File file=new File(classDir+ "Worker.class"); long newTime=file.lastModified(); if(lastTime<newTime){ lastTime=newTime; return true; } return false; } }
Worker.java
public class Worker { public void sayHello(){ System.out.println("version:fds"); } }
MyClassLoader.java
import java.net.URL; import java.net.URLClassLoader; public class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] urls) { super(urls); } // 打破双亲模式,保证本身的类会被本身的classloader加载 @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(name); if (c == null) { try { //这里若是是先加载本身没法找到object类会报错的因此catch下 c=findClass(name); } catch (Exception e) { } } if(c==null){ c=super.loadClass(name, resolve); } return c; } }