写本篇的动因只是一段看起来很诡异的代码,让我感觉有必要认识一下ClassLoader

----[Counter.java]-------------------------
public class Counter {private static Counter sCounter = new Counter();//<---- tag1public static int count = 10;//<---- tag2private 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类加载流程

1.Java虚拟机结构

上一篇讲了Java虚拟机,关于类加载器一笔带过,本篇详细讲一下
java文件通过javac可以编译成.class文件,类加载器就是将.calss加载到内存里


2.类加载的流程

关于Class实例在堆中还是方法区中?这里找了一篇文章,讲得挺深


2.1:加载
将字节码(二进制流)载入方法区
堆内存中生成java.lang.Class对象,作为方法区中该类各种数据的操作入口|-- .class文件主要来源---------------------– 磁盘中直接加载-– 网络加载.class文件-– 从zip ,jar 等文件中加载.class 文件-– 从专有数据库中提取.class文件-– 将Java源文件动态编译为.class文件
复制代码

2.2:连接 - 验证

验证加载进来的字节流信息是否符合虚拟机规范

[1].文件格式验证: 字节流是否符合class文件格式规范
[2].元数据验证: 是否符合java的语言语法的规范
[3].字节码验证:方法体进行校验分析,保证运行时没危害出现
[4].符号引用验证 :常量池中的各种符号引用信息进行匹配性校验
复制代码

2.3:连接 - 准备

为类静态变量分配内存并设置为[对应类型的初始值]

----[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
复制代码

2.4:连接 - 解析

常量池内的符号引用替换为直接引用的过程,也就是字面量转化为指针。
主要解析:类,接口,字段,类方法,接口方法,方法类型,方法句柄和调用点限定符引用


2.5 : 初始化

按顺序查找静态变量以及静态代码块对用户自定义类变量的赋值,

//现在count=0,调用后new Counter()时count++,变为1
private static Counter sCounter = new Counter();
public static int count = 10;// 此时count赋值为10
复制代码

二、类被初始化的时机

1.类被初始化的时机代码测试
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.初始化一个类的子类
复制代码

2.final对初始化的影响
|-- 访问编译期静态常量[不会]触发初始化
|-- 访问运行期静态常量[会]触发初始化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在运行时才可以确定值,访问会触发初始化
复制代码

3.初始化的其他小点
|-- 类初始化时并不会初始化它的接口
|-- 子接口初始化不会初始化父接口
|-- 声明类变量时不会初始化
|-- 子类再调用父类的静态方法或属性时,子类不会被初始化Shape shape;//声明类变量,不会初始化
String color = Rect.color;//只初始化Shape
Rect.draw();//只初始化Shape
复制代码

三、关于类加载器

1.系统类加载器(应用类加载器)

通过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();//创建实例时才会初始化
复制代码

2.父委托机制(或双亲委托机制)

这里的父并不是指继承,而是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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}复制代码

3.三个JVM中的类加载器
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\classesLauncher$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\extLauncher$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
复制代码

四、自定义类本地磁盘类加载器

1.自定义类加载器的干爹
---->[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);
}
就问你一句:人家直接抛异常,你敢不覆写吗?
复制代码

2.自定义LocalClassLoader
/*** 作者:张风捷特烈* 时间:2019/3/7/007:14:05* 邮箱:1981462002@qq.com* 说明:本地磁盘类加载器*/
public class LocalClassLoader extends ClassLoader {private String path;public LocalClassLoader(String path) {this.path = path;}@Overrideprotected 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;}
}
复制代码

3.测试类的字节码文件

新建一个类HelloWorld,有一个公共方法say,注意包名和文件夹名

package com.toly1994.classloader;
public class HelloWorld {public void say() {System.out.println("HelloWorld");}
}
复制代码

4.使用LocalClassLoader

使用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文件放到服务器上:www.toly1994.com:8089/imgs/HelloW…
然后访问路径来读取字节流,进行类的加载

1.自定义NetClassLoader

核心也就是获取到流,然后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;}@Overrideprotected 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;}
}
复制代码

2.使用

使用上基本一致

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
复制代码

3.父委派机制测试

现在网络和本地都可以,我们让本地的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@591f989eMethod 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也加载不了,就崩了


六、class对象的卸载

1.一个类被class被能被GC回收(即:卸载)的条件
[1].该类所有的实例都已经被GC。
[2].加载该类的ClassLoader实例已经被GC。
[3].该类的java.lang.Class对象没有在任何地方被引用。
复制代码

2.使用自定义加载器时JVM中的引用关系
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
复制代码

2.卸载
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实例在堆中还是方法区中?


