本篇文章主要总结一下JVM核心知识之一的类加载机制以及实现原理,最后再介绍一个如何实现自定义类加载器?首先说一下java的运行机制,比如编写完一个java文件,jvm到底是怎么执行的?一般来说需要5个过程:
- 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
- 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
- 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
- 解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析
- 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
JVM存在的几种类加载形式:
系统启动类:BootstrapClassLoader
启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib
路径下的核心类库或-Xbootclasspath
参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar
扩展类加载器:ExtAppClassLoader
扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader
类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext
目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器
系统类加载器:AppClassLoader
也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader
。它负责加载系统类路径java -classpath
或-D java.class.path
指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()
方法可以获取到该类加载器
在了解各种类加载的作用之后,接下来还要看类加载过程尝尝使用的一种模式'双亲委托模式'
原理:除顶层的启动类加载器之外,其余的类都应该首先有自己的父类的类加载来加载,如果父类能够直接加载,就返回,如果父类
加载不了,再由自己的类加载来加载完成,具体流程可以看一下如图:
优势:
1 避免类的重复加载:因为这种加载方式,让类的加载有一种的层次关系和优先级关系,如果父类加载了,子类就不需要再加载
2 避免不安全的类被加载:java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer
的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer
,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改
自定义类加载器:
有时候因为特殊的业务需求,我们需要自己手动写一个类加载器来加载自己的类或者外部类,这个时候就需要自定义类加载了,如何实现呢?其实很简单就是我们定义一个类继承ClassLoader,然后重写里面对应的方法:主要下面三个方法
& loadClass:该方法加载指定名称的二进制文件(完整的包名类名)不过在JDK1.2版本之后,不建议重写该方法,而是直接调用,因为该方法是有classloader自己实现的
& findClass:完成自己的类加载逻辑
& defineClass:用来将byte字节流解析成JVM能够识别的Class对象
接下来实现一个简单的类加载器:
编写一个简单的person对象 就是我们要加载的class文件
public class Person {
public void eat(){
System.out.println("i am person,i like to eat banana...");
}
}
定义一个classloader的实现:
package com.suning.dynamic_proxy.classloader;
/**
* Created by jack on 2018/6/19.
* 自定义类加载器 用户把字节码转换为class对象
*/
public class PersonClassLoader extends ClassLoader{
public Class<?> defineMyClass(byte[] b, int off, int len) {
return super.defineClass(b, off, len);
}
}
编写一个测试类:
package com.suning.dynamic_proxy.classloader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
/**
* Created by jack on 2018/6/19.
* 测试自定义的类加载器
*/
public class ClassLoaderTest {
public static void main(String[] args)throws Exception {
File file = new File(".");
//指定要加载的class文件
String classPath = "/target/classes/com/suning/dynamic_proxy/classloader/Person.class";
InputStream input = new FileInputStream(file.getCanonicalPath()+classPath);
byte [] result = new byte[1024];
int count = input.read(result);
//使用自定义类加载起
PersonClassLoader personClassLoader = new PersonClassLoader();
//通过类加载器获取对应的class对象
Class<?> personClass = personClassLoader.defineMyClass(result, 0, count);
System.out.println("class info:"+personClass.getSimpleName());
//通过反射实例户class对象
Object object = personClass.newInstance();
//通过反射调用class的相关方法
personClass.getMethod("eat").invoke(object,null);
}
}
ok,到此为止有关classloader基础知识就差不多说完了,之后可以看一下运行效果.