内容目录
1.0 JVM运行流程,JVM基本结构
2.0 类加载器双亲委派模型讲解
3.0 ClassLoader源码解析
4.0 从源码分析实现自定义类加载器
一、JVM运行流程,JVM基本结构
1.0 JVM运行流程:JAVA源程序通过javac编译、将源文件编译成字节码文件。然后交给JVM虚拟机。java虚拟机根据不同的环境解释成本地机器指令实现跨平台的特点
2.0 JVM基本机构
JVM由类加载器、执行引擎、运行时数据区、本地接口
Class Files – > ClassLoader –>运行时数据区 —> 执行引擎,本地库接口 —> 本地分法库
二、类加载器双亲委派模型讲解
2.1 类的加载过程
加载、连接(验证、准备、解析)、初始化、使用、卸载
2.1.1 加载
加载阶段主要完成三件事,即通过一个类的全限定名来获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,在Java堆中生成一个代表此类的Class对象,作为访问方法区这些数据的入口。这个加载过程主要就是靠类加载器实现的,这个过程可以由用户自定义类的加载过程。
2.1.2 加载
这个阶段目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证:
文件格式验证:基于字节流验证,验证字节流是否符合Class文件格式的规范,并且能被当前虚拟机处理。
元数据验证:基于方法区的存储结构验证,对字节码描述信息进行语义验证。
字节码验证:基于方法区的存储结构验证,进行数据流和控制流的验证。
符号引用验证:基于方法区的存储结构验证,发生在解析中,是否可以将符号引用成功解析为直接引用。
2.1.3 准备
仅仅为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即零值,这里不包含用final修饰的static,因为final在编译的时候就会分配了,同时这里也不会为实例变量分配初始化。类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
2.1.4解析
解析主要就是将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析。
2.1.5 初始化
初始化阶段依旧是初始化类变量和其他资源,这里将执行用户的static字段和静态语句块的赋值操作。这个过程就是执行类构造器方法的过程。
//源码解释。debug的时候,先执行public static int tmp = 1;
//然后执行:static { .... }
//然后定位到Class Demo上
//最后再执行main方法。
//结论:初始化类,先加载static变量 + static{}语气,两者加载的顺序是按照你写的代码顺序加载的哦
package classLoader;
public class Demo {
public static int tmp = 1;
static {
tmp = 2 ;
System.out.println(tmp);
}
public static void main(String[] args) {
tmp = 3 ;
System.out.println(tmp);
}
}
2.2 双亲委派模型过程
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去反过来委托给它的发起者,自己来加载这个类,如果自己都加载不了该类的话,那么就会报ClassNotFound错误。
使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。防止重复加载、父类已经加载过的,子类就不需要重复加载。否则会出现错误
2.3 JDK已有的类加载器
2.3.1 BootStrap Classloader ~~启动类加载器 - - - - -》 rt.jar(jdk/lib文件夹里面的jar)
2.3.2 Extension Classloader extends ClassLoader ~~ 扩展类加载器 - - - -》%JAVA%Home/lib/ext/*.jar
2.3.3 App Classloader Classloader extends ClassLoader ~~ 系统(应用)类加载器 - - - - -》ClassPath类
2.3.4 自定义类加载器extends ClassLoader - - - - - - 》完全自定义路径
【总结】2.3.4 的父加载器 是 2.3.3 ,2.3.3.的父加载器是2.3.2,2.3.2的父加载器是2.3.1.
//证明源码
package classLoader;
public class Demo2 {
public static void main(String[] args) {
ClassLoader loader = Demo2.class.getClassLoader();
System.out.println("当前类的加载器"+loader);
while(loader!=null) {
System.out.println(loader);
loader = loader.getParent();
}
System.out.println(loader);
}
}
当前类的加载器sun.misc.Launcher$AppClassLoader@1d264bf5
sun.misc.Launcher$AppClassLoader@1d264bf5
sun.misc.Launcher$ExtClassLoader@3d44d0c6
null
三、ClassLoader源码解析
3.1 双亲委派模型源码
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
四、从源码分析实现自定义类加载器 – 》一般可以忽略,知道原理即可。有兴趣可以看看
package classLoader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader{
private String path; //加载类的路径
private String name; //类加载器的名称
public MyClassLoader(String name,String path) {
super();//让系统类加载器成为该类的父加载器
this.name = name;
this.path = path;
}
public MyClassLoader(ClassLoader parent, String name,String path) {
super(parent); //显示指定的类加载器
this.name = name;
this.path = path;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name;
}
/*
* 加载我们自定义的类,通过我们自定义的这个ClassLoader
* classLoader.Demo.java
*/
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] data = readClassFileToByteArray(name);
return this.defineClass(name,data,0,data.length);
//return super.findClass(name);
}
/**
* 获取.class文件的字节数组
* classLoader.Demo.java - 》 F:/temp/classLoader/Demo.class
* @param name2
* @return
*/
private byte[] readClassFileToByteArray(String name) {
InputStream is = null;
byte[] returnData = null;
name = name.replaceAll("\\.", "/");
String filePath = this.path + name + ".class";
File file = new File(filePath);
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
is = new FileInputStream(file);
int tmp = 0;
while((tmp = is.read())!=-1) {
os.write(tmp);
}
returnData = os.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
os.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return returnData;
}
}
public class TestName {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
MyClassLoader loader = new MyClassLoader("xiaozheng", "D:/temp/");
Class> c = loader.loadClass("Demo");
c.newInstance();
System.out.println("231231");
}
}
//这里需要我在D盘的temp文件夹中,新建一个Demo.java的类,然后进行编译