详细讲解java中的类加载器是什么_java类加载器包括几种

详细讲解java中的类加载器是什么_java类加载器包括几种1简介与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序

1 简介

与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVMJava虚拟机),然后再把Java class文件加载到JVM里运行,负责加载Java class的这部分就叫做Class Loader

JVM 本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,Bootstrap ClassLoader是用本地代码实现的,它负责加载核心Java Class,即所有java.*开头的类,它搜索的范围为:jdk/jre/lib/*.jar

另外JVM还会提供两个ClassLoaderExtClassLoaderAppClassLoader。它们都是用 Java语言编写的,由Bootstrap ClassLoader加载,其中Extension ClassLoader负责加载扩展的Java Class类,包括所有javax.*开头的类和存放在jdk/jre/ext/*.jar目录下的类)。Application ClassLoader负责加载应用程序自身的类,它搜索的范围为:classPath指定的jar或者class类。如下图所示:

详细讲解Java中的类加载器 - 第1张 | IT江湖

2 类加载器的父子关系

Java

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

public 
class 
Person 
{
       
static 
       
{
              
System
.
out
.
println
(
“I am loading”
)
;
       
}
}
public 
class 
Test 
{
           
public 
static 
void 
main
(
String
[
] 
args
) 
{
              
Person 
p 
= 
new 
Person 
(
)
;
              
Class 
pclass 
= 
p
.
getClass
(
)
;
              
ClassLoader 
cl 
= 
pclass
.
getClassLoader
(
)
;
              
System
.
out
.
println
(
“person类加载器”
+
cl
)
;
              
System
.
out
.
println
(
“person类加载器的父类加载器”
+
cl
.
getParent
(
)
)
;
              
System
.
out
.
println
(
“person类加载器的祖父类加载器”
+
cl
.
getParent
(
)
.
getParent
(
)
)
;
       
}
}

I am loading

person类加载器sun.misc.Launcher$AppClassLoader@82ba41

person类加载器的父类加载器sun.misc.Launcher$ExtClassLoader@923e30

person类加载器的祖父类加载器null

注意:$说明是内部类。

3 委托加载机制

在Java的类加载器中,存在一种委托关系:当类加载器需要加载一个类的时候,会首先委托它的父类从其搜索路径中搜索相关类,如果找到,则加载父类加载器所找到的类,否则,才从自身的搜索路径中寻找相关的类,如果还是找不到,将会抛出一个ClassNotFoundException异常。注意:在这里的类加载器之间的委托是递归的,它将一层一层的往上委托,直到Bootstrap ClassLoader。具体实现过程见下面的例子:

Java

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

public 
class 
Sample
{
       
public 
int 
v1 
= 
1
;
 
       
public 
Sample
(
)
       
{
              
System
.
out
.
println
(
“Sample is load by :” 
+ 
this
.
getClass
(
)
.
getClassLoader
(
)
)
;
       
}
}
import 
java
.
io
.
ByteArrayOutputStream
;
import 
java
.
io
.
File
;
import 
java
.
io
.
FileInputStream
;
import 
java
.
io
.
InputStream
;
 
public 
class 
MyClassLoader 
extends 
ClassLoader
{
       
private 
String 
name
;
       
private 
String 
path 
= 
“d:\\”
;
       
private 
final 
String 
fileType 
= 
“.class”
;
 
       
public 
MyClassLoader
(
String 
name
)
       
{
              
super
(
)
;
              
this
.
name 
= 
name
;
       
}
 
       
public 
MyClassLoader
(
ClassLoader 
parent
, 
String 
name
)
       
{
              
super
(
parent
)
;
              
this
.
name 
= 
name
;
       
}
 
       
@Override
       
public 
String 
toString
(
)
       
{
              
return 
this
.
name
;
       
}
 
       
public 
String 
getName
(
)
       
{
              
return 
name
;
       
}
 
       
public 
void 
setName
(
String 
name
)
       
{
              
this
.
name 
= 
name
;
       
}
 
       
public 
String 
getPath
(
)
       
{
              
return 
path
;
       
}
 
       
public 
void 
setPath
(
String 
path
)
       
{
              
this
.
path 
= 
path
;
       
}
 
       
public 
String 
getFileType
(
)
       
{
              
return 
fileType
;
       
}
 
       
protected 
Class
<
?
> 
findClass
(
String 
name
) 
throws 
ClassNotFoundException
       
{
              
byte
[
] 
data 
= 
this
.
loadClassData
(
name
)
;
 
              
return 
this
.
defineClass
(
name
, 
data
, 
0
, 
data
.
length
)
;
       
}
 
       
private 
byte
[
] 
loadClassData
(
String 
name
)
       
{
              
InputStream 
is 
= 
null
;
              
byte
[
] 
data 
= 
null
;
              
ByteArrayOutputStream 
baos 
= 
null
;
              
try
              
{
                     
this
.
name 
= 
this
.
name
.
replace
(
“.”
, 
“\\”
)
;
                     
is 
= 
new 
FileInputStream
(
new 
File
(
path 
+ 
name 
+ 
fileType
)
)
;
                     
baos 
= 
new 
ByteArrayOutputStream
(
)
;
                     
int 
ch 
= 
0
;
                     
while 
(

1 
!= 
(
ch 
= 
is
.
read
(
)
)
)
                     
{
                            
baos
.
write
(
ch
)
;
                     
}
                     
data 
= 
baos
.
toByteArray
(
)
;
 
              
}
              
catch 
(
Exception 
e
)
              
{
                     
e
.
printStackTrace
(
)
;
              
}
              
finally
              
{
                     
try
                     
{
                            
is
.
close
(
)
;
                            
baos
.
close
(
)
;
                     
}
                     
catch 
(
Exception 
e
)
                     
{
                            
e
.
printStackTrace
(
)
;
                     
}
              
}
 
              
return 
data
;
       
}
 
       
public 
static 
void 
showClassLoader
(
ClassLoader 
loader
) 
throws 
Exception
       
{
              
Class 
clazz 
= 
loader
.
loadClass
(
“Sample”
)
;
              
Object 
object 
= 
clazz
.
newInstance
(
)
;
       
}
 
       
public 
static 
void 
main
(
String
[
] 
args
) 
throws 
Exception
       
{
              
MyClassLoader 
loader1 
= 
new 
MyClassLoader
(
“loader1”
)
;
              
loader1
.
setPath
(
“d:\\loader1\\”
)
;
              
MyClassLoader 
loader2 
= 
new 
MyClassLoader
(
loader1
, 
“loader2”
)
;
              
loader2
.
setPath
(
“d:\\loader2\\”
)
;
              
MyClassLoader 
loader3 
= 
new 
MyClassLoader
(
null
, 
“loader3”
)
;
              
loader3
.
setPath
(
“d:\\loader3\\”
)
;
              
showClassLoader
(
loader2
)
;
              
showClassLoader
(
loader3
)
;
       
}
 
}

上面类的关系为:

详细讲解Java中的类加载器 - 第2张 | IT江湖

将sample.class放到d:\loader1d:\loader3中,运行结果为:

Sample is load by :sun.misc.Launcher$AppClassLoader@3e25a5

Sample is load by :loader3

但是将classpath中的sample.class文件删除之后,运行结果为:

Sample is load by :loader1

Sample is load by :loader3

上面类的内部引用关系为:

详细讲解Java中的类加载器 - 第3张 | IT江湖

4 类的互见性质

命名空间

每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。

运行时包

由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。假设用户自己定义了一个类java.lang.Spy,并由用户自定义的类加载器加载,由于java.lang.Spy和核心类库 java.lang.*由不同的加载器加载,它们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。

类的互见

同一个命名空间内的类是互相可见的。子加载器的命名空间包含所有父加载器的命名空间,因此由子加载器加载的类能看见父加载器加载的类,例如系统类加载器加载的类能看见根类加载器加载的类;但是由父加载器加载的类不能看见子加载器加载的类;如果两个加载器之间没有直接或间接的父子关系,那么他们各自加载的类相互不可见。如下所示:

Java

1
2
3
4
5
6
7
8
9

public 
static 
void 
main
(
String
[
] 
args
) 
throws 
Exception
{
              
MyClassLoader 
loader1 
= 
new 
MyClassLoader
(
“loader1”
)
;
              
loader1
.
setPath
(
“d:\\loader1\\”
)
;
              
Class 
clazz 
= 
loader1
.
loadClass
(
“Sample”
)
;
              
Object 
object 
= 
clazz
.
newInstance
(
)
;
              
Sample 
sample 
= 
(
Sample
) 
object
;
              
System
.
out
.
println
(
sample
.
v1
)
;
}

运行结果为:

Sample is load by :loader1

Exception in thread “main” java.lang.NoClassDefFoundError: Sample

错误在于Sample sample = Sampleobject;这一行;

MyClassLoader类由系统类加载器加载,而Sample类由loader1类加载器加载,因此MyClassLoader类看不见Sample 类。在MyClassLoadermain方法中使用Sample类,会导致NoClassDefFoundError错误。当两个不同命名空间内的类互相不可见时,可采用java的反射机制来访问对方实例的属性和方法。如下:

Field field = clazz.getField(“v1”);

int v1 = field.getInt(object);

System.out.println(v1);

5 资源的加载

每一个.class文件加载都要用到类加载器,加载器也可以加载其他文件。

Java

1
2
3
4
5
6
7

public 
class 
Test
{
public 
static 
void 
main
(
String
[
] 
args
) 
throws 
FileNotFoundException
{
       
InputStream 
is 
= 
Test
.
class
.
getClassLoader
(
)
.
getResourceAsStream
(
“config.properties”
)
;
}
}

6 类的卸载

当Sample类被加载、连接和初始化后,他的生命周期就开始了。当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,Sample类在方法区内的数据也会被卸载,从而结束Sample类的生命周期。由此可见,一个类何时结束生命周期,取决于代表他的Class对象何时结束生命周期。

java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载,java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。

Java

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

public 
static 
void 
main
(
String
[
] 
args
) 
throws 
Exception
       
{
              
MyClassLoader 
loader1 
= 
new 
MyClassLoader
(
“loader1”
)
;
              
loader1
.
setPath
(
“d:\\loader1\\”
)
;
              
Class 
clazz 
= 
loader1
.
loadClass
(
“Sample”
)
;
              
System
.
out
.
println
(
clazz
.
hashCode
(
)
)
;
              
Object 
obj 
= 
clazz
.
newInstance
(
)
;
              
loader1 
= 
null
;
              
clazz 
= 
null
;
              
obj 
= 
null
;
              
loader1 
= 
new 
MyClassLoader
(
“loader1”
)
;
              
loader1
.
setPath
(
“d:\\loader1\\”
)
;
              
clazz 
= 
loader1
.
loadClass
(
“Sample”
)
;
              
System
.
out
.
println
(
clazz
.
hashCode
(
)
)
;
              
obj 
= 
clazz
.
newInstance
(
)
;
       
}

运行结果一为:

Sample is load by :sun.misc.Launcher$AppClassLoader@3e25a5

Sample is load by :sun.misc.Launcher$AppClassLoader@3e25a5

 

运行结果二为:

Sample is load by :loader1

Sample is load by :loader1

7线程上下文类加载器

线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader() setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

今天的文章
详细讲解java中的类加载器是什么_java类加载器包括几种分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/89767.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注