您的位置:首页 > 其它

深入分析ClassLoader工作机制

2016-12-13 12:35 459 查看


加载机制


ClassLoader类结构分析

ClassLoader抽象类,有很多子类,一般在实现自己的ClassLoader时候,一般都会继承URLClassLoader这个子类,因为这个类已经实现了大部分的工作,就像Servlet通过会直接HttpServlet一样。

打开源码:

几个重要的方法

protected final Class


等级加载机制

一层一层判断是否应该由本层ClassLoader加载,并通过在哪一层加载确定类的加载级别。

Bootstrap ClassLoader :主要加载JVM自身工作需要的类,这个ClassLoader完全由JVM自己控制的。不准守普通的加载规则,没有父类和子类。

sun.misc.Launcher Java程序的入口就是sun.misc.Launcher,jdk的扩展类加载器ExtClassLoader和系统类加载器AppClassLoader都是Launcher的内部类。Launcher初始化extension
classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。下列代码是Launcher构建的过程。
public Launcher() {

Launcher.ExtClassLoader var1;

...

var1 = Launcher.ExtClassLoader.getExtClassLoader();

...

this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);

....

}


类图:



  可以看到APPClassLoader和ExtClassLoader都继承自URLClassLoader,在构建Launcher过程中,先创建ExtClassLoader,然后根据ExtClassLoader作为父加载器创建APPClassLoader。然后使用Launcher的getLauncher方法得到的就是APPClassLoader。如果在Java应用中没有其他的ClassLoader,则除了”java.ext.dirs”目录下的类由ExtClassLoader加载的外,其他的都是由APPClassLoader。看一下源码。

ExtClassLoader:
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {

final File[] var0 = getExtDirs();//这里

try {

return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction() {

public Launcher.ExtClassLoader run() throws IOException {

int var1 = var0.length;

for(int var2 = 0; var2 < var1; ++var2) {

MetaIndex.registerDirectory(var0[var2]);

}

return new Launcher.ExtClassLoader(var0);///这里

}

});

} catch (PrivilegedActionException var2) {

throw (IOException)var2.getException();

}

}

private static File[] getExtDirs() {

String var0 = System.getProperty("java.ext.dirs");

...

}
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
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

