详细讲解java中的类加载器是什么_java类加载器包括几种1简介与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序
1 简介
与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class文件加载到JVM里运行,负责加载Java class的这部分就叫做Class Loader。
JVM 本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,Bootstrap ClassLoader是用本地代码实现的,它负责加载核心Java Class,即所有java.*开头的类,它搜索的范围为:jdk/jre/lib/*.jar。
另外JVM还会提供两个ClassLoader:ExtClassLoader和AppClassLoader。它们都是用 Java语言编写的,由Bootstrap ClassLoader加载,其中Extension ClassLoader负责加载扩展的Java Class类,包括所有javax.*开头的类和存放在jdk/jre/ext/*.jar目录下的类)。Application ClassLoader负责加载应用程序自身的类,它搜索的范围为:classPath指定的jar或者class类。如下图所示:
2 类加载器的父子关系
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。具体实现过程见下面的例子:
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
)
;
}
}
|
上面类的关系为:
将sample.class放到d:\loader1和d:\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
上面类的内部引用关系为:
4 类的互见性质
命名空间
每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
运行时包
由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。假设用户自己定义了一个类java.lang.Spy,并由用户自定义的类加载器加载,由于java.lang.Spy和核心类库 java.lang.*由不同的加载器加载,它们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。
类的互见
同一个命名空间内的类是互相可见的。子加载器的命名空间包含所有父加载器的命名空间,因此由子加载器加载的类能看见父加载器加载的类,例如系统类加载器加载的类能看见根类加载器加载的类;但是由父加载器加载的类不能看见子加载器加载的类;如果两个加载器之间没有直接或间接的父子关系,那么他们各自加载的类相互不可见。如下所示:
|
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 = (Sample)object;这一行;
MyClassLoader类由系统类加载器加载,而Sample类由loader1类加载器加载,因此MyClassLoader类看不见Sample 类。在MyClassLoader的main方法中使用Sample类,会导致NoClassDefFoundError错误。当两个不同命名空间内的类互相不可见时,可采用java的反射机制来访问对方实例的属性和方法。如下:
Field field = clazz.getField(“v1”);
int v1 = field.getInt(object);
System.out.println(v1);
5 资源的加载
每一个.class文件加载都要用到类加载器,加载器也可以加载其他文件。
|
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对象始终是可触及的。
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