1.本文成长记录及勘误表
项目源码 日期 附录
V0.1--无 2018-3-7

发布名:JVM之类加载器ClassLoader
捷文链接:juejin.im/post/5c7a95…

2.更多关于我
笔名 QQ 微信
张风捷特烈 1981462002 zdl1994328

我的github:github.com/toly1994328
我的简书:www.jianshu.com/u/e4e52c116…
我的简书:www.jianshu.com/u/e4e52c116…
个人网站:www.toly1994.com

3.声明

1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持

JVM篇2:[-加载器ClassLoader-]相关推荐

  1. 这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析

    前言 package com.jvm.classloader;class Father2{public static String strFather="HelloJVM_Father&qu ...

  2. 尚硅谷2020最新版宋红康JVM教程-中篇-第4章:再谈类的加载器-02和03-类的加载器分类

    引言 JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader). 从概念上来讲,自定义类加载器 ...

  3. JVM类加载、加载机制、加载器

    一.类加载过程 类的7个生命周期:加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载 1.加载 加载阶段主要做三件事: 1)加载指的是将 ...

  4. 类加载器 java委托机制_解析Java虚拟机中类的初始化及加载器的父委托机制

    类的初始化 在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值. 在程序中,静态变量的初始化有两种途径: 1.在静态变量的声明处进行初始化: 2.在静态代码块中进行初始化. 没有 ...

  5. java 内部类 加载_举例讲解Java的内部类与类的加载器

    内部类 class A { //Inner1 要在 A 初始化后 才能使用,即要被A的对象所调用 class Inner1 { int k = 0; // static int j = 0; //A加 ...

  6. 020-JVM-类加载器的四个层级-ClassLoader

    上一篇:019-JVM-类的加载过程 https://yuhongliang.blog.csdn.net/article/details/111499604 1.package sun.misc.La ...

  7. JVM学习笔记-03-类加载器及双亲委派机制

    JVM学习笔记-03-类加载器及双亲委派机制 文章目录 JVM学习笔记-03-类加载器及双亲委派机制 1. 类加载器 视频链接-最新JVM教程IDEA版[Java面试速补篇]-03-类加载器及双亲委派 ...

  8. Android 中LayoutInflater(布局加载器)之介绍篇

    本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢 博客地址:http://blog.csdn.net/l540675759/article/details/78099358 前 ...

  9. 一文学会Webpack实用功能|加载器篇

    Webpack 资源模块加载 通过探索我们知道可以把css文件作为打包的入口,不过webpack的打包入口一般还是JavaScript, 因为他的打包入口从某种程度来说可以算是我们应用的运行入口. 而 ...

最新文章

  1. Oracle的一些经典SQL面试题
  2. 超简单-用协程简化你的网络请求吧,兼容你的老项目和旧的网络请求方式
  3. android ListView适配器之SimpleAdapter的用法
  4. OSI七层模型的作用
  5. PyCharm5.0.2最新版破解注册激活码
  6. TENER: Adapting Transformer Encoder for Name Entity Recognition
  7. 1423 poj Big Number 公式题
  8. vue项目中axios请求网络接口封装
  9. 『ORACLE』 Linux和oracle用户下的常用命令(11g)
  10. cam350怎么看顶层_CAM350软件怎么查看gerber文件 cam350导出gerber教程
  11. 机器人启示录 百度影音_斯皮尔伯格筹备新作 《机器人启示录》有望启动
  12. 山特UPS电源注意事项
  13. origin柱状图同时有两组数和两组数差值_「技能」如何用Origin进行实验数据处理...
  14. Android源码分析-PackageManagerService(PMS)源码分析(三)- queryIntentActivities函数来查找activity
  15. 2019年10月19日星期六
  16. 海康大华RTSP转HLS直播
  17. bit digger
  18. “数据”到底是资产还是负债?
  19. 我们的秘密是绿色的!他喵的
  20. iphone 计算机知道密码忘了,苹果笔记本密码忘了怎么办_苹果笔记本密码忘记如何解决-win7之家...

热门文章

  1. CSS3下的渐变文字效果实现
  2. 谁为“上班玩耍”买单
  3. Tools:Android studio 使用
  4. 抓取Crash不让崩溃
  5. MDT2010-windows 7镜像捕获与模板镜像部署(二)(特别版)
  6. 制作本地yum镜像站
  7. nagios插件脚本check_linux_stats.pl 安装
  8. Java魔法堂:注解用法详解——@SuppressWarnings(转)
  9. 第十三章——表和索引分区(2)——使用拆分删除和加载大数据
  10. tab页签切换----bootstrap