- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章JAVA提高第七篇 类加载器解析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
今天我们学习类加载器,关于类加载器其实和jvm有很大关系,在这里这篇文章只是简单的介绍下类加载器,后面学习到jvm的时候还会详细讲到类加载器,本文分为下面几个小节讲解:
1、认识类加载器 。
1.什么是类加载器?
所谓的类加载器可以从其作用来理解,其功能就是将classpath目录下.class文件,加载到内存中来进行一些处理,处理完的结果就是一些字节码.那是谁把这些class类加载到内存中来的呢?就是类加载器.
2.jvm中默认的类加载器有哪些?
java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类加载器负责加载不同位置的类:bootstrap,extclassloader,appclassloader 。
注意的是:
1.类加载器本身也是一个java类,因为类加载器本身也是一个java类,那么这个特殊的java类【类加载器】是有谁加载进来的呢?这显然要有第一个类加载器,这第一个类加载器不是一个java类,它是bootstrap.
2.bootstrap不是一个java类,不需要类加载器java加载,他是嵌套在java虚拟机内核里面的。java 虚拟机内核已启动的时候,他就已经在那里面了,他是用c++语言写的一段二进制代码。他可以去加载别的类,其中别的类就包含了类加载器【如上面提到的ext 和 app】.
案例:
下面我们写个例子来获取classloadertest这个类的类加载器的名字,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package
study.javaenhance;
import
java.util.arraylist;
public
class
classloadertest
{
public
static
void
main(string[] args)
throws
exception
{
//获取类加载器,那么这个获取的是一个实例对象,我们知道类加载器也有很多种,那么因此也有其对应的类存在,因此可以获取到对应的字节码
system.out.println(classloadertest.
class
.getclassloader());
//获取类加载的字节码,然后获取到类加载字节码的名字
system.out.println(classloadertest.
class
.getclassloader().getclass().getname());
//下面我们看下获取非我们定义的类,比如system arraylist 等常用类
system.out.println(system.
class
.getclassloader());
system.out.println(arraylist.
class
.getclassloader());
}
}
|
结果如下:
sun.misc.launcher$appclassloader@1c78e57 sun.misc.launcher$appclassloader null null 。
结果分析:
classloadertest的类加载器的名称是appclassloader。也就是这个类是由appclassloader这个类加载器加载的。 system/arraylist的类加载器是null。这说明这个类加载器是由bootstrap加载的。因为我们上面说了bootstrap不是java类,不需要类加载器加载。所以他的类加载器是null。 ================================== 我们说了java给我们提供了三种类加载器:bootstrap,extclassloader,appclassloader。这三种类加载器是有父子关系组成了一个树形结构。bootstrap是根节点,bootstrap下面挂着extclassloader,extclassloader下面挂着appclassloader. 。
代码演示如下:
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
|
package
study.javaenhance;
import
java.util.arraylist;
public
class
classloadertest
{
public
static
void
main(string[] args)
throws
exception
{
//获取类加载器,那么这个获取的是一个实例对象,我们知道类加载器也有很多种,那么因此也有其对应的类存在,因此可以获取到对应的字节码
system.out.println(classloadertest.
class
.getclassloader());
//获取类加载的字节码,然后获取到类加载字节码的名字
system.out.println(classloadertest.
class
.getclassloader().getclass().getname());
//下面我们看下获取非我们定义的类,比如system arraylist 等常用类
system.out.println(system.
class
.getclassloader());
system.out.println(arraylist.
class
.getclassloader());
//演示java 提供的类加载器关系
classloader classloader = classloadertest.
class
.getclassloader();
while
(classloader !=
null
)
{
system.out.print(classloader.getclass().getname()+
"-->"
);
classloader = classloader.getparent();
}
system.out.println(classloader);
}
}
|
输出结果为:
sun.misc.launcher$appclassloader-->sun.misc.launcher$extclassloader-->null 。
通过这段程序可以看出来,classloadertest由appclassloader加载,appclassloader的父类节点是extclassloader,extclassloader的父节点是bootstrap.
每一个类加载器都有自己的管辖范围。 bootstrap根节点,只负责加载rt.jar里的类,刚刚那个system就是属于rt.jar包里面的,extclassloader负责加载jre/lib/ext/*.jar这个目录文件夹下的文件。而appclassloader负责加载classpath目录下的所有jar文件及目录。 最后一级是我们自定义的加载器,他们的父类都是appclassloader.
2、类加载器的双亲委派机制 。
除了系统自带了类加载器,我们还可以自定义类加载器。然后把自己的类加载器挂在树上。作为某个类加载器的孩子。所有自定义类加载器都要继承classloader。实现里面的一个方法classloader()如下:
通过上面的知识,我们知道java提供了三个类加载器,而且我们也可以自定义类加载器,并且通过上面的类加载图也看到了之前的关系,那么对于一个类的.class 到底是谁去加载呢?
当java虚拟机要加载第一个类的时候,到底派出哪个类加载器去加载呢?
(1). 首先当前线程的类加载器去加载线程中的第一个类(当前线程的类加载器:thread类中有一个get/setcontextclassloader(classloader cl);方法,可以获取/指定本线程中的类加载器) 。
(2). 如果类a中引用了类b,java虚拟机将使用加载类a的类加载器来加载类b 。
(3). 还可以直接调用classloader.loadclass(string classname)方法来指定某个类加载器去加载某个类 。
每个类加载器加载类时,又先委托给其上级类加载器当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则会抛出classnotfoundexception,不是再去找发起者类加载器的儿子,因为没有getchild()方法。例如:如上图所示: myclassloader->appclassloader->ext->classloader->bootstrap.自定定义的myclassloader1首先会先委托给appclassloader,appclassloader会委托给extclassloader,extclassloader会委托给bootstrap,这时候bootstrap就去加载,如果加载成功,就结束了。如果加载失败,就交给extclassloader去加载,如果extclassloader加载成功了,就结束了,如果加载失败就交给appclassloader加载,如果加载成功,就结束了,如果加载失败,就交给自定义的myclassloader1类加载器加载,如果加载失败,就报classnotfoundexception异常,结束.
这样的好处在哪里呢?可以集中管理,不会出现多份字节码重复的现象。有两个类要再在system,如果让底层的类加载器加载,可能会出现两份字节码。而都让爷爷加载,爷爷加载到已有,当再有请求过来的时候,爷爷说:哎,我加载过啊,直接把那份拿出来给你用啊。就不会出现多份字节码重复的现象.
现在有一道面试题:能不能自己写一套java.lang.system.?
分析:你写了也白写,因为类加载器加载,直接到爷爷那里去找,找成功了,分本就不回来理你的那个。 答案:通常不可以,因为委托机制委托给爷爷,爷爷在rt.jar包加载到这个类以后就不会加载你自己写了那个system类了。但是,我也有办法加载,我写一个自己的类加载器,不让他用委托机制,不委托给上级了,就可以了. 。
因为system类,list,map等这样的系统提供jar类都在rt.jar中,所以由bootstrap类加载器加载,因为bootstrap是祖先类,不是java编写的,所以打印出class为null 。
对于classloadertest类的加载过程,打印结果也是很清楚的.
3、自定义类加载器 。
下面来看一下怎么定义我们自己的一个类加载器myclassloader:
自定义的类加载器必须继承抽象类classloader然后重写findclass方法,其实他内部还有一个loadclass方法和defineclass方法,这两个方法的作用是:
loadclass方法的源代码:
1
2
3
|
public
class
<?> loadclass(string name)
throws
classnotfoundexception {
return
loadclass(name,
false
);
}
|
再来看一下loadclass(name,false)方法的源代码:
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
|
protected
class
<?> loadclass(string name,
boolean
resolve)
throws
classnotfoundexception{
//加上锁,同步处理,因为可能是多线程在加载类
synchronized
(getclassloadinglock(name)) {
//检查,是否该类已经加载过了,如果加载过了,就不加载了
class
c = findloadedclass(name);
if
(c ==
null
) {
long
t0 = system.nanotime();
try
{
//如果自定义的类加载器的parent不为null,就调用parent的loadclass进行加载类
if
(parent !=
null
) {
c = parent.loadclass(name,
false
);
}
else
{
//如果自定义的类加载器的parent为null,就调用findbootstrapclass方法查找类,就是bootstrap类加载器
c = findbootstrapclassornull(name);
}
}
catch
(classnotfoundexception e) {
// classnotfoundexception thrown if class not found
// from the non-null parent class loader
}
if
(c ==
null
) {
// if still not found, then invoke findclass in order
// to find the class.
long
t1 = system.nanotime();
//如果parent加载类失败,就调用自己的findclass方法进行类加载
c = findclass(name);
// this is the defining class loader; record the stats
sun.misc.perfcounter.getparentdelegationtime().addtime(t1 - t0);
sun.misc.perfcounter.getfindclasstime().addelapsedtimefrom(t1);
sun.misc.perfcounter.getfindclasses().increment();
}
}
if
(resolve) {
resolveclass(c);
}
return
c;
}
}
|
在loadclass代码中也可以看到类加载机制的原理,这里还有这个方法findbootstrapclassornull,看一下源代码:
1
2
3
4
5
6
|
private
class
findbootstrapclassornull(string name)
{
if
(!checkname(name))
return
null
;
return
findbootstrapclass(name);
}
|
就是检查一下name是否是否正确,然后调用findbootstrapclass方法,但是findbootstrapclass方法是个native本地方法,看不到源代码了,但是可以猜测是用bootstrap类加载器进行加载类的,这个方法我们也不能重写,因为如果重写了这个方法的话,就会破坏这种委托机制,我们还要自己写一个委托机制.
defineclass这个方法很简单就是将class文件的字节数组编程一个class对象,这个方法肯定不能重写,内部实现是在c/c++代码中实现的findclass这个方法就是根据name来查找到class文件,在loadclass方法中用到,所以我们只能重写这个方法了,只要在这个方法中找到class文件,再将它用defineclass方法返回一个class对象即可.
这三个方法的执行流程是:每个类加载器:loadclass->findclass->defineclass 。
前期的知识了解后现在就来实现了 。
首先来看一下需要加载的一个类:classloaderattachment.java
1
2
3
4
5
6
7
8
9
10
|
package
study.javaenhance;
public
class
classloaderattachment {
@override
public
string tostring() {
return
"hello classloader!"
;
}
}
|
这个类中输出一段话即可:编译成classloaderattachment.class 。
再来看一下自定义的myclassloader.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
|
package
study.javaenhance;
import
java.io.bytearrayoutputstream;
import
java.io.fileinputstream;
import
java.io.fileoutputstream;
import
java.io.inputstream;
import
java.io.outputstream;
public
class
myclassloader
extends
classloader
{
//需要加载类.class文件的目录
private
string classdir;
//无参的构造方法,用于class.newinstance()构造对象使用
public
myclassloader(){
}
public
myclassloader(string classdir){
this
.classdir = classdir;
}
@override
protected
class
<?> findclass(string name)
throws
classnotfoundexception {
system.out.println(name);
string classpathfile = classdir +
"\\"
+ name.substring(name.lastindexof(
"."
)+
1
) +
".class"
;
system.out.println(classpathfile);
try
{
system.out.println(
"my"
);
//将class文件进行解密
fileinputstream fis =
new
fileinputstream(classpathfile);
bytearrayoutputstream bos =
new
bytearrayoutputstream();
encodeanddecode(fis,bos);
byte
[] classbyte = bos.tobytearray();
//将字节流变成一个class
return
defineclass(classbyte,
0
,classbyte.length);
}
catch
(exception e)
{
e.printstacktrace();
}
return
super
.findclass(name);
}
//测试,先将classloaderattachment.class文件加密写到工程的class_temp目录下
public
static
void
main(string[] args)
throws
exception{
//配置运行参数
string srcpath = args[
0
];
//classloaderattachment.class原路径
string despath = args[
1
];
//classloaderattachment.class输出的路径
string desfilename = srcpath.substring(srcpath.lastindexof(
"\\"
)+
1
);
string despathfile = despath +
"/"
+ desfilename;
fileinputstream fis =
new
fileinputstream(srcpath);
fileoutputstream fos =
new
fileoutputstream(despathfile);
//将class进行加密
encodeanddecode(fis,fos);
fis.close();
fos.close();
}
/**
* 加密和解密算法
* @param is
* @param os
* @throws exception
*/
private
static
void
encodeanddecode(inputstream is,outputstream os)
throws
exception{
int
bytes = -
1
;
while
((bytes = is.read())!= -
1
){
bytes = bytes ^
0xff
;
//和0xff进行异或处理
os.write(bytes);
}
}
}
|
这个类中定义了一个加密和解密的算法,很简单的,就是将字节和oxff异或一下即可,而且这个算法是加密和解密的都可以用! 。
当然我们还要先做一个操作就是,将classloaderattachment.class加密后的文件存起来,也就是在main方法中执行的,这里我是在项目中新建一个 。
同时采用的是参数的形式来进行赋值的,所以在运行的myclassloader的时候要进行输入参数的配置:右击myclassloader->run as -> run configurations 。
。
第一个参数是classloaderattachment.class文件的源路径,第二个参数是加密后存放的目录,运行myclassloader之后,刷新class_temp文件夹,出现了classloaderattachment.class,这个是加密后的class文件.
下面来看一下测试类: 。
。
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
|
package
study.javaenhance;
import
java.util.arraylist;
public
class
classloadertest
{
public
static
void
main(string[] args)
throws
exception
{
//获取类加载器,那么这个获取的是一个实例对象,我们知道类加载器也有很多种,那么因此也有其对应的类存在,因此可以获取到对应的字节码
system.out.println(classloadertest.
class
.getclassloader());
//获取类加载的字节码,然后获取到类加载字节码的名字
system.out.println(classloadertest.
class
.getclassloader().getclass().getname());
//下面我们看下获取非我们定义的类,比如system arraylist 等常用类
system.out.println(system.
class
.getclassloader());
system.out.println(arraylist.
class
.getclassloader());
//演示java 提供的类加载器关系
classloader classloader = classloadertest.
class
.getclassloader();
while
(classloader !=
null
)
{
system.out.print(classloader.getclass().getname()+
"-->"
);
classloader = classloader.getparent();
}
system.out.println(classloader);
try
{
//class classdate = new myclassloader("class_temp").loadclass("classloaderattachment");
class
classdate =
new
myclassloader(
"class_temp"
).loadclass(
"study.javaenhance.classloaderattachment"
);
object object = classdate.newinstance();
//输出classloaderattachment类的加载器名称
system.out.println(
"classloader:"
+object.getclass().getclassloader().getclass().getname());
system.out.println(object);
}
catch
(exception e1) {
e1.printstacktrace();
}
}
}
|
结果如下:
sun.misc.launcher$appclassloader@6b97fd sun.misc.launcher$appclassloader null null sun.misc.launcher$appclassloader-->sun.misc.launcher$extclassloader-->null classloader:sun.misc.launcher$appclassloader hello classloader.
这个时候我们会发现调用的app 的类加载器然后输出了结果,这个是正常的,因为这个时候会采用双亲委派机制.
那么这个时候,我们将自己生成的classloaderattachemet class文件,覆盖掉编译的时候生成的class 文件看下结果如何,如果正常应该会报错,因为这个时候走双亲委派机制在对应的classpath 是可以找到这个class 文件,因此app类加载器会处理,但是因为我们的class 是加密的因此会报错,运行结果如:
那么如何让其走到我们自定义的类加载器呢,只需要将编译时候生成的目录下的.class 文件删掉即可,那么这个是app加载不到,则会去调用findclass ,然后就会走到我们定义的类加载器中,运行结果如下:
参考资料:
张孝祥老师java增强视频 。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我.
原文链接:http://www.cnblogs.com/pony1223/p/7711092.html 。
最后此篇关于JAVA提高第七篇 类加载器解析的文章就讲到这里了,如果你想了解更多关于JAVA提高第七篇 类加载器解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我之前让 dll 注入(inject)器变得简单,但我有 Windows 7,我用 C# 和 C++ 做了它,它工作得很好!但是现在当我在 Windows 8 中尝试相同的代码时,它似乎没有以正确的方
我正在尝试制作一个名为 core-splitter 的元素,该元素在 1.0 中已弃用,因为它在我们的项目中起着关键作用。 如果您不知道 core-splitter 的作用,我可以提供一个简短的描述。
我有几个不同的蜘蛛,想一次运行所有它们。基于 this和 this ,我可以在同一个进程中运行多个蜘蛛。但是,我不知道如何设计一个信号系统来在所有蜘蛛都完成后停止 react 器。 我试过了: cra
有没有办法在达到特定条件时停止扭曲 react 器。例如,如果一个变量被设置为某个值,那么 react 器应该停止吗? 最佳答案 理想情况下,您不会将变量设置为一个值并停止 react 器,而是调用
https://code.angularjs.org/1.0.0rc9/angular-1.0.0rc9.js 上面的链接定义了外部js文件,我不知道Angular-1.0.0rc9.js的注入(in
我正在尝试运行一个函数并将服务注入(inject)其中。我认为这可以使用 $injector 轻松完成.所以我尝试了以下(简化示例): angular.injector().invoke( [ "$q
在 google Guice 中,我可以使用函数 createInjector 创建基于多个模块的注入(inject)器。 因为我使用 GWT.create 在 GoogleGin 中实例化注入(in
我在 ASP.NET Core 1.1 解决方案中使用配置绑定(bind)。基本上,我在“ConfigureServices Startup”部分中有一些用于绑定(bind)的简单代码,如下所示: s
我在 Spring MVC 中设置 initBinder 时遇到一些问题。我有一个 ModelAttribute,它有一个有时会显示的字段。 public class Model { privat
我正在尝试通过jquery post发布knockoutjs View 模型 var $form = $('#barcodeTemplate form'); var data = ko.toJS(vm
如何为包含多态对象集合的复杂模型编写自定义模型绑定(bind)程序? 我有下一个模型结构: public class CustomAttributeValueViewModel { publi
您好,我正在尝试实现我在 this article 中找到的扩展方法对于简单的注入(inject)器,因为它不支持开箱即用的特定构造函数的注册。 根据这篇文章,我需要用一个假的委托(delegate)
你好,我想自动注册我的依赖项。 我现在拥有的是: public interface IRepository where T : class public interface IFolderReposi
我正在使用 Jasmine 测试一些 Angular.js 代码。为此,我需要一个 Angular 注入(inject)器: var injector = angular.injector(['ng'
我正在使用 Matlab 代码生成器。不可能包含代码风格指南。这就是为什么我正在寻找一个工具来“ reshape ”、重命名和重新格式化生成的代码,根据我的: 功能横幅约定 文件横幅约定 命名约定 等
这个问题在这里已经有了答案: Where and why do I have to put the "template" and "typename" keywords? (8 个答案) 关闭 8
我开发了一种工具,可以更改某些程序的外观。为此,我需要在某些进程中注入(inject)一个 dll。 现在我基本上使用这个 approach .问题通常是人们无法注入(inject) dll,因为他们
我想使用 swing、spring 和 hibernate 编写一个 java 应用程序。 我想使用数据绑定(bind)器用 bean 的值填充 gui,并且我还希望它反射(reflect) gui
我有这段代码,当两个蜘蛛完成后,程序仍在运行。 #!C:\Python27\python.exe from twisted.internet import reactor from scrapy.cr
要点是 Spring Batch (v2) 测试框架具有带有 @Autowired 注释的 JobLauncherTestUtils.setJob。我们的测试套件有多个 Job 类提供者。因为这个类不
我是一名优秀的程序员,十分优秀!