JVM核心机制
类加载的全过程
类加载机制
JVM把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成JVM可以直接使用的Java类型的过程
加载
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。
链接
将Java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static变量)分配肉冻并设置类变量初始值的阶段,这些内存将在方法区中进行分配
- 解析:虚拟机常量池内的符号引用替换为直接引用的过程
初始化
- 初始化阶段是执行类构造器< clinit >()方法的过程。类构造器< clinit >()方法是由编译器自动收集类中的所有变量的赋值动作和静态语句块(static块)中的语句合并产生的。
- 当初始化一个类的时候,如果发现其还没有进行过初始化化,则需要先对其父类初始化
- 虚拟机会保证一个类的< clinit >()方法在多线程环境中被正确加锁和同步
- 当访问一个Java类的静态域时,只有真正声明这个域的类才会被初始化。
代码验证:
1 | public class t { |
执行结果: 静态初始化类A 创建A类的对象 2

1 | public class t { |
执行结果: 静态初始化块t main方法 静态初始化A_Father 静态初始化类A 创建A_Father对象 创建A类的对象 2
类的主动引用(一定会发生类的初始化)
- new 一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当虚拟机启动,java Hello,则一定会初始化Hello类。也就是先启动main方法所在的类
- 当初始化一个类时,如果其父类没有被初始化,则先会初始化他的父类
类的被动引用(不会发生类的初始化)
当访问一个静态域时,只有真正声明这个域的类才会被初始化
通过子类引用父类的静态变量,不会导致子类初始化
通过数组定义类引用,不会触发此类的初始化
引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
深入类加载器
类加载器原理
类加载器的作用
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类折java.lang.Class对象,作为方法区类数据的访问入口
类缓存
标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class对象。
java.class.ClassLoader类介绍
作用:java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义一个Java类,即java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载Java应用所需的资源,如图像文件和配置文件等。
相关方法:
1 | getParent()//返回类加载器的父类加载器 |
类加载器树状结构、双亲委托(代理)机制
树状结构
引导类加载器(用C语言写的)
它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的内容),是用原生代码来实现的,并不继承自java.lang.ClassLoader
扩展类加载器(用Java写的)
用来加载Java的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容)。Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类
应用程序类加载器(用Java写的)
它根据Java应用的类路径(classpath,java.class.path路径)
一般来说,Java应用的类都是由它来完成加载的
自定义类加载器(用Java写的)
开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求

代理模式
代理模式:
交给其它加载器来加载指定的类
双亲委托机制:
就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
双亲委托机制是为了保证Java核心库的类型安全(这种机制就保证不会出现用户自己能定义java.lang.Object类的情况)
类加载器除了用于加载类,也是安全的最基本的屏障
双亲委托机制是代理模式的一种
并不是所有的类加载器都采用双亲委托机制
tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。
自定义类加载器(文件、网络、加密)
自定义类加载器的流程:
首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;
委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例;
调用本类加载器的findClass()方法,试图获取对应的字节码,如果获取到,则调用defineClass()导入类型到方法区;如果获取不到对应的字节码或者其它原因失败,返回异常给loadClass(),loadClass()转抛异常,终止加载过程
注意:被两个类加载器加载的同一个类,JVM不认为是相同的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81/**
* 文件类加载器
*/
public class FileSystemClassLoader extends ClassLoader {
//com.zephon.test.User
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
//先查询有没有加载过这个类,如果已经加载,则直接返回加载好的类。如果没有,则加载新的类
if (c != null) {
return c;
} else {
ClassLoader parent = this.getParent();
try{
c = parent.loadClass(name); //委派给父类加载
} catch (Exception e){
// e.printStackTrace();
}
if (c != null) {
return c;
} else {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
c = defineClass(name, classData, 0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String classname) {
String path = rootDir + "/" + classname.replace(".", "/") + ".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
byte[] buffer = new byte[1024];
int temp = 0;
while ((temp = is.read(buffer)) != -1) {
baos.write(buffer, 0, temp);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//测试
public static void main(String[] args) throws ClassNotFoundException {
FileSystemClassLoader l = new FileSystemClassLoader("./temp");
Class<?> c = l.loadClass("temp.com.Hello");
System.out.println(c);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72/**
* 网络类加载器
*/
public class NetClassLoader extends ClassLoader {
private String rootUrl;
public NetClassLoader(String rootUrl){
this.rootUrl = rootUrl;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
//先查询有没有加载过这个类,如果已经加载,则直接返回加载好的类。如果没有,则加载新的类
if (c != null) {
return c;
} else {
ClassLoader parent = this.getParent();
try{
c = parent.loadClass(name); //委派给父类加载
} catch (Exception e){
// e.printStackTrace();
}
if (c != null) {
return c;
} else {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
c = defineClass(name, classData, 0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String classname) {
String path = rootUrl + "/" + classname.replace(".", "/") + ".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
URL url = new URL(path);
is = url.openStream();
byte[] buffer = new byte[1024];
int temp = 0;
while ((temp = is.read(buffer)) != -1) {
baos.write(buffer, 0, temp);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 加密工具类
* @author zephon
*/
public class EncrptUtil {
//测试
public static void main(String[] args) {
encrpt("./temp/temp/com/Hello.class","./temp/temp/com/a.class");
}
public static void encrpt(String src,String dest){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dest);
int temp = -1;
while((temp=fis.read())!=-1){
fos.write(temp^0xff);//取反操作
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(fis!=null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try{
if(fos!=null){
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73/**
* 加载文件系统中加密后的class字节码的类加载器
* @author zephon
*/
public class DecrptClassLoader extends ClassLoader {
private String rootDir;
public DecrptClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
//先查询有没有加载过这个类,如果已经加载,则直接返回加载好的类。如果没有,则加载新的类
if (c != null) {
return c;
} else {
ClassLoader parent = this.getParent();
try{
c = parent.loadClass(name); //委派给父类加载
} catch (Exception e){
// e.printStackTrace();
}
if (c != null) {
return c;
} else {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
c = defineClass(name, classData, 0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String classname) {
String path = rootDir + "/" + classname.replace(".", "/") + ".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
//解密
int temp = -1;
while ((temp = is.read()) != -1) {
baos.write(temp ^ 0xff);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
线程上下文类加载器

服务器类加载原理和OSGI介绍

