今天的博客主题
Java虚拟机 ——》 Jvm类加载机制
类加载过程
在一开始学习 Java 语言的时候,对 javac 和 java 命令应该都不陌生。
这两个命令对应了你的 JDK 安装目录:C:\Program Files\Java\jdk1.8.0_151\bin 目录下的javac.exe 和 java.exe 。
这个目录下不仅仅包含了这两个命令,比如:javap、jmap、jstack、jstat 等等的一些命令。
可以尝试的去使用下,后面也会对这些命令有解释。
javac 命令是把写好的 .java 文件,编译成 Java 可执行的 .class 文件。
java 命令是把 .class 文件,通过类加载器给加载到 JVM 里去执行。
这个类是怎么被加载到 JVM 里面的呢?
加载流程是什么样的呢?
loadClass 的类加载过程主要有以下几步:
加载 》 验证 》 准备 》 解析 》 初始化 》 使用 》 卸载
加载:在硬盘上查找并通过IO读入字节码文件(使用到类的时候才会加载,比如调用类的main()方法,new对象等等)在这个加载阶段会在内存中生成一个代表类的 java.lang.Class() 对象,做为方法区这个类的各种数据访问入口。
验证:校验字节码文件的正确性。
准备:给类的静态变量分配内存,并赋予默认值。
解析:将符号引用替换为直接引用。把一些静态方法(符号引用,比如main()方法) 替换为指向数据所存的内存指针或句柄等(直接引用),这就是所谓的静态链接过程(会在类加载期间完成)。动态链接是在程序运行期间完成的,将符号引用替换为直接引用。
初始化:对类的静态变量初始化为指定的值,执行静态代码块。
当类被加载到方法区中后主要包含了:运行时常量池,类型信息,字段信息,方法信息,类加载器的引用(当前类到类加载器实例的引用),对应class实例的引用(类加载器在加载类信息放到方法区中后,会创建一个对应的 Class 类型的对象实例放到堆(Heap)中,访问方法区中类定义的入口和切入点) 等信息。
注意:jar包或war包里的类不是一次性全部被加载进来的,只有在主类运行过程中,使用到其他的类的时候,才会去加载这些类。懒加载机制,节省空间
public class JvmSample {
static{
System.out.println(">>>加载JvmSample");
}
public static void main(String[] args) {
A a = new A();
// B 是不会被加载的。只有在 new 的时候,才会被加载
B b1 = null;
}
}
class A{
static {
System.out.println(">>>加载 A");
}
public A(){
System.out.println(">>>初始化 A");
}
}
class B{
static {
System.out.println(">>>加载 B");
}
public B(){
System.out.println(">>>初始化 B");
}
}
类加载器
类加载过程主要是依赖类加载器来实现的,主要有以下几种类加载器。
1)BootstrapLoader(引导类加载器):负责加载支持JVM运行,位于JRE的lib目录下的核心类库,比如rt.jar,charsets.jar等。
2)ExtClassLoader(扩展类加载器):负责加载支撑JVM运行的,位于JRE的lib目录下的ext扩展目录中的类库。
3)AppClassLoader(应用程序类加载器):负责加载ClassPath路径下的类库,主要就是自己写的那些类。
4)自定义类加载器:负责加载用户自定义路径下的类库。
看下 classLoader.loadClass 是怎么去加载类的。
类加载器示例
public class JvmClassLoaderSample {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(JvmClassLoaderSample.class.getClassLoader());
System.out.println();
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
System.out.println();
System.out.println("bootstrapLoader加载的类文件:");
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(urL);
}
System.out.println();
System.out.println("extClassloader加载的类文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载的类文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
>>>>>>>>>>>>>>>运行结果>>>>>>>>>>>>>>>>
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4e50df2e
null
bootstrapLoader加载的类文件:
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/classes
......
extClassloader加载的类文件:
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
appClassLoader加载的类文件:
C:\Program Files\Java\jdk1.8.0_151\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\localedata.jar;
......
类加载器初始化的过程
在类加载的过程会创建一个JVM启动器实例 sun.misc.Launcher
一个单例的Launcher,确保整个虚拟机内只有一个 Launcher 实例
private static Launcher launcher = new Launcher();
public static Launcher getLauncher() {
return launcher;
}
Launcher 构造方法,创建了扩展类加载器和应用程序类加载器
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
...省略部分代码...
}
在调用 Launcher 类的 getClassLoader() 方法,返回的就是 Launcher.AppClassLoader,一般都是用这个加载器来加载我们自己写的程序代码。
public ClassLoader getClassLoader() {
return this.loader;
}
双亲委派机制
JVM类加载器是有亲子层级结构的。
类加载是有一个双亲委派机制的,就是在加载某个类的时候,先委托父加载器去寻找要加载的这个类, 这个父类找不到就再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到这个类,则在自己的类加载路径中查找并载入。
简单说就是:类加载的时候先找父类加载,父类都加载不了,自己加载。
为什么需要双亲委派机制?
1)沙箱安全机制:就是自己写的 java.lang.String.class 不会被加载进去,防止核心类库被篡改。
2)避免类重复加载:一个类只被一个加载器加载一次,保证被加载类的唯一性。
类加载示例:
就是自己写一个 String 类包路径和Java rt.jar包下的 String 路径一样 java.lang.String
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println(" my String ");
}
}
》》》执行结果:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
自定义类加载器
实现自定义类加载器只需要继承 ClassLoader 类。
ClassLoader 类有两个核心方法:loadClass(String, boolean) 实现了双亲委派机制,
findClass(String) 默认实现是一个空方法,自定义类加载器就是重写这个方法。
/**
* 自定义类加载器
*/
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String fileName) throws Exception {
String newFileName = fileName.replace(".", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + newFileName + ".class");
int length = fis.available();
byte[] bytes = new byte[length];
fis.read(bytes);
fis.close();
return bytes;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
// defineClass 将字节数组转换为类的实例
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
// Test Class
public class Test{
public void test(){
System.out.println("Hello World");
}
}
// 测试
private static void customClassLoader() throws Exception {
MyClassLoader myClassLoader = new MyClassLoader("D:/StudyIT/Jvm");
Class clazz = myClassLoader.loadClass("Test");
Object object = clazz.newInstance();
Method method = clazz.getDeclaredMethod("test", null);
method.invoke(object, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
// 输出结果
Hello World
com.uzi.jvm.JvmClassLoaderSample$MyClassLoader