java类加载机制
本文介绍从java源文件到在JVM运行时经历的过程
一、编译
我们写好的java代码保存为.java源文件,要投入内存运行,首先需要由java的编译器javac进行编译,成为.clss文件即字节码文件,该文件本质上是一个二进制文件,用读取字节流的软件打开发现是16进制的。它其实就是把我们写的代码转换为另一种表达方法,一种符合在jvm上运行规范的标准的文件。
二、类的加载过程(加载到JVM上)
其实就是把编译后的字节码文件加载到内存中去,以便JVM对改文件进行解释运行。整个类的加载过程可以分为三个步骤:
1.类加载
由类加载器完成,负责根据类的全限定类名把.class文件中的字节流加载到运行时内存的方法区上,并为该类创建一个java.lang.Class对象,一般存放在堆中,以后对方法区中该类的数据访问就以这个Class对象为接口。
2.链接
可以细分为验证、准备和解析三个阶段。验证:确保字节码文件中字节流中包含的信息符合当前JVM的要求,且不会危害JVM自身安全。准备:该阶段负责为类的静态变量分配内存,并赋默认值。解析:负责把把类中的符号引用转换为直接引用。
3.初始化
为类的静态变量赋值(链接阶段的准备是赋默认值),然后执行类的初始化语句(即static代码块)。可细分为三个步骤:
1.如果类还没被加载和链接,先进行加载和链接。
2.如果类存在父类,并且父类为初始化,先初始化父类。
3.如果类中存在初始化语句,顺序执行这些语句。
三、JVM解释运行
字节码文件经类加载器加载到内存中后,就由JVM将其解释成机器码,让机器直接运行。
java运行时数据区介绍
java运行时数据区主要包含五个部分:方法区、堆,这两个区域是线程共享的;jvm栈、程序计数器和本地方法栈,这三个区域是每个线程私有的。
1.方法区:共享内存区,允许多个线程共同访问,是线程安全的。运行时常量池包含在内。存储内容有:类的全限定类名、超类的权限定类名、类的访问修饰符、常量池等类的基本信息,在虚拟机启动时创建。
2.堆:共享内存区,允许多个线程共同访问,是线程安全的。也是GC的主要区域,在虚拟机启动时创建。存储类的实例化对象和数组。
3.虚拟机栈:线程私有,栈中包含着一个个栈帧(基本单位),一个栈帧的入栈和出栈即对应该线程内一个方法的调用和销毁。栈帧中包含该方法的局部变量表、操作数栈、指向运行时常量池的引用、方法返回地址等信息。
4.程序计数器:线程私有,负责记录线程下一条指令的选取,虚拟机解释字节码就需要用到该器件,从而决定执行线程的哪一条指令。
5.本地方法栈:与虚拟机栈功能基本一致,只是虚拟机栈是用于执行java方法,而本地方法栈用于执行本地方法。
类加载器介绍
JVM提供了三种类加载器:
1.系统类加载器(Bootstrap ClassLoader):C++实现,在java里无法获取,负责加载<JAVA_HOME>/lib下的类。
2.扩展类加载器(Extension ClassLoader):Java实现,可以再java里获取,负责加载<JAVA_HOME>/lib/ext下的类。
3.系统类加载器/应用程序类加载器(Application ClassLoader):是我们接触最多的类加载器,我们写的代码默认就是由它来加载的。
双亲委派机制:当前加载器加载某个类的时候,一般先不自己加载,如果有父类加载器,那就先交给父类加载器加载该类,逐层递归,如果父类加载器无法加载该类,才逐层返回,交由子类加载器加载该类。loadClass()源码如下:
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;
}
}
如果要破坏双亲委派机制,交给我们自己定义的类加载器来加载某个类,可以重写loadClass()方法。