APPClassLoader:使用ExtClassLoader作为父类加载器
static class AppClassLoader extends URLClassLoader {

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {

final String var1 = System.getProperty("java.class.path");//这里

final File[] var2 = var1 == null?new File[0]:Launcher.getClassPath(var1);

return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {

public Launcher.AppClassLoader run() {

URL[] var1x = var1 == null?new URL[0]:Launcher.pathToURLs(var2);

return new Launcher.AppClassLoader(var1x, var0);//这里

}

});

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

这是URLClassLoader的:
public URLClassLoader(URL[] urls, ClassLoader parent,....){}


加载Class文件到内存中的2种方式

一种是隐式加载,一种是显式加载。

隐式加载: 就是不需要在代码中调用ClassLoader来加载的类,而是通过JVM来自动加载这些需要的类到内存中的方式,比如在我们继承或引用某些类的时候,JVM分析当前类引用的类不在内存中,那么就会自动加载类到内存中。

显示加载:就是在代码中调用ClassLoader来加载一个类,比如
this.getClass().getClassLoader().loadClass()
或者
Class.forName()
或者自定义的ClassLoader的loadClass方法。

混合的方式,就是在自定义ClassLoader时候引用其他类,就算是隐式加载。


加载class的过程

加载过程,简单地看了一下代码。大概是:

—>findClass(final String name)

—> defineClass(String name, Resource res)

—>Class


加载字节码到内存中

在抽象类ClassLoader中,很多实现都交给子类实现,如如何找到,如何加载到内存中。就是findClass()方法。最常用的URLClassLoader的findClass源码如下;
protected Class<?> findClass(final String name)

throws ClassNotFoundException

{

try {

return AccessController.doPrivileged(

new PrivilegedExceptionAction<Class>() {

public Class run() throws ClassNotFoundException {

String path = name.replace('.', '/').concat(".class");

Resource res = ucp.getResource(path, false);

if (res != null) {

try {

return defineClass(name, res);

} catch (IOException e) {

throw new ClassNotFoundException(name, e);

}

} else {

throw new ClassNotFoundException(name);

}

}

}, acc);

} catch (java.security.PrivilegedActionException pae) {

throw (ClassNotFoundException) pae.getException();

}

}
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
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

简要说明一下:首先将Path,类名的包定义,由’.’换为路径的’/’。并将.class加上。然后ucp(URLClassPath类型)定义哪里找到这个class文件,读取它的字节流。然后通过defineClass来实例类class对象。根据URL传过来的是Jar包还是实际Class文件,就能创建不同的Loader了,这样就实现加载Class文件到内存中。下图还可以看出Loader是URLClassPath的内部类。




常见加载类错误分析


ClassNotFoundException

这个非常常见的错误,一般问题是找不到.class文件,就是classpath路径和你想要的class文件路径不在一个地方。如果不知道classpath路径是什么,可以通过下列代码方式找到。
this.getClass().getClassLoader().getResource("").toString();
1
1

通常加载一个不知道的类时就会发生这样的错误。
public class notFound{

public static void main(String []args){

try {

Class.forName("NotFoundClass");

}catch(ClassNotFoundException e){

e.printStackTrace();

}

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

显示加载的几种方式:

Class.forName();

ClassLoader的loadClass();

ClassLoader的findSystemClass();


UnsatisfiedLinkError

这个问题不常见,出现的场景就是调用某些本地lib中的native方法时,没有找到这样的lib。报出的错误。
/**

* 文件描述:

* 作者: bamboo

* 时间: 2016/12/9

*/

public class NoLibException {

public native void nativeMethod();

static {

System.loadLibrary("NoLib");

}

public static void main(String[] args) {

new NoLibException().nativeMethod();

}

}
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
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
java.lang.UnsatisfiedLinkError: no NoLib in java.library.path

at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1886)

at java.lang.Runtime.loadLibrary0(Runtime.java:849)

at java.lang.System.loadLibrary(System.java:1088)

at CCF.NoLibException.<clinit>(NoLibException.java:11)
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9


ClassCastException

强制转换异常,比如说想把String强制转换为Integer

代码
import java.util.HashMap;

import java.util.Map;

/**

* 文件描述:

* 作者: bamboo

* 时间: 2016/12/9

*/

public class CastException {

public static Map m = new HashMap() {

{

put("a", "2");

}

};

public static void main(String[] args) {

Integer integer= (Integer) m.get("a");

System.out.println(integer);

}

}
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
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

结果:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

at CCF.CastException.main(CastException.java:19)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:606)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13


实现自己的ClassLoader

之前写的博客。

http://blog.csdn.net/newpidian/article/details/52831121 主要实现以下3个功能

获取类加载器

根据类名称加载类

获取指定包下的所有类


加载自定义格式的Class文件

实现一个从远端服务器,经过加密的class文件的网络传输,在本地解密,并通过defineClass方法创建这个类的实例的方法。

代码如下:
import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.InputStream;

import java.net.URL;

/**

* 文件描述:

* 作者: bamboo

* 时间: 2016/12/9

*/

public class NetClassLoader extends ClassLoader {

private String classPath;

private String packageName;

public NetClassLoader(ClassLoader parent, String classPath, String packageName) {

super(parent);

this.classPath = classPath;

this.packageName = packageName;

}

public NetClassLoader(String classPath, String packageName) {

this.classPath = classPath;

this.packageName = packageName;

}

@Override

protected Class<?> findClass(String name) throws ClassNotFoundException {

Class<?> aClass = findLoadedClass(name);

if (aClass != null) {

return aClass;

}

if (packageName.startsWith(name)) {

byte[] classData = getData(name);

if (classData == null) {

throw new ClassNotFoundException();

} else {

defineClass(name, classData, 0, classData.length);

}

}

return super.loadClass(name);

}

private byte[] getData(String className) {

String path = classPath + File.separator + className.replace('.', File.separatorChar) + ".class";

try {

URL url = new URL(path);

InputStream is = url.openStream();

ByteArrayOutputStream stream = new ByteArrayOutputStream();

byte[] buffer = new byte[2048];

int num = 0;

while ((num = is.read(buffer)) != -1) {

stream.write(buffer, 0, num);

}

return stream.toByteArray();

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

private byte[] deCode(byte[] src) {

byte[] decode = null;

///字节编码处理

return decode;

}

}
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136


实现类的热部署

在JVM加载类之前会先判定要请求的类是否已经被加载过来,就是通过findLoadedClass()来返回类实例,如果已经加载了,则loadClass()会冲突,如何判断是不是同一个类有2个条件。

类名是否完全一样,包括包名。

看这个类的ClassLoader是否完全一样。这里指的是是不是同一个ClassLoader实例。即使是同一个ClassLoader类的不同实例,加载的类也不一样。

所以实现热部署,可以创建不同的ClassLoader的实例对象,然后通过这个不同的实例对象来加载同名类。
import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.InputStream;

/**

* 文件描述:

* 作者: bamboo

* 时间: 2016/12/9

*/

public class ClassReloader extends ClassLoader {

private String classPath;

public ClassReloader(ClassLoader parent, String classPath) {

super(parent);

this.classPath = classPath;

}

public ClassReloader(String classPath) {

this.classPath = classPath;

}

@Override

protected Class<?> findClass(String name) throws ClassNotFoundException {

byte[] classData = getData(name);

if (classData == null) {

throw new ClassNotFoundException();

} else {

return defineClass(name, classData, 0, classData.length);

}

}

private byte[] getData(String className) {

String path = classPath +  className+".class";

try {

InputStream is = new FileInputStream(path);

ByteArrayOutputStream stream = new ByteArrayOutputStream();

byte[] buffer = new byte[2048];

int num = 0;

while ((num = is.read(buffer)) != -1) {

stream.write(buffer, 0, num);

}

return stream.toByteArray();

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

public static void main(String[] args) {

try {

String path = "C:\\Users\\bamboo\\Desktop\\单源\\Leetcode\\out\\";

ClassReloader reloader = new ClassReloader(path);

Class r = reloader.findClass("AllOne");

System.out.println((r.newInstance()));

ClassReloader reloader1 = new ClassReloader(path);

Class r1 = reloader1.findClass("AllOne");

System.out.println((r1.newInstance()));

} catch (Exception 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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

输出
AllOne@26a30589

AllOne@ec5aba9


如果将 
Class r1 = reloader1.findClass("AllOne");
改为
Class r1 = reloader.findClass("AllOne");


则会抛出duplicate class definition,类的重复定义。
AllOne@642423ad

Exception in thread "main" java.lang.LinkageError: loader (instance of  ParallelBasic/ClassReloader): attempted  duplicate class definition for name: "AllOne"

at java.lang.ClassLoader.defineClass1(Native Method)

at java.lang.ClassLoader.defineClass(ClassLoader.java:800)

at java.lang.ClassLoader.defineClass(ClassLoader.java:643)

at ParallelBasic.ClassReloader.findClass(ClassReloader.java:32)

at ParallelBasic.ClassReloader.main(ClassReloader.java:61)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:606)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

一个问题:如果使用不同的ClassLoader实例加载同一个类会怎么样,会不会导致JVM的PermGen(永久代)n区无限增大??答案是否定的,why,因为ClassLoader对象也是对象,在没有被持有引用的时候,也会被JVM回收。值得注意的是,被ClassLoader加载的类的字节码会一直保存在JVM的PermGen中,这个数据一般是在Full GC的时候才会被回收,所以,如果应用大量使用动态类加载。Full GC又不是太频繁,也要注意PermGen的大小,防止内存溢出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: