写本篇的动因只是一段看起来很诡异的代码,让我感觉有必要认识一下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类加载流程
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 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;
}
}
复制代码
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\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
复制代码
四、自定义类本地磁盘类加载器
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;
}
@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;
}
}
复制代码
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;
}
@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;
}
}
复制代码
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@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也加载不了,就崩了
六、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.更多关于我
笔名 | 微信 | |
---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 |
我的github:github.com/toly1994328
我的简书:www.jianshu.com/u/e4e52c116…
我的简书:www.jianshu.com/u/e4e52c116…
个人网站:www.toly1994.com
3.声明
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持