类加载机制概述
什么是虚拟机的类加载机制?
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行检验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型(数据类型)。
在java语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略虽然会使得类在加载时增加一点性能开销,但是会为java应用程序提供高度的灵活性,java天生可以动态扩展的语言特性就是依赖于
运行期
动态加载和
动态连接的这个特点实现的。
java虚拟机类加载机制
——
一个类的生命周期图
虚拟机的类加载机制:虚拟机把描述类的数据 从Class文件加载到内存,并对数据进行 校验、 转换 解析和 初始化,最终形成可以被虚拟机直接使用的java类型。
类加载的过程
1、
加载:“加载”是“类加载”过程的一个阶段;在加载阶段,虚拟机需要完成以下三件事情。
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
2、
验证:验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
3、
准备:准备阶段看是正式为类变量分配内存并设置类变量初始值(数据类型的零值)的阶段。
基本数据类型的零值如下表所示:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4、
解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。
直接引用:直接引用可以使直接指向目标的指针、相对偏移量或是一个能间接定位到的句柄。
5、
初始化:初始化阶段,才真正开始执行程序员在类中定义的java程序代码(或是字节码)。
类初始化阶段是类加载过程的最后一步,
除了前面的加载阶段可以由用户应用程序通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。
类加载器:它可以使java类被动态加载到java虚拟机中并执行。执行类的生命周期中的第一步。
类加载器的基本概念:顾名思义,
类加载器是用来加载java类到java虚拟机中。
一般来说,java虚拟机使用java类的方式如下:java源程序(.java文件)在经过java编译器编译之后就被转换成java字节代码(.class文件)。类加载器负责读取java字节代码,并转换成java.lang.Class类的一个实例,每个这样的实例用来表示一个java类,通过这个实例的newInstance()方法就可以创建出该类的一个对象(反射机制)。
流程如下图所示:
类加载器的由来:
虚拟机设计团队将
加载过程中的第一步“通过一个类的全限定名来获取描述此类的二进制字节流”这个
动作放到java虚拟机的外部去实现,为了使应用程序自己决定如何去获取所需要的类。而实现这个动作的
代码模块被称为“类加载器”
类与类加载器:
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其
在java虚拟机中的唯一性。
eg:要比较两个类是否相等,前提必须是两个类是由同一类加载器加载的。
双亲委派模型:
这里类加载器之间的父子关系一般不会以继承的关系来实现,而是使用组合关系来复用父类加载器的代码。
双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己加载这个类,而是把这个请求委派给自己的父类加载器去完成,只有当父类加载器无法完成这个加载请求时(它的搜索范围内没有找到这个类),子类才会尝试自己加载。
使用双亲委派模型的好处:java类随着他的类加载器一起具备了一种优先级的层次关系。
eg:例如类Object他存放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给最顶层的启动类加载器加载,因此object类在程序中的各种类加载器环境中都是同一个类。
自我理解:这种方式可以减少类加载器加载一个核心类的次数,提高效率。
从java虚拟机角度来看,只存在两种不同的类加载器:
- 启动类加载器——由C++实现(eg:HotSpot,不同的虚拟机实现方式不同),是虚拟机自身的一部分。
- 所有其他类加载器——这些类加载器由java语言实现,独立于虚拟机,并且全部继承于抽象类java.lang.Classloader。
开发人员角度的三种类加载器:
- 启动类加载器(Bootstrap ClassLoader):加载<JAVA_HOME>\lib目录中或者参数 -Xbootclasspath所指定路径中的类库到虚拟机内存中。
- 扩展类加载器(Extension ClassLoader):加载<JAVA_HOME>\lib\ext目录中或者被java.ext.dirs系统变量所指定的路径中的类库到虚拟机内存中。
- 应用程序类加载器(Application Classloader)——这个类加载器是由ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称为系统类加载器:加载用户类路径上所指定的类库到虚拟机内存中。
自定义类加载器:
源码分析:jdk中的ClassLoader的loadClass方法
方法介绍:使用指定的二进制名称来加载类,这个方法的默认实现按照以下顺序查找类: 调用findLoadedClass(String)方法检查这个类是否被加载过 使用父加载器调用loadClass(String)方法,如果父加载器为Null,类加载器装载虚拟机内置的加载器调用findClass(String)方法装载类, 如果,按照以上的步骤成功的找到对应的类,并且该方法接收的resolve参数的值为true,那么就调用resolveClass(Class)方法来处理类。 ClassLoader的子类最好覆盖findClass(String)而不是这个方法。
除非被重写,这个方法默认在整个装载过程中都是
同步
的(
线程安全
的)
protected
Class<?> loadClass(String
name
,
boolean
resolve
)
throws
ClassNotFoundException
{
synchronized
(getClassLoadingLock(
name
)) {
// First, check if the class has already been loaded
//首先,检查请求的类是否已经被加载过了
Class<?>
c
= findLoadedClass(
name
);
if
(
c
==
null
) {
long
t0
= System.
nanoTime
();//返回系统计时器的当前值,单位ms
try
{
//如果当前类加载器没有父类加载器,则使用启动类加载器加载该类
if
(
parent
!=
null
) {
c
=
parent
.loadClass(
name
,
false
);
}
else
{
c
= findBootstrapClassOrNull(
name
);//使用父类加载器加载该类
}
}
catch
(ClassNotFoundException
e
) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果启动类加载器无法加载该类,则通过调用自己的findClass方法加载该类
if
(
c
==
null
) {
// If still not found, then invoke findClass in order
// to find the class.
long
t1
= System.
nanoTime
();
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
;
}
}
从上面ClassLoader类的loadClass()方法源码可以看出:
一、通过继承ClassLoader并且实现其中的findClass()方法来实现自定义类加载器。
注意:
如果你是直接在当前项目里面创建,待Test.java编译后,请把Test.class文件拷贝走,再将Test.java删除。因为如果Test.class存放在当前项目中,根据双亲委派模型可知,会通过sun.misc.Launcher$AppClassLoader 类加载器加载。为了让我们自定义的类加载器加载,我们把Test.class文件放入到其他目录。
我在编译model类的时候,是在编译器上编的,为了避免错误,我将其编译后的class文件给移到F盘下的class文件中,但是因为该类是在编译器下写的,所以会有包路径(java中的一层包 == 磁盘路径的文件)的问题,因此这里需要在class文件中建立对应的文件路径。如下所示:
并且这里为了避免
1、使用编译器(eclipse)编写一个model类。
package edu.stu.kx.model;
import java.io.PrintStream;
import org.junit.Test;
public class Demo
{
public void hello()
{
System.out.println("我是被【" + getClass().getClassLoader().getClass() + "】加载进来的……");
}
@Test
public void test1() {
new Demo().hello();
}
}
2、编写一个继承于ClassLoader类的子类,并实现其findClass方法。
package
edu.stu.kx.core;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.RandomAccessFile;
public
class
MyClassLoader
extends
ClassLoader {
public
MyClassLoader() {
}
protected
Class<?> findClass(String
name
)
throws
ClassNotFoundException {
try
{
byte
[]
bytes
= loadByteByFileName(
name
);
Class<?>
c
=
this
.defineClass(
name
,
bytes
, 0,
bytes
.
length
);
return
c
;
}
catch
(Exception
e
) {
e
.printStackTrace();
}
return
super
.findClass(
name
);
}
//默认读取F盘下class文件中的.class文件
private
File
getClassFile(String
name
)
{
File
file
=
new
File(
"F:/class/"
+
name
);
return
file
;
}
//根据带路径的class文件名,读取文件,返回值为byte[]
private
byte
[]
loadByte(String
name
)
throws
Exception
{
name
=
name
.replaceAll(
"\\."
,
"/"
);
//将name中的"."替换为"/"
FileInputStream
fis
=
new
FileInputStream(
"F:/class"
+
"/"
+
name
+
".class"
);
int
len
=
fis
.available();
byte
[]
data
=
new
byte
[
len
];
fis
.read(
data
);
fis
.close();
return
data
;
}
//根据带路径的class文件名,读取文件,返回值为byte[]
private
byte
[] loadByteByFileName(String
name
)
throws
Exception {
name
=
name
.replaceAll(
"\\."
,
"/"
);
//将name中的"."替换为"/"
String
filePath
=
"F:/class"
+
"/"
+
name
+
".class"
;
File
file
=
new
File(
filePath
);
RandomAccessFile
raf
=
new
RandomAccessFile(
filePath
,
"r"
);
long
length
=
raf
.length();
byte
[]
resultByte
=
new
byte
[(
int
)
length
];
int
resultBytePointer
= 0;
long
restLength
=
length
;
while
(
restLength
> 0) {
byte
[]
tempByte
=
new
byte
[(
int
)(
restLength
> 1<<15 ? 1<<15 :
restLength
)];
int
readLength
=
raf
.read(
tempByte
, 0,
tempByte
.
length
);
System.
arraycopy
(
tempByte
, 0,
resultByte
,
resultBytePointer
,
readLength
);
resultBytePointer
+=
readLength
;
restLength
-= 1<<15;
}
return
resultByte
;
}
}
3、测试。
MyClassLoader
mcl
=
new
MyClassLoader();
Class<?>
c1
;
c1
=
mcl
.loadClass(
"edu.stu.kx.model.Demo"
);
Object
obj
=
c1
.newInstance();
Method
method
=
c1
.getMethod(
"hello"
,
null
)
;
method
.invoke(
obj
,
null
)
;
System.out.println(obj.getClass().getClassLoader());
4、结果。
<br><br>
机会是留给有准备的人的,放下该放下的,收获该收获的,努力,只为了遇到更好的自己。