根据jvm源码剖析类加载机制
java Test.class之后的大致流程
java Test.class ----> 对于windows操作系统
----> java.exe调用jvm.dll文件创建JVM,
----> 在创建JVM中先由C++的代码创建Boostarp(引导)类加载器,
----> C++代码会创建sun.misc.Launcher.getLauncher()生成Launcher实例(JVM启动器实例)
----> 引导类加载器首先加载sun.misc.Launcher类,
----> 在初始化时执行静态代码块new Launcher(),
----> new的过程中调用Launcher类的构造方法,
----> 在Launcher类的构造方法中,创建ExtClassLoader和AppClassLoader,
----> 并维护三个类加载器的父子关系,将AppClassLoader赋给当前线程的ClassLoader属性和Launcher对象的ClassLoader属性。
----> jvm默认使用sun.misc.Launcher.getClassLoader()返回Launcher对象的ClassLoader对象(AppClassLoader)加载我们程序的类
类的加载过程
加载—校验—准备—解析—初始化—使用—卸载
加载:从磁盘中以IO流的方式读取class文件的字节码
校验:验证class文件合法性,如文件头
准备:为静态变量分配内存,并赋默认值,如boolean类型赋false、int类型赋0
解析:静态链接:将符号引用替换为直接引用,把静态方法替换为指向内存的地址或句柄!!!
初始化:对类的静态变量设置指定的值、执行静态代码块
类加载器
种类
bootstrapClassLoader(启动/引导)类加载器:由C++创建,负责加载jre/lib下的核心类库
ExtClassLoader扩展类加载器:负责加载jre/lib/ext目录下的类
AppClassLoader应用程序类加载器:负责加载用户自定义路径下的类
import sun.misc.Launcher;
import java.net.URL;
public class TestJDKClassLoader {
public static void main(String[] args) {
//jre/lib下的核心类由启动类加载器加载
System.out.println(String.class.getClassLoader());
//jre/lib/ext下的类由扩展类加载器加载
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
//用户类路径下的类由应用程序类加载器加载
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
//获取当前应用程序类加载器
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
//获取引导类加载器加载的jar包路径
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
双亲委派原则
双亲委派机制:当类加载器加载某个类时,先判断类是否已加载,如果没有则委派父类加载器加载,父类加载器判断类是否已加载,如果没有则委派父类加载器加载;如果到引导类加载器(bootstrapClassloader)判断类尚未加载,将尝试加载类,如果加载不到,则指派子类加载器加载。
源码:
在java.lang.ClassLoader.loadClass()方法实现的双亲委派机制:
//name是加载的全类名,如com.example.demo.Test
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//1、判断该类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//2、如果找不到委派父类加载器加载
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) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//3、如果父类加载器没有加载到,子类加载器尝试加载
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;
}
}
设计双亲委派机制的目的
沙箱安全机制:用户自定义的与核心类同名的类不会被加载,而加载的仍是jre定义的类,防止核心库的类和API被篡改;
防止类被重复加载:如果父类加载器已经加过某个类了,子类加载器就不会二次加载了。
自定义类加载器和打破双亲委派原则
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法:
一个是loadClass(String, boolean),实现了双亲委派机制,若要打破双亲委派原则重写loadClass()方法
一个是findClass,默认实现是空方法,实现了根据全类名加载字节码并返回Class对象,所以我们自定义类加载器主要是重写findClass方法。
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
// @Override
// protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// //自己的包进行加载的时候打破双亲委派原则
// if(name.contains("com.example.demo")){
// return findClass(name);
// }
//
// //核心包依然由父的类加载器加载
// return super.loadClass(name, resolve);
// }
@Override
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();
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//指定的类路径下的包 ,不在委托父类加载器进行加载,直接由该类加载器加载
if (name.contains("com.example.demo")) {
c = findClass(name);
} else {
c = this.getParent().loadClass(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;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字数组。
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
private byte[] loadByte(String name) throws IOException {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
public static void main(String args[]) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test");
//D盘创建 com/example/demo几级目录,将Test类的复制类Test.class丢入该目录
Class clazz = classLoader.loadClass("com.example.demo.Test");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("test", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
注意:自定义类加载器的父类加载器是AppClassLoader。
因为,在new MyClassLoader
时,会执行MyClassLoader的父类ClassLoader的无参构造方法,调用getSystemClassLoader()
,会返回sun.misc.Launcher类的loader属性,该属性的值就是AppClassLoader对象,在ClassLoader构造方法中将AppClassLoader对象赋值到MyClassLoader的parent属性。
Tomcat打破了双亲委派原则
Tomcat是web容器,所以:
1、会部署多个服务,但每个服务的同一个类路径一样,但版本可能不一样;
2、部署在同一web容器的服务可以共享同一个类,防止同一个类重复加载多次;
3、web容器自己的依赖不能与web应用程序的类混淆,基于安全考虑;
4、web容器支持jsp热更新,通过卸载类加载器重新更新的方式。
所以,tomcat要打破双亲委派原则。