JVM学习_类加载篇
类加载机制
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
- 类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。
当类被加载后就进入连接阶段,这一阶段包括:
- 验证: 字节码文件是否正确
- 准备: 为静态变量分配内存并设置默认的初始值;此阶段仅仅只为静态类变量(即static修饰的字段)分配内存和初始化默认值(比如 static int num = 5,这里只将num初始化为0,5的值将会在初始化的时候赋值);对于final static 修饰的常量,编译的时候就会分配了.也不会分配内存.
- 解析: 将符号引用替换为直接引用
最后JVM对类进行初始化,包括:
- 1: 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
ps:父类的静态字段>父类静态代码块>子类静态字段>子类静态代码块>父类成员变量(非静态字段>父类非静态代码块>父类构造器>子类成员变量>子类非静态代码块>子类构造器)
- 2: 如果类中存在初始化语句,就依次执行这些初始化语句。
类的加载是由类加载器完成的,类加载器包括:
-
Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
-
Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
-
System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
类加载器与双亲委派机制
上图
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getName());
System.out.println(ClassLoader_test.class.getClassLoader().getClass().getName());
System.out.println();
//通过ClassLoader的getSystemClassLoader获取系统默认的加载器,默认是sun.misc.Launcher$AppClassLoader@18b4aac2,应用类加载器
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("the appClassLoader:" + appClassLoader);
//获取应用加载器的上层加载器
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("the extClassLoader:" + extClassLoader);
//获取扩展类加载器的上层加载器
ClassLoader bootStarapLaoder = extClassLoader.getParent();
System.out.println("the bootStrapLoader:" + bootStarapLaoder);
}
结果
D:\Java\jdk1.8.0_131\bin\java.exe ...
null -- 这里应该是bootStarpClassLoader,但是这个根(引导类)加载器是有C ++ 实现的
sun.misc.Launcher$ExtClassLoader -- sun.misc.Launcher-启动器实例
sun.misc.Launcher$AppClassLoader -- ExtClassLoader和AppClassLoader都是Launcher类中的一个静态内部类
the appClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
the extClassLoader:sun.misc.Launcher$ExtClassLoader@45ee12a7
the bootStrapLoader:null
Process finished with exit code 0
比如我现在写了一个类: com.text.HelloWorld,那么他的加载过程:
图一
图二
图三
图四
测试代码
package java.lang;
/**
* @author 0101
*/
public class String {
public static void main(String[] args) {
System.out.println("双亲委派");
}
}
jdk中的String类也是在这个路径下,我们现在运行这个min方法
D:\Java\jdk1.8.0_131\bin\java.exe ...
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
Process finished with exit code 1
找不到min方法? 明明在这里啊
这里是因为jdk中也有一个同样路径的String类,是在rt.jar里,而tr.jar是被引导类加载器所加载的,对照上面的第三幅图,实际运行这个min方法后加载的是rt.jar下面的String类.显然那个类是没有min方法的
这样的加载过程称为双亲委派机制
为什么要设计双亲委派机制?
- 避免类的重复加载:当父类已经加载了该类,就没必要子加载器再加载一次
- 沙箱安全机制: 比如上面我们自己写的java.lang.String.class就不会被加载,这样便可以防止核心API库被随意篡改
为什么每次加载都要从应用类加载器往上找
- 在实际开发中,95%的代码都是我们自己写的,那么这样做在这个类第一次加载的时候可能会很慢,但第二次就可以直接从应用类加载器拿,这样很快,上面图四
用户自定义类加载器
- . 在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们可以自定义类加载器,来定制类的加载方式
为什么要自定义类加载器
1.隔离加载类
2.修改类加载的方式
3.扩展加载源
4.防止源码泄漏
用户自定义类加载器实现步骤:
1.开发人员可以通过继承抽象类java.lang.ClassLoader类的方式,实现自己的类加载器满足一些特殊
的需求.
2.在jdk1.2之前,在自定义类加载器时,总会继承ClassLoader类并重写loadClass()方法,从而实现自
定义的类加载器.但在jdk1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加
载逻辑卸载findClass()方法中
3.在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以
避免自己去编写findClass()方法及获取字节流的方式,是自定义类加载器编写更加简洁
package JVM;
import java.io.*;
/**
* 用户自定义类加载器
* @author 0101
*/
public class CustomClassLoader extends ClassLoader{
@Override
//根据名称或位置加载.class字节码,然后使用defineClass
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] result = getClassFromCustomPath(name);
if (result == null) {
throw new FileNotFoundException();
} else {
//把字节码转化为Class
return defineClass(name,result,0,result.length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
throw new ClassNotFoundException(name);
}
private byte[] getClassFromCustomPath(String name){
//从自定义路径中加载制定类:细节略
//如果指定路径的字节码文件进行加密,则需要在此方法中进行解密操作
try{
File filename = new File(name);
BufferedInputStream in = new BufferedInputStream(new FileInputStream(filename));
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
byte[] temp = new byte[1024];
int size = 0;
while((size = in.read(temp)) != -1){
out.write(temp, 0, size);
}
in.close();
byte[] content = out.toByteArray();
return content;
} catch (IOException e){
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
CustomClassLoader customClassLoader = new CustomClassLoader();
try {
Class<?> clazz = Class.forName("JVM.CustomClassLoader",true,customClassLoader);
Object obj = clazz.newInstance();
System.out.println(obj.getClass().getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
其他
在JVM中表示两个class对象是否为同一个类存在的两个必要条件
- 类的完整类名必须一致,包括包名
- 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同
换句话说,在JVM中,即使这两个类对象(Class对象)来源同一个Class文件,被同一个虚拟机加载,但是只要他们的ClassLoader实例对象不同,那么这个类对象也是不相等的.
类加载器的引用
JVM必须知道一个类型是由引导类(启动)加载器加载的还是由用户类加载器加载的.如果一个类型是由用户类型加载器加载的,那么JVM会**将这个类加载器的一个引用作为类型信息的一部分保存在方法区中.**当解析一个类型到另一个类型引用的时候,JVM需要保证这两个类型的类加载器是相同的