jvm主要由类加载器、运行时数据区、执行引擎组成
一个 java 文件被编译后由 jvm 加载到执行的过程:
加载 —> 连接(验证—>准备—>解析)—> 初始化 —> 使用 —> 卸载
1、加载1.1、 通过一个类的全限定名来获取定义此类的二进制字节流(并没有指明要从 一个Class文件中获取,可以从其他渠道,譬如:本地磁盘、网络下载.class文件、war,jar下加载.class文件、从数据库中读取、将java源文件动态编译成class文件【动态代理、jsp转换为servlet,而servlet是一个java文件,可编译成clas文件】等);
1.2、 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
1.3、 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
加载阶段和连接阶段(linking)的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序
2、连接2.1、验证
保证类没有被加载跑偏,而且结构没被破坏
验证阶段大致会完成4个阶段的检验动作:
1、文件格式验证: 验证字节流是否符合Class文件格式的规范,并且能够被当前版本的虚拟机处理,分为以下几点:
-
是否以魔术0xCAFEBABE开头
-
主次版本号是否在当前虚拟机的处理范围之内
-
常量池中的常量是否有不被支持的类型。
2、元数据验证: 对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;
-
这个类是否有父类。(除了java.lang.Object之外)
-
这个类的父类是否集继承了不允许被继承的类(被final修饰的类)
-
如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法
3、字节码验证: 整个验证过程最复杂的一个阶段。主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型做完校验后,这个阶段将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件
-
保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似在操作栈放int型数据,使用却按long行加载如本地变量表中。
-
保证跳转指令不会跳转到方法体意外的字节码指令上
4、符号引用验证: 目的是确保解析动作能正常执行,发生在虚拟机将符号引用转换为直接引用的时候,这个转化动作将在连接的第三阶段-解析阶段中发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
-
符号引用中通过字符串描述的全限定名是否能够找到对应的类。
-
在指定类中是否存在符号方法的字段描述符以及简单名称所描述的方法和字段
-
符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间
2.2、准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
public static int value=123;
那变量value在准备阶段过后的初始值为0而不是123.因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。
至于“特殊情况”是指:
public static final int value=123
即当类字段的字段属性是ConstantValue时,会在准备阶段初始化为指定的值,所以标注为final之后,value的值在准备阶段初始化为123而非0.
2.3、解析
解析阶段是虚拟机将常量池内的符号引用转换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
3、初始化类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的java程序代码。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序猿通过程序制定的主观计划去初始化类变量和其他资源,或者说:初始化阶段是执行类构造器()方法的过程。
- clinit()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。如下:
public class Test
{
static
{
i=0;//给变量赋值可以正常编译通过
System.out.println(i);//这句编译器会报错:Cannot reference a field before it is defined(非法向前应用)
}
static int i=1;
触发类初始化的操作
1、调用类的static属性
2、调用类的static方法
3、mian方法
4、new大小
5、class.forName()
6、子类初始化触发父类初始化
初始化一定会类加载,类加载不一定初始化
参考文章
三、类加载器类加载器分类:
-
启动类加载器(Bootstrap ClassLoader):C++实现,不是ClassLoader的子类
-
拓展类加载器(Extension ClassLoader):由java实现,它是Launcher类的静态内部类,并且继承了 URLClassLoader类
-
系统类加载器(Application ClassLoader):由java实现,它是Launcher类的静态内部类,并且继承了 URLClassLoader类
-
自定义类加载器:需要继承ClassLoader类,并重写findClass()方法
1、启动类加载器
负责加载放置在
public static void bootStartClassLoaderPath(){
String properties = System.getProperty("sun.boot.class.path");
List strings = Arrays.asList(properties.split(";"));
for (String string : strings) {
System.out.println(string);
}
}
结果如下:
D:Javajdk1.8.0_271jrelibresources.jar
D:Javajdk1.8.0_271jrelibrt.jar
D:Javajdk1.8.0_271jrelibsunrsasign.jar
D:Javajdk1.8.0_271jrelibjsse.jar
D:Javajdk1.8.0_271jrelibjce.jar
D:Javajdk1.8.0_271jrelibcharsets.jar
D:Javajdk1.8.0_271jrelibjfr.jar
D:Javajdk1.8.0_271jreclasses
如果将自定义的类放置在
2、拓展类加载器
负责加载
public static void extClassLoaderPath(){
String properties = System.getProperty("java.ext.dirs");
List strings = Arrays.asList(properties.split(";"));
for (String string : strings) {
System.out.println(string);
}
}
D:Javajdk1.8.0_271jrelibext
C:WindowsSunJavalibext
3、系统类加载器
负责加载用户类路径(ClassPath)上所指定的类库
public static void appClassLoaderPath(){
String properties = System.getProperty("java.class.path");
List strings = Arrays.asList(properties.split(";"));
for (String string : strings) {
System.out.println(string);
}
}
D:Javajdk1.8.0_271jrelibcharsets.jar
D:Javajdk1.8.0_271jrelibdeploy.jar
D:Javajdk1.8.0_271jrelibextaccess-bridge-64.jar
D:Javajdk1.8.0_271jrelibextcldrdata.jar
D:Javajdk1.8.0_271jrelibextdnsns.jar
D:Javajdk1.8.0_271jrelibextjaccess.jar
D:Javajdk1.8.0_271jrelibextjfxrt.jar
D:Javajdk1.8.0_271jrelibextlocaledata.jar
D:Javajdk1.8.0_271jrelibextnashorn.jar
D:Javajdk1.8.0_271jrelibextsunec.jar
D:Javajdk1.8.0_271jrelibextsunjce_provider.jar
D:Javajdk1.8.0_271jrelibextsunmscapi.jar
D:Javajdk1.8.0_271jrelibextsunpkcs11.jar
D:Javajdk1.8.0_271jrelibextzipfs.jar
D:Javajdk1.8.0_271jrelibjavaws.jar
D:Javajdk1.8.0_271jrelibjce.jar
D:Javajdk1.8.0_271jrelibjfr.jar
D:Javajdk1.8.0_271jrelibjfxswt.jar
D:Javajdk1.8.0_271jrelibjsse.jar
D:Javajdk1.8.0_271jrelibmanagement-agent.jar
D:Javajdk1.8.0_271jrelibplugin.jar
D:Javajdk1.8.0_271jrelibresources.jar
D:Javajdk1.8.0_271jrelibrt.jar
C:UsersslqIdeaProjectsjvmTesttargetclasses
C:Usersslq.m2repositorycomalibabaeasyexcel2.2.6easyexcel-2.2.6.jar
C:Usersslq.m2repositoryorgapachepoipoi3.17poi-3.17.jar
C:Usersslq.m2repositorycommons-codeccommons-codec1.10commons-codec-1.10.jar
C:Usersslq.m2repositoryorgapachecommonscommons-collections44.1commons-collections4-4.1.jar
C:Usersslq.m2repositoryorgapachepoipoi-ooxml3.17poi-ooxml-3.17.jar
C:Usersslq.m2repositorycomgithubvirtualdcurvesapi1.04curvesapi-1.04.jar
C:Usersslq.m2repositoryorgapachepoipoi-ooxml-schemas3.17poi-ooxml-schemas-3.17.jar
C:Usersslq.m2repositoryorgapachexmlbeansxmlbeans2.6.0xmlbeans-2.6.0.jar
C:Usersslq.m2repositorystaxstax-api1.0.1stax-api-1.0.1.jar
C:Usersslq.m2repositorycglibcglib3.1cglib-3.1.jar
C:Usersslq.m2repositoryorgow2asmasm4.2asm-4.2.jar
C:Usersslq.m2repositoryorgslf4jslf4j-api1.7.26slf4j-api-1.7.26.jar
C:Usersslq.m2repositoryorgehcacheehcache3.4.0ehcache-3.4.0.jar
D:IDEAIntelliJ IDEA Educational Edition 2020.2.3libidea_rt.jar
4、自定义类加载器
自定义类加载器需满足基本的两个条件
1、需继承ClassLoader类并重写findClass()方法
2、包含将class文件转换为二进制字符流的方法
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader{
private final static String fileStuff =".class";
private String classLoaderName;
private String loadPath;
public void setLoadPath(String loadPath) {
this.loadPath = loadPath;
}
public MyClassLoader(ClassLoader parent,String classLoaderName) {
super(parent);
this.classLoaderName = classLoaderName;
}
public MyClassLoader(String classLoaderName) {
super();
this.classLoaderName = classLoaderName;
}
public MyClassLoader(ClassLoader classLoader) {
super(classLoader);
}
private byte[] loadClassData(String name){
byte [] data =null ;
ByteArrayOutputStream baos =null;
InputStream is =null;
try {
name =name.replace(".","\");
String fileName =loadPath+name+fileStuff;
File file =new File(fileName);
is =new FileInputStream(file);
baos =new ByteArrayOutputStream();
int ch;
while (-1 != (ch = is.read())){
baos.write(ch);
}
data = baos.toByteArray();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if ( baos != null){
baos.close();
}
if (is != null) {
is.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
return data;
}
protected Class> findClass(String name){
byte[] bytes = loadClassData(name);
System.out.println("loaderClass------>"+name);
return defineClass(name,bytes,0,bytes.length);
}
}
测试前需将测试加载的类ReferenceCountDemo.class文件剪切到测试文件路径下(d:comslqtestReferenceCountDemo.class);否则该类会被系统加载器加载;因系统类加载器的优先级高于自定义类加载器
MyClassLoader myClassLoaderTest = new MyClassLoader("MyClassLoaderTest");
myClassLoaderTest.setLoadPath("d:\");
Class> clazz = myClassLoaderTest.loadClass("com.slq.test.ReferenceCountDemo");
System.out.println("类加载器------>"+clazz.getClassLoader());
System.out.println("当前类加载器的父加载器------>"+clazz.getClassLoader().getParent());
结果如下:
四、双亲委派模型loaderClass------>com.slq.test.ReferenceCountDemo
类加载器------>com.slq.test.MyClassLoader@66d3c617
当前类加载器的父加载器------>sun.misc.Launcher$AppClassLoader@18b4aac2
1、加载器的优先级由高到低分别为:
启动类加载器 ----> 拓展类加载器 ----> 系统类加载器 ----> 自定义类加载器
2、加载器的优先级设定:
由启动类加载器加载Launcher类,调用Launcher的构造方法
在该构造方法中先获取拓展类加载器,再获取系统类加载器
然后将拓展类加载器指定为系统类加载器的父加载器
3、双亲委派模型的工作过程:
如果一个类加载器收到了类加载的请求
- 它首先判断该类是否已经被加载,若已被加载过,则直接返回Class;
- 若没有被加载过,则判断当前加载器的父加载器是否为null,如果为null就表示当前加载器的父加载器是启动类加载器,直接交给启动类加载器加载;
- 如果不为null,则交给当前加载器的父加载器加载,父加载器也会重复之前操作直至找到启动类加载器为止
- 当启动类加载器无法加载到该类时,就交给它的子加载器加载,若子加载器也无法加载就再交给下一子加载器,直到所有的加载加载完毕后,若都无法加载该类,那么就会抛出ClassNotFoundException
ClassLoader类的 loadClass(String name, boolean resolve) 方法
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先判断该类是否已经被加载,若已被加载过,则直接返回Class
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//若没有被加载过,则判断当前加载器的父加载器是否为null
if (parent != null) {
//如果不为null,则交给当前加载器的父加载器加载,
//父加载器再次调用loadClass()方法,父加载器重复之前操作直至找到启动类加载器为止
c = parent.loadClass(name, false);
} else {
//如果为null就表示当前加载器的父加载器是启动类加载器,直接交给启动类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
long t1 = System.nanoTime();
//当启动类加载器无法加载到该类时,就交给它的子加载器加载,AppClassLoader与ExtClassLoader都继承了URLClassLoader,因此调用URLClassLoader中的findClass(final String name)
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;
}
}
URLClassLoader中的findClass(final String name)方法
protected Class> findClass(final String name)
throws ClassNotFoundException
{
final Class> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction>() {
public Class> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
//获取类加载器对应的加载路径的Resource,如果该类不在该路径下,则返回 null ,
//即加载失败,交由下一子类加载器加载
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}



