从源码剖析JVM类加载机制
万丈高楼平地起。大家入门JAVA
必然绕不开JVM
的研究,在保证基础知识储备的同时,把面试的火箭顺手造了岂不是美滋滋。
干货概要
搞通类加载器,自己动手做类加载器!
为啥有双亲委派机制?
如何打破双亲委派机制及其应用?
1 搞通类加载器
方便理解晦涩原理,先来一波简单实例代码开开胃。
package TaorenCoding
public class Math {
public static final int initData = 666;
public static User user = new User();
public int compute() { //一个方法对应一块栈帧内存区域
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
} }
如上代码,看的懂吧,够清晰吧。好了那接下来就是真正的硬菜。
-
扒拉出其中的核心要点: 类加载过程
其中loadClass的类加载过程有如下几步:
加载 : 在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证 : 校验字节码文件的正确性(如魔数等二进制内容)
准备 : 给类的静态变量分配内存,并赋予默认值
解析 : 将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
初始化: 对类的静态变量初始化为指定的值,执行静态代码块
使用 卸载
自定义类加载器
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是
loadClass(String, boolean)
,实现了双亲委派机制,还有一个方法是findClass
,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
直接继承方法开造!
1 public class MyClassLoaderTest {
2 static class MyClassLoader extends ClassLoader {
3 private String classPath;
4
5 public MyClassLoader(String classPath) {
6 this.classPath = classPath;
7 }
8
9 private byte[] loadByte(String name) throws Exception {
10 name = name.replaceAll("\\.", "/");
11 FileInputStream fis = new FileInputStream(classPath + "/" + name+ ".class");
13 int len = fis.available();
14 byte[] data = new byte[len];
15 fis.read(data);
16 fis.close();
17 return data;
18 }
19
20 protected Class<?> findClass(String name) throws ClassNotFoundException {
21 try {
22 byte[] data = loadByte(name);
23 //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节
数组。
24 return defineClass(name, data, 0, data.length);
25 } catch (Exception e) {
26 e.printStackTrace();
27 throw new ClassNotFoundException();
28 }
29 }
31 }
32
33 public static void main(String args[]) throws Exception {
34 //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定 //义类加载器的父加载器设置为应用程序类加载器AppClassLoader
35 MyClassLoader classLoader = new MyClassLoader("D:/test");
36 //D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录
37 Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
38 Object obj = clazz.newInstance();
39 Method method = clazz.getDeclaredMethod("sout", null);
40 method.invoke(obj, null);
41 System.out.println(clazz.getClassLoader().getClass().getName());
42 }
43 }
**运行结果:**
=======自己的加载器加载类调用方法=======
com.taoren.jvm.MyClassLoaderTest$MyClassLoader
双亲委派机制
为啥要使用双亲委派机制?
沙箱安全机制: 自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
避免类的重复加载: 当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
打破双亲委派机制
Tomcat
作为一个经典的web容器,我们经常用来部署多个实例。有没有想过,我们所部署的多个应用时不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离
,由此,Tomcat
是打破双亲委派机制的典型应用。主要原因如下:
-
-
部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。 -
-
web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。 -
-
web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。
万丈高楼平地起。大家入门JAVA
必然绕不开JVM
的研究,在保证基础知识储备的同时,把面试的火箭顺手造了岂不是美滋滋。
干货概要
搞通类加载器,自己动手做类加载器!
为啥有双亲委派机制?
如何打破双亲委派机制及其应用?
1 搞通类加载器
方便理解晦涩原理,先来一波简单实例代码开开胃。
package TaorenCoding
public class Math {
public static final int initData = 666;
public static User user = new User();
public int compute() { //一个方法对应一块栈帧内存区域
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
} }
如上代码,看的懂吧,够清晰吧。好了那接下来就是真正的硬菜。
-
扒拉出其中的核心要点: 类加载过程
其中loadClass的类加载过程有如下几步:
加载 : 在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证 : 校验字节码文件的正确性(如魔数等二进制内容)
准备 : 给类的静态变量分配内存,并赋予默认值
解析 : 将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
初始化: 对类的静态变量初始化为指定的值,执行静态代码块
使用 卸载
自定义类加载器
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是
loadClass(String, boolean)
,实现了双亲委派机制,还有一个方法是findClass
,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
直接继承方法开造!
1 public class MyClassLoaderTest {
2 static class MyClassLoader extends ClassLoader {
3 private String classPath;
4
5 public MyClassLoader(String classPath) {
6 this.classPath = classPath;
7 }
8
9 private byte[] loadByte(String name) throws Exception {
10 name = name.replaceAll("\\.", "/");
11 FileInputStream fis = new FileInputStream(classPath + "/" + name+ ".class");
13 int len = fis.available();
14 byte[] data = new byte[len];
15 fis.read(data);
16 fis.close();
17 return data;
18 }
19
20 protected Class<?> findClass(String name) throws ClassNotFoundException {
21 try {
22 byte[] data = loadByte(name);
23 //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节
数组。
24 return defineClass(name, data, 0, data.length);
25 } catch (Exception e) {
26 e.printStackTrace();
27 throw new ClassNotFoundException();
28 }
29 }
31 }
32
33 public static void main(String args[]) throws Exception {
34 //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定 //义类加载器的父加载器设置为应用程序类加载器AppClassLoader
35 MyClassLoader classLoader = new MyClassLoader("D:/test");
36 //D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录
37 Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
38 Object obj = clazz.newInstance();
39 Method method = clazz.getDeclaredMethod("sout", null);
40 method.invoke(obj, null);
41 System.out.println(clazz.getClassLoader().getClass().getName());
42 }
43 }
**运行结果:**
=======自己的加载器加载类调用方法=======
com.taoren.jvm.MyClassLoaderTest$MyClassLoader
双亲委派机制
为啥要使用双亲委派机制?
沙箱安全机制: 自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
避免类的重复加载: 当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
打破双亲委派机制
Tomcat
作为一个经典的web容器,我们经常用来部署多个实例。有没有想过,我们所部署的多个应用时不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离
,由此,Tomcat
是打破双亲委派机制的典型应用。主要原因如下:
-
-
部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。 -
-
web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。 -
-
web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。
万丈高楼平地起。大家入门JAVA
必然绕不开JVM
的研究,在保证基础知识储备的同时,把面试的火箭顺手造了岂不是美滋滋。
干货概要
搞通类加载器,自己动手做类加载器!
为啥有双亲委派机制?
如何打破双亲委派机制及其应用?
1 搞通类加载器
方便理解晦涩原理,先来一波简单实例代码开开胃。
package TaorenCoding
public class Math {
public static final int initData = 666;
public static User user = new User();
public int compute() { //一个方法对应一块栈帧内存区域
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
} }
如上代码,看的懂吧,够清晰吧。好了那接下来就是真正的硬菜。
-
扒拉出其中的核心要点: 类加载过程
其中loadClass的类加载过程有如下几步:
加载 : 在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证 : 校验字节码文件的正确性(如魔数等二进制内容)
准备 : 给类的静态变量分配内存,并赋予默认值
解析 : 将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
初始化: 对类的静态变量初始化为指定的值,执行静态代码块
使用 卸载
自定义类加载器
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是
loadClass(String, boolean)
,实现了双亲委派机制,还有一个方法是findClass
,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
直接继承方法开造!
1 public class MyClassLoaderTest {
2 static class MyClassLoader extends ClassLoader {
3 private String classPath;
4
5 public MyClassLoader(String classPath) {
6 this.classPath = classPath;
7 }
8
9 private byte[] loadByte(String name) throws Exception {
10 name = name.replaceAll("\\.", "/");
11 FileInputStream fis = new FileInputStream(classPath + "/" + name+ ".class");
13 int len = fis.available();
14 byte[] data = new byte[len];
15 fis.read(data);
16 fis.close();
17 return data;
18 }
19
20 protected Class<?> findClass(String name) throws ClassNotFoundException {
21 try {
22 byte[] data = loadByte(name);
23 //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节
数组。
24 return defineClass(name, data, 0, data.length);
25 } catch (Exception e) {
26 e.printStackTrace();
27 throw new ClassNotFoundException();
28 }
29 }
31 }
32
33 public static void main(String args[]) throws Exception {
34 //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定 //义类加载器的父加载器设置为应用程序类加载器AppClassLoader
35 MyClassLoader classLoader = new MyClassLoader("D:/test");
36 //D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录
37 Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
38 Object obj = clazz.newInstance();
39 Method method = clazz.getDeclaredMethod("sout", null);
40 method.invoke(obj, null);
41 System.out.println(clazz.getClassLoader().getClass().getName());
42 }
43 }
**运行结果:**
=======自己的加载器加载类调用方法=======
com.taoren.jvm.MyClassLoaderTest$MyClassLoader
双亲委派机制
为啥要使用双亲委派机制?
沙箱安全机制: 自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
避免类的重复加载: 当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
打破双亲委派机制
Tomcat
作为一个经典的web容器,我们经常用来部署多个实例。有没有想过,我们所部署的多个应用时不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离
,由此,Tomcat
是打破双亲委派机制的典型应用。主要原因如下:
-
-
部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。 -
-
web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。 -
-
web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。