虚拟机的类加载机制
这篇文章是学习《深入理解JAVA虚拟机》中的一些个人认为比较重要的知识点的总结
关于类的初始化
类的生命周期: 加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用和卸载(Using and Unloading)
-
加载:获取类的二进制字节流
-
验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求
-
准备:为类变量分配内存并设置类变量初始值(不包含实例变量)
-
解析:将常量池内的符号引用替换为直接引用的过程
-
初始化:类加载过程的最后一步,开始执行类中定义的Java程序代码
-
使用:
-
卸载:
5种触发类进行初始化的场景:
-
new(使用new初始化一个类)、getStatic\putStatic(获取或设置一个类的静态字段)、invokeStatic(调用一个类的静态方法)
-
使用java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先出发其初始化
-
初始化一个类的时候,如果发现其父类还没进行过初始化,则需要先触发其父类的初始化
-
虚拟机启动时,用户需要指定一个要执行的(包含main()方法的那个类),虚拟机会先初始化这个主类
-
略
其中一个demo
public class NotInitialization1 {
public static void main(String[] args) {
/**
* 例子一:
* 1. 读取非final修饰的静态字段时,如果类未被初始化则去初始化
* 2. 当初始化一个类的时候,如果发现父类还未被初始化,则需要先出发父类的初始化
* 3. 通过子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化
* @param args
*/
System.out.println(SubClass.value);
}
}
public class SubClass extends SuperClass {
static {
System.out.println("SubClass init!");
}
}
public class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
运行结果:
SuperClass init!
123
关于类加载器 Classloader
虚拟机设计团队把类加载阶段中“通过一个类的名称来获取描述此类的二进制字节流”,放到虚拟机外部去实现。这个代码模块成为“类加载器”。
注意: 即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类必定不相等。
import java.io.IOException;
import java.io.InputStream;
/**
* 示例:
* 即使这两个类都来源于同一个Class文件,但是属于两个不同的类加载器
* 一个是由系统应用程序类加载器加载的,另一个由自定义的类加载器
*
* 结论:
* 即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类必定不相等
*
*/
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
//自定义一个类加载器
ClassLoader myLoader = new ClassLoader() {
/**
* Loads the class with the specified <a href="#name">binary name</a>.
* This method searches for classes in the same manner as the {@link
* #loadClass(String, boolean)} method. It is invoked by the Java virtual
* machine to resolve class references. Invoking this method is equivalent
* to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
* false)</tt>}.
*
* @param name The <a href="#name">binary name</a> of the class
* @return The resulting <tt>Class</tt> object
* @throws ClassNotFoundException If the class was not found
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[0];
b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
//用自定义的类加载器去加载一个类
Object obj = myLoader.loadClass("java8.classloading.classloader.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
//与相对路径的类相对比
System.out.println(obj instanceof java8.classloading.classloader.ClassLoaderTest);
}
}
运行结果:
class java8.classloading.classloader.ClassLoaderTest
false
关于双亲委派模型(Parents Delegation Model)
3种类加载器
- 启动类加载器(Bootstrap ClassLoader)
-
这个类加载器使用C++语言实现,是虚拟机自身的一部分;
-
无法被Java程序直接引用
-
负责把类库加载到虚拟机内存中
-
放在<JAVA_HOME>\lib目录中
-
或者被 -Xbootclasspath 参数所指定的路径中的
-
被虚拟机识别的(按照文件名识别,如rt.jar)
-
- 扩展类加载器(Extension ClassLoader)
-
由sun.misc.Launcher$ExtClassLoader实现
-
开发者可以直接使用
-
负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库
- 应用程序类加载器(Application ClassLoader)
-
由sun.misc.Launcher$App-ClassLoader实现
-
一般也称为系统加载器。程序中的默认类加载器
-
加载用户类路径(ClassPath)上所指定的类库
什么是双亲委派模型
如图所示:类加载器之间的这种层次关系,称为类加载器的双亲委派模型
- 工作流程:
如果一个类加载器收到了类加载的请求,先把请求委派给父类加载器去完成,每层如此;只有父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载
- 好处:
保证程序里各类加载器环境中都是同一个类,对于保证Java程序的稳定运作很重要
(代码集中在java.lang.ClassLoader的loadClass()方法中)
题外
-
热部署: 其实是通过自定义类加载机制实现了,其破坏了双亲委派模型(OSGI:Java模块化标准)
-
关于反射: 提供很多方法,可以直接通过(类名.方法)的形式获取类的相关信息(变量、方法等)