【37期】请你详细说说类加载流程,类加载机制及自定义类加载器
阅读本文大概需要 12 分钟。
来自:juejin.im/post/5cffa528e51d4556da53d091
一、引言
二、类的加载、链接、初始化
1、加载
1.1、加载的class来源
从本地文件系统内加载class文件
从JAR包加载class文件
通过网络加载class文件
把一个java源文件动态编译,并执行加载。
2、类的链接
2.1、验证
2.2、准备
2.3、解析
3、类的初始化
在定义时初始化
在静态初始化块内初始化
3.1、< clinit>方法相关
public class Test {
static int A = 10;
static {
A = 20;
}
}
class Test1 extends Test {
private static int B = A;
public static void main(String[] args) {
System.out.println(Test1.B);
}
}
//输出结果
//20
public interface InterfaceInitTest {
long A = CurrentTime.getTime();
}
interface InterfaceInitTest1 extends InterfaceInitTest {
int B = 100;
}
class InterfaceInitTestImpl implements InterfaceInitTest1 {
public static void main(String[] args) {
System.out.println(InterfaceInitTestImpl.B);
System.out.println("---------------------------");
System.out.println("当前时间:"+InterfaceInitTestImpl.A);
}
}
class CurrentTime {
static long getTime() {
System.out.println("加载了InterfaceInitTest接口");
return System.currentTimeMillis();
}
}
//输出结果
//100
//---------------------------
//加载了InterfaceInitTest接口
//当前时间:1560158880660
public class MultiThreadInitTest {
static int A = 10;
static {
System.out.println(Thread.currentThread()+"init MultiThreadInitTest");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println(Thread.currentThread() + "start");
System.out.println(MultiThreadInitTest.A);
System.out.println(Thread.currentThread() + "run over");
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
//输出结果
//Thread[main,5,main]init MultiThreadInitTest
//Thread[Thread-0,5,main]start
//10
//Thread[Thread-0,5,main]run over
//Thread[Thread-1,5,main]start
//10
//Thread[Thread-1,5,main]run over
3.2、类初始化时机
当虚拟机启动时,初始化用户指定的主类;
当遇到用以新建目标类实例的new指令时,初始化new指令的目标类;
当遇到调用静态方法或者使用静态变量,初始化静态变量或方法所在的类;
子类初始化过程会触发父类初始化;
如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口初始化;
使用反射API对某个类进行反射调用时,初始化这个类;
Class.forName()会触发类的初始化
3.3、final定义的初始化
public class StaticInnerSingleton {
/**
* 使用静态内部类实现单例:
* 1:线程安全
* 2:懒加载
* 3:非反序列化安全,即反序列化得到的对象与序列化时的单例对象不是同一个,违反单例原则
*/
private static class LazyHolder {
private static final StaticInnerSingleton INNER_SINGLETON = new StaticInnerSingleton();
}
private StaticInnerSingleton() {
}
public static StaticInnerSingleton getInstance() {
return LazyHolder.INNER_SINGLETON;
}
}
3.4、ClassLoader只会对类进行加载,不会进行初始化
public class Tester {
static {
System.out.println("Tester类的静态初始化块");
}
}
class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
//下面语句仅仅是加载Tester类
classLoader.loadClass("loader.Tester");
System.out.println("系统加载Tester类");
//下面语句才会初始化Tester类
Class.forName("loader.Tester");
}
}
//输出结果
//系统加载Tester类
//Tester类的静态初始化块
三、类加载器
1、JVM类加载器分类
1.1、Bootstrap ClassLoader
public class BootstrapTest {
public static void main(String[] args) {
//获取根类加载器所加载的全部URL数组
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
Arrays.stream(urLs).forEach(System.out::println);
}
}
//输出结果
//file:/C:/SorftwareInstall/java/jdk/jre/lib/resources.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/rt.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/sunrsasign.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jsse.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jce.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/charsets.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jfr.jar
//file:/C:/SorftwareInstall/java/jdk/jre/classes
1.2 、Extension ClassLoader
1.3、 System ClassLoader
四、类加载机制
1.1、JVM主要的类加载机制。
全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖和引用的其他Class也由该类加载器负责载入,除非显示使用另一个类加载器来载入。
父类委托(双亲委派):先让父加载器试图加载该Class,只有在父加载器无法加载时该类加载器才会尝试从自己的类路径中加载该类。
缓存机制:缓存机制会将已经加载的class缓存起来,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存中不存在该Class时,系统才会读取该类的二进制数据,并将其转换为Class对象,存入缓存中。这就是为什么更改了class后,需要重启JVM才生效的原因。
public class ClassloaderPropTest {
public static void main(String[] args) throws IOException {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:" + systemClassLoader);
/*
获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定,如果操作系统没有指定
CLASSPATH环境变量,则默认以当前路径作为系统类加载器的加载路径
*/
Enumerationeml = systemClassLoader.getResources("");
while (eml.hasMoreElements()) {
System.out.println(eml.nextElement());
}
//获取系统类加载器的父类加载器,得到扩展类加载器
ClassLoader extensionLoader = systemClassLoader.getParent();
System.out.println("系统类的父加载器是扩展类加载器:" + extensionLoader);
System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs"));
System.out.println("扩展类加载器的parant:" + extensionLoader.getParent());
}
}
//输出结果
//系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
//file:/C:/ProjectTest/FengKuang/out/production/FengKuang/
//系统类的父加载器是扩展类加载器:sun.misc.Launcher$ExtClassLoader@1540e19d
//扩展类加载器的加载路径:C:\SorftwareInstall\java\jdk\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
//扩展类加载器的parant:null
1.2、类加载流程图
五、创建并使用自定义类加载器
1、自定义类加载分析
protected Class loadClass(String name, boolean resolve):name为类名,resove如果为true,在加载时解析该类。
protected Class findClass(String name) :根据指定类名来查找类。
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//第一步,先从缓存里查看是否已经加载
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//第二步,判断父加载器是否为null
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
}
if (c == null) {
//第三步,如果前面都没有找到,就会调用findClass方法
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;
}
}
判断此类是否已经加载;
如果父加载器不为null,则使用父加载器进行加载;反之,使用根加载器进行加载;
如果前面都没加载成功,则使用findClass方法进行加载。
2、实现自定义类加载器
public class Hello {
public void test(String str){
System.out.println(str);
}
}
public class MyClassloader extends ClassLoader {
/**
* 读取文件内容
*
* @param fileName 文件名
* @return
*/
private byte[] getBytes(String fileName) throws IOException {
File file = new File(fileName);
long len = file.length();
byte[] raw = new byte[(int) len];
try (FileInputStream fin = new FileInputStream(file)) {
//一次性读取Class文件的全部二进制数据
int read = fin.read(raw);
if (read != len) {
throw new IOException("无法读取全部文件");
}
return raw;
}
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
//将包路径的(.)替换为斜线(/)
String fileStub = name.replace(".", "/");
String classFileName = fileStub + ".class";
File classFile = new File(classFileName);
//如果Class文件存在,系统负责将该文件转换为Class对象
if (classFile.exists()) {
try {
//将Class文件的二进制数据读入数组
byte[] raw = getBytes(classFileName);
//调用ClassLoader的defineClass方法将二进制数据转换为Class对象
clazz = defineClass(name, raw, 0, raw.length);
} catch (IOException e) {
e.printStackTrace();
}
}
//如果clazz为null,表明加载失败,抛出异常
if (null == clazz) {
throw new ClassNotFoundException(name);
}
return clazz;
}
public static void main(String[] args) throws Exception {
String classPath = "loader.Hello";
MyClassloader myClassloader = new MyClassloader();
Class> aClass = myClassloader.loadClass(classPath);
Method main = aClass.getMethod("test", String.class);
System.out.println(main);
main.invoke(aClass.newInstance(), "Hello World");
}
}
//输出结果
//Hello World
六、总结
《疯狂java讲义(第3版)》
《深入理解java虚拟机++JVM高级特性与最佳实践》
推荐阅读:
微信扫描二维码,关注我的公众号
朕已阅