目录
- 引言
- java类加载流程
- java类加载机制
- 类加载原理
- 双亲委派机制 - Tomcat中双亲委派机制的应用
引言
JVM(java virtual machine)是决定java语言可以跨平台执行的重要因素。对于不同的操作系统,java语言均实现了一套可供执行程序的JVM环境。在我们的程序开始执行时,我们将Java类文件读取到JVM提供的类加载器中;加载后,在内存中将保存我们程序中使用的java类。
企业微信截图_2bdf7229-2d5e-4460-b0ff-9d218e4237fb.png
java类加载流程
-
java类加载逻辑流程
企业微信截图_0e5d6fb0-7d95-42cd-91cc-d2236a4b3021.png - java类加载流程详解
企业微信截图_c5666a79-253a-4beb-9063-b972fd716546.png
类加载:类文件从磁盘中读入到内存中
(1)验证阶段:验证字节码文件中格式是否合法.
(2)准备阶段:为程序中静态变量(static)赋默认值;一般int类型为0,bool类型为false
(3)解析阶段:将(静态变量)符号引用替换为直接引用;
该阶段将静态方法替换为指向数据所存内存的指针或句柄,被称为静态链接过程;
注:动态链接过程:在运行期间完成将符号引用变为直接引用。
(4)初始化:最后对类静态变量赋指定值(即赋值语句中的值) - JVM懒加载特性
思考以下场景
class A {
static {
system.out.println("load A");
}
public A() {
system.out.println("initual A");
}
public static void main(String[] args) {
# 思考以下两种初始化方法有何不同
A a = new A();
A a = null;
}
}
new A:
打印"load A"与"initual A"字段
A a = null:
懒加载将不加载A()的方法,仅会在类加载阶段加载类的静态方法
java的双亲委派机制
-
双亲委派机制流程:
企业微信截图_2f8b5ce3-dc44-4294-8357-7ddd16029af5.png
其中:
(1)引导类加载器:负责加载支撑了JVM运行的位于JRE目录下的核心类库,例如: rt.jar, charset.jar等
(2)扩展类加载器:负责加载支持JVM运行的位于JRE lib目录下的ext jar包
(3)应用类加载器:加载ClassPath下的类包,主要加载开发者自己创建的类 -
双亲委派/类加载原理:
(1)Launcher类:rt.jar下的核心类,负责类文件的加载
详细:待补充
(2)类加载入口方法:getClassLoader()
代码:
public classLoader getClassLoader() {
return this.loader;(loader通过Launcher()方式获取,该方法默认从AppClassLoader中进行加载,故开发者的类加载过程一般从App类加载器开始)
}
思考:从AppClassLoader开始加载的优点?
通过第一次加载将引导类与扩展类中相关类文件代入AppClass类加载中,后续仅访问AppclassLoader即可实现对所有资源的加载
(3)AppClass类中核心方法loadClass()解析:
// 注:以下为根据理解形成的伪代码
public LoaderClass loadClass() {
Class<?> c = findLoadClass(name); // 从Appclass目录下加载类文件
if (parent != null) {
c = parent.loadClass(name); // 在AppClass目录下未找到对应的类文件,尝试从它的父类(Ext)目录下加载类文件
} else {
c = findBootstrapClassOrNull(name); // 尝试通过BootstrapClass目录下获取类文件
}
if (c == null) {
c = findClass(name); // 都找不到,最后回到AppClass中的findClass方法
}
return c;
}
(4)双亲委派机制设计思想:
(4.1)沙箱安全机制:防止开发者用自己写的核心类(或其他)篡改核心库Api,例如:
企业微信截图_55fec45b-7a4e-499c-bb81-18021c221326.png
(4.2)避免类的重复加载
- 尝试自定义类加载器:
思路:实现自定类加载器需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写这个方法。
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
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;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("User");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
- 尝试打破双亲委派机制
思路:重写 java.lang.ClassLoader 类中loadClass方法,实现以下功能:当类加载机制从AppClassLoader开始时,不向上层的Loader类进行委托。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
// 当c=null时,不调用parent.loadClass(name)方法
if(c==null){
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getFindClassTime().
addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
Tomcat中双亲委派机制的应用
- 双亲委派机制给Tomcat带来的问题:
待补充 -
Tomcat8中的实现:
企业微信截图_71e31b91-8772-495e-ac2b-3a0fac506ca4.png
网友评论