写本篇的动因只是一段看起来很诡异的代码,让我感觉有必要认识一下ClassLoader
----[Counter.java]------------------------- public class Counter { private static Counter sCounter = new Counter();//<---- tag1 public static int count = 10;//<---- tag2 private Counter() { count++; } public static Counter getInstance() { return sCounter; } } ----[Client.java]------------------------- public class Client { public static void main(String[] args) { Counter counter = Counter.getInstance(); System.out.println(counter.count);//10 } } |-- 当tag1和tag2换一下位置,得到的是11 复制代码
上一篇讲了Java虚拟机,关于类加载器一笔带过,本篇详细讲一下
java文件通过javac可以编译成.class文件,类加载器就是将.calss加载到内存里
关于Class实例在堆中还是方法区中? 这里找了一篇文章,讲得挺深
将字节码(二进制流)载入方法区 堆内存中生成java.lang.Class对象,作为方法区中该类各种数据的操作入口 |-- .class文件主要来源-------------------- -– 磁盘中直接加载 -– 网络加载.class文件 -– 从zip ,jar 等文件中加载.class 文件 -– 从专有数据库中提取.class文件 -– 将Java源文件动态编译为.class文件 复制代码
验证加载进来的字节流信息是否符合虚拟机规范
[1].文件格式验证: 字节流是否符合class文件格式规范 [2].元数据验证: 是否符合java的语言语法的规范 [3].字节码验证:方法体进行校验分析,保证运行时没危害出现 [4].符号引用验证 :常量池中的各种符号引用信息进行匹配性校验 复制代码
为类静态变量分配内存并设置为[对应类型的初始值]
----[Counter.java]------------------------- public class Counter { private static Counter sCounter = new Counter(); public static int count = 1; private Counter() { count++; } public static Counter getInstance() { return sCounter; } } 如上:在准备阶段 count 的值为int的默认值 = 0 复制代码
常量池内的符号引用替换为直接引用的过程,也就是字面量转化为指针。
主要解析: 类,接口,字段,类方法,接口方法,方法类型,方法句柄和调用点限定符引用
按顺序查找 静态变量
以及 静态代码块
对用户自定义类变量的 赋值
,
//现在count=0,调用后new Counter()时count++,变为1 private static Counter sCounter = new Counter(); public static int count = 10;// 此时count赋值为10 复制代码
1.创建实例 2.访问静态变量或者对该静态变量赋值 3.调用静态方法 4.反射 5.初始化一个类的子类 6.JVM启动时被标明为启动类(main) ---->[Shape类]------------------ public class Shape { public static String color = "白色"; static { System.out.println("-----初始化于Shape-----"); } public static void draw() { } } ---->[Shape子类:Rect]------------------ public class Rect extends Shape { public static int radius = 20; static { System.out.println("-----初始化于Rect-----"); } } new Shape(); //1.创建实例 String color = Shape.color;//2.访问静态变量 Shape.color = "黑色";//2.对该静态变量赋值 Shape.draw();//3.调用静态方法 Class.forName("classloader.Shape");//4.反射 Rect.radius = 10;//5.初始化一个类的子类 复制代码
|-- 访问编译期静态常量[不会]触发初始化 |-- 访问运行期静态常量[会]触发初始化 public class Shape { ... public static final int weight = 1; public static final int height = new Random(10).nextInt(); ... } int w = Shape.weight;//编译期静态常量不会触发初始化 int h = Shape.height;//运行期静态常量会触发初始化 |-- 其中height在运行时才可以确定值,访问会触发初始化 复制代码
|-- 类初始化时并不会初始化它的接口 |-- 子接口初始化不会初始化父接口 |-- 声明类变量时不会初始化 |-- 子类再调用父类的静态方法或属性时,子类不会被初始化 Shape shape;//声明类变量,不会初始化 String color = Rect.color;//只初始化Shape Rect.draw();//只初始化Shape 复制代码
通过 ClassLoader.getSystemClassLoader()
可以获取系统类类加载器
debug一下,可以看到系统类加载器:类名为 AppClassLoader
,所以也称应用类加载器
ClassLoader loader = ClassLoader.getSystemClassLoader(); System.out.println(loader); Shape shape = new Shape(); ////sun.misc.Launcher$AppClassLoader@18b4aac2 ClassLoader loader = shape.getClass().getClassLoader(); String name = "toly"; ClassLoader loaderSting = name.getClass().getClassLoader(); System.out.println(loaderSting);//null //可见String的类加载器为null,先说一下,为null时由Bootstrap类加载器加载 |-- 还有一点想强调一下,类加载器加载类后,不会触发类的初始化 ClassLoader loader = ClassLoader.getSystemClassLoader(); Class<?> shapeClazz = loader.loadClass("classloader.Shape");//此时不初始化 Shape shape = (Shape) shapeClazz.newInstance();//创建实例时才会初始化 复制代码
这里的父并不是指继承,而是ClassLoader类中有一个parent属性是ClassLoader类型
所以是认干爹,而不是亲生的。就像Android中的ViewGroup和View的父子View关系
认了干爹之后,有事先让干爹来摆平,干爹摆不平,再自己来,都摆不平,就崩了呗。
---->[ClassLoader#成员变量]---------------- private final ClassLoader parent; ---->[ClassLoader#构造函数一参]---------------- |-- 可以在一参构造函数中传入parent,认个干爹,瞟了一下源码,貌似是parent初始化的唯一途径 protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } |--关于父委托机制loadClass方法完美诠释: ---->[ClassLoader#loadClass]------------------ public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } ---->[ClassLoader#loadClass(String,boolean)]------------------------------ 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); if (c == null) {//未被加载 long t0 = System.nanoTime(); try { if (parent != null) {//有干爹,让干爹来加载 c = parent.loadClass(name, false); } else {//没有干爹,让大佬Bootstrap类加载器加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) {//干爹和大佬都加载不了 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; } } 复制代码
Bootstrap ClassLoader : 引导类加载器(启动类加载器/根类加载器) |-- C++语言实现, 负责加载jre/lib路径下的核心类库 System.out.println(System.getProperty("sun.boot.class.path")); //D:/M/JDK1.8/jre/lib/resources.jar; // D:/M/JDK1.8/jre/lib/rt.jar; // D:/M/JDK1.8/jre/lib/sunrsasign.jar; // D:/M/JDK1.8/jre/lib/jsse.jar; // D:/M/JDK1.8/jre/lib/jce.jar; // D:/M/JDK1.8/jre/lib/charsets.jar; // D:/M/JDK1.8/jre/lib/jfr.jar; // D:/M/JDK1.8/jre/classes Launcher$ExtClassLoader : 拓展类加载器 |-- Java语言实现,负责加载jre/lib/ext System.out.println(System.getProperty("java.ext.dirs")); //D:/M/JDK1.8/jre/lib/ext;C:/Windows/Sun/Java/lib/ext Launcher$AppClassLoader : 系统类加载器 |-- Java语言实现,加载环境变量路径classpath或java.class.path 指定路径下的类库 String property = System.getProperty("java.class.path"); //D:/M/JDK1.8/jre/lib/charsets.jar; // D:/M/JDK1.8/jre/lib/deploy.jar; ...略若干jre的jar路径... // J:/FileUnit/file_java/base/out/production/classes; <--- 当前项目的输出路径 // C:/Program Files/JetBrains/IntelliJ IDEA 2018.1.3/lib/idea_rt.jar 复制代码
---->[ClassLoader#构造函数]------------------------------------------ protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } 这里可以看出无参构造是默认干爹是:getSystemClassLoader,也就是系统类加载器加载器 当然也可以使用一参构造认干爹 |-- 上面分析:在ClassLoader#loadClass方法中,当三个JVM的类加载器都找不到时 |-- 会调用findClass方法来初始化c ,那我们来看一下findClass: ---->[在ClassLoader#findClass]------------------------ protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } 就问你一句:人家直接抛异常,你敢不覆写吗? 复制代码
/** * 作者:张风捷特烈 * 时间:2019/3/7/007:14:05 * 邮箱:1981462002@qq.com * 说明:本地磁盘类加载器 */ public class LocalClassLoader extends ClassLoader { private String path; public LocalClassLoader(String path) { this.path = path; } @Override protected Class<?> findClass(String name) { byte[] data = getBinaryData(name); if (data == null) { return null; } return defineClass(name, data, 0, data.length); } /** * 读取字节流 * * @param name 全类名 * @return 字节码数组 */ private byte[] getBinaryData(String name) { InputStream is = null; byte[] result = null; ByteArrayOutputStream baos = null; try { if (name.contains(".")) { String[] split = name.split("//."); name = split[split.length - 1]; } String path = this.path + "//" + name + ".class"; File file = new File(path); if (!file.exists()) { return null; } is = new FileInputStream(file); baos = new ByteArrayOutputStream(); byte[] buff = new byte[1024]; int len = 0; while ((len = is.read(buff)) != -1) { baos.write(buff, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } if (baos != null) { result = baos.toByteArray(); baos.close(); } } catch (IOException e) { e.printStackTrace(); } } return result; } } 复制代码
新建一个类HelloWorld,有一个公共方法say,注意包名和文件夹名
package com.toly1994.classloader; public class HelloWorld { public void say() { System.out.println("HelloWorld"); } } 复制代码
使用LocalClassLoader加载刚才的字节码文件,通过反射调用say方法,执行无误
这里要提醒一下:使用javac编译时的jdk版本,要和工程的jdk版本一致,不然会报错
LocalClassLoader loader = new LocalClassLoader("G://Out//java//com//toly1994//classloader"); try { Class<?> clazz = loader.loadClass("com.toly1994.classloader.HelloWorld");; Constructor<?> constructor = clazz.getConstructor(); Object obj = constructor.newInstance(); Method say = clazz.getMethod("say"); say.invoke(obj);//HelloWorld } catch (NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } |-- 这里可以测试一下obj的类加载器 System.out.println(obj.getClass().getClassLoader()); //classloader.LocalClassLoader@6b71769e 复制代码
这样无论.java文件移到磁盘的哪个位置,都可以的通过指定路径加载
将刚才的class文件放到服务器上:http://www.toly1994.com:8089/imgs/HelloWorld.class
然后访问路径来读取字节流,进行类的加载
核心也就是获取到流,然后findClass中通过defineClass生成Class对象
/** * 作者:张风捷特烈 * 时间:2019/3/7/007:14:05 * 邮箱:1981462002@qq.com * 说明:网络类加载器 */ public class NetClassLoader extends ClassLoader { private String urlPath; public NetClassLoader(String urlPath) { this.urlPath = urlPath; } @Override protected Class<?> findClass(String name) { byte[] data = getDataFromNet(urlPath); if (data == null) { return null; } return defineClass(name, data, 0, data.length); } private byte[] getDataFromNet(String urlPath) { byte[] result = null; InputStream is = null; ByteArrayOutputStream baos = null; try { URL url = new URL(urlPath); is = url.openStream(); baos = new ByteArrayOutputStream(); byte[] buff = new byte[1024]; int len = 0; while ((len = is.read(buff)) != -1) { baos.write(buff, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } if (baos != null) { result = baos.toByteArray(); baos.close(); } } catch (IOException e) { e.printStackTrace(); } } return result; } } 复制代码
使用上基本一致
NetClassLoader loader = new NetClassLoader("http://www.toly1994.com:8089/imgs/HelloWorld.class"); try { Class<?> clazz = loader.loadClass("com.toly1994.classloader.HelloWorld"); Constructor<?> constructor = clazz.getConstructor(); Object obj = constructor.newInstance(); Method say = clazz.getMethod("say"); say.invoke(obj);//HelloWorld } catch (NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } |-- 这里可以测试一下obj的类加载器 System.out.println(obj.getClass().getClassLoader()); //classloader.NetClassLoader@66d2e7d9 复制代码
现在网络和本地都可以,我们让本地的loader当做网络加载的父亲
---->[NetClassLoader#添加构造]------------------------ public NetClassLoader(ClassLoader parent, String urlPath) { super(parent); this.urlPath = urlPath; } ---->[测试类]----------------------------- LocalClassLoader localLoader = new LocalClassLoader("G://Out//java//com//toly1994//classloader"); //这里讲NetClassLoader的干爹设置为localLoader NetClassLoader netLoader = new NetClassLoader(localLoader, "http://www.toly1994.com:8089/imgs/HelloWorld.class"); try { Class<?> clazz = netLoader.loadClass("com.toly1994.classloader.HelloWorld"); Constructor<?> constructor = clazz.getConstructor(); Object obj = constructor.newInstance(); System.out.println(obj.getClass().getClassLoader()); //这里打印classloader.LocalClassLoader@591f989e Method say = clazz.getMethod("say"); say.invoke(obj);//HelloWorld } catch (NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } |-- 可以看到,老爹LocalClassLoader能加载,作为孩子的NetClassLoader就没加载 |--- 现在将本地的[删了],老爹LocalClassLoader加载不了,NetClassLoader自己搞 System.out.println(obj.getClass().getClassLoader()); classloader.NetClassLoader@4de8b406 复制代码
现在应该很明白父委派机制是怎么玩的了吧,如果NetClassLoader也加载不了,就崩了
[1].该类所有的实例都已经被GC。 [2].加载该类的ClassLoader实例已经被GC。 [3].该类的java.lang.Class对象没有在任何地方被引用。 复制代码
LocalClassLoader localLoader = new LocalClassLoader("G://Out//java//com//toly1994//classloader"); Class<?> clazz = localLoader.loadClass("com.toly1994.classloader.HelloWorld"); Constructor<?> constructor = clazz.getConstructor(); Object obj = constructor.newInstance(); System.out.println(obj.getClass().getClassLoader()); Method say = clazz.getMethod("say"); say.invoke(obj);//HelloWorld |-- 使用上面的类加载器再加载一次com.toly1994.classloader.HelloWorld可见两个class对象一致 System.out.println(clazz.hashCode());//1265210847 Class<?> clazz2 = localLoader.loadClass("com.toly1994.classloader.HelloWorld"); System.out.println(clazz2.hashCode());//1265210847 复制代码
LocalClassLoader localLoader = new LocalClassLoader("G://Out//java//com//toly1994//classloader"); Class<?> clazz = localLoader.loadClass("com.toly1994.classloader.HelloWorld"); Constructor<?> constructor = clazz.getConstructor(); Object obj = constructor.newInstance(); Method say = clazz.getMethod("say"); say.invoke(obj);//HelloWorld // 清除引用 obj = null; //清除该类的实例 localLoader = null; //清楚该类的ClassLoader引用 clazz = null; //清除该class对象的引用 复制代码
深入理解Java类加载器(ClassLoader)
Java --ClassLoader创建、加载class、卸载class
关于Class实例在堆中还是方法区中?项目源码 | 日期 | 附录 |
---|---|---|
V0.1--无 | 2018-3-7 | 无 |
发布名: JVM之类加载器ClassLoader
捷文链接:https://juejin.im/post/5c7a9595f265da2db66df32c
笔名 | 微信 | |
---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 |
我的github:https://github.com/toly1994328
我的简书:https://www.jianshu.com/u/e4e52c116681
我的简书:https://www.jianshu.com/u/e4e52c116681
个人网站:http://www.toly1994.com
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持