java类加载机制
一. java类加载过程
java类加载一共经历7个过程
1. 加载
加载是类加载的第一过程,这个阶段完成三件事
- 通过类的全路径获取该类的二进制流。
- 将该二进制流中的静态存储结构转化为方法运行时数据结构。
- 在内存中生成该类的Class对象,作为该类的数据访问入口。
2. 验证
验证确保Class文件的字节流中的信息正确,以及不会危害到虚拟机;主要有四种验证。
- 文件格式验证:验证字节流是否符合Class文件的规范,如:常量池中的常亮是否有不被支持的类型等;
- 元数据验证:对字节码信息进行语义分析,如:该类是否有父类,是否继承了不被继承的类等;
- 字节码验证:是整个验证过程中最复杂的一个阶段,确定语义是否正确,主要针对方法提的验证,如:方法中的类型转换是否正确,跳转指令是否真确等;
- 符号引用验证:在解析过程中发生,主要是为了确保解析动作能正确执行;
3. 准备
准备阶段是为类的静态变量分配内存并将其初始化为默认值,内存在方法区(永久代)中分配,准备阶段不分配勒种实例变量的内存,实例变量会随对象分配在就java堆中;
//准备阶段value初始值为0,在初始化阶段才会变成123
public static int value = 123;
4. 解析
该阶段主要完成符号的间接引用转化为直接引用,解析动作不一定在初始化之前,也有可能在初始化之后
5. 初始化
初始化时类加载的最后一步,类加载过程中,加载阶段用户可以通过自定义类加载器参与,其余动作完全由虚拟机主导控制的。初始化阶段,才真正执行类中定义的java程序代码。
6. 使用
程序员正常使用类,做一些操作
7. 卸载
由java虚拟机自带的类加载器所加载的类,在虚拟机生命周期中是不会被卸载的
由用户自己定义的类加载器加载的类是可以别卸载的
二. JVM加载Class文件的原理机制
Java 语言是一种具有动态性的解释型语言,类(Class)只有被加载到 JVM 后才能运行。当运行指定程序时,JVM 会将编译生成的 .class 文件按照需求和一定的规则加载到内存中,并组织成为一个完整的 Java 应用程序。 这个加载过程是由类加载器完成,具体来说,就是由 ClassLoader 和它的子类来实现的。类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中。类的加载方式分为隐式加载和显示加载。
- 隐式加载指的是程序在使用 new 等方式创建对象时,会隐式地调用类的加载器把对应的类加载到 JVM 中。
- 显示加载指的是通过直接调用 class.forName() 方法来把所需的类加载到 JVM 中。
任何一个工程项目都是由许多类组成的,当程序启动时,只把需要的类加载到 JVM 中,其他类只有被使用到的时候才会被加载,采用这种方法一方面可以加快加载速度,另一方面可以节约程序运行时对内存的开销。此外,在 Java 语言中,每个类或接口都对应一个 .class 文件,这些文件可以被看成是一个个可以被动态加载的单元,因此当只有部分类被修改时,只需要重新编译变化的类即可,而不需要重新编译所有文件,因此加快了编译速度。在 Java 语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(例如基类)完全加载到 JVM 中,至于其他类,则在需要的时候才加载。
类加载的主要步骤:
- 装载。根据查找路径找到相应的 class 文件,然后导入
- 链接。链接又可分为 3 个小步:
- 检查,检查待加载的 class 文件的正确性
- 准备,给类中的静态变量分配存储空间
- 解析,将符号引用转换为直接引用(这一步可选)
- 初始化。对静态变量和静态代码块执行初始化工
三. 什么是类加载器,类加载器又有那些
通过类的全路径获取该类的二进制流的代码块叫做类加载器;
类加载器主要分为三种:
- 启动类加载器(Bootstrap ClassLoader):用来加载java核心类库,无法被java程序直接引用。
- 扩展类加载器(Extensions ClassLoader):它用来加载java的扩展库;java虚拟机的实现会提供一个扩展目录,该类加载器再此目录里面查找并加载java类
- 系统类加载器(System ClassLoader):他根据java应用的类路径来加载java类,java应用的类都是由它来完成加载的,可以通过ClassLoader.getSystemClassLoader()来获取它
public static void main(String[] args) {
System.out.println(ClassLoader.getSystemClassLoader());
}
sun.misc.Launcher$AppClassLoader@73d16e93
除了这些,用户还可以自己定义类加载器,通过集成java.lang.ClassLoader类的方式实现
- 继承ClassLoader
- 重写findClass()方法
- 调用defineClass()方法
package com.fzz.java.demo2;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class ClassLoaderDemo extends ClassLoader {
private String classPath;
public ClassLoaderDemo() {
}
public ClassLoaderDemo(String classPath){
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classByte = new byte[0];
try {
classByte = getByte(name);
} catch (IOException e) {
e.printStackTrace();
}
// defineClass方法将字节码转化为类
return super.defineClass(name,classByte,0,classByte.length);
}
// 返回类的字节码
private byte[] getByte(String className) throws IOException{
InputStream in = null;
ByteArrayOutputStream out = null;
String path=classPath + File.separatorChar +
className.replace('.',File.separatorChar)+".class";
try {
in=new FileInputStream(path);
out=new ByteArrayOutputStream();
byte[] buffer=new byte[2048];
int len=0;
while((len=in.read(buffer))!=-1){
out.write(buffer,0,len);
}
return out.toByteArray();
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
finally{
in.close();
out.close();
}
return null;
}
}
package com.fzz.java.demo2;
public class Demo2 {
public void show(String str) {
System.out.println(str + " Hello MyClassLoader ");
}
}
package com.fzz.java.demo2;
import java.lang.reflect.Method;
public class Demo {
public static void main(String[] args) throws Exception {
//自定义类加载器的加载路径
ClassLoaderDemo classLoaderDemo=new ClassLoaderDemo("D:\\eclipse_workspace_two\\JavaDemo");
//包名+类名
Class c=classLoaderDemo.loadClass("com.fzz.java.demo2.Demo2");
if(c!=null){
Object obj=c.newInstance();
Method method=c.getMethod("show", new Class[] {String.class});
method.invoke(obj, new String[] {"fzz"});
System.out.println(c.getClassLoader().toString());
}
}
}
结果:
fzz Hello MyClassLoader
sun.misc.Launcher$AppClassLoader@73d16e93
四. 类加载机制双亲委派
当一个类请求加载时,不会先去自己加载这个类,而是将其委派给父类,有父类去加载,如果父类不能加载,再反馈给子类,由子类去完成类的加载。
避免了一些类的重复加载,有效防止核心类被恶意篡改