通过实例解析Java class文件编译加载过程
一、java从编码到执行
首先我们来看一下java是如何从编码到执行的呢? 我们有一个x.java文件通过执行javac命令可以变成x.class文件,当我们调用java命令的时候class文件会被装载到内存中,这个过程叫做classloader。一般情况下我们自己写代码的时候会用到java的类库,所以在加载的时候也会把java类库相关的类也加载到内存中。装载完成之后会调用字节码解释器和jit即时编译器来进行解释和编译,编译完之后由执行引擎开始执行,执行引擎下面对应的就是操作系统硬件了。下图是大体的流程:
java叫做跨平台的语言,jvm可以称之为跨语言的平台;
有个问题:java是解释执行还是编译执行?答:解释和编译是可以混合的,特别常用的代码或则是代码用到的次数特别多的时候,会把一个即时编译做成本地编译,这样会很大程度上的提高效率。
java虚拟机是如何做到这么多语言都可以在上面运行,关键在于class文件,任何语言只要能编译成class文件,并且符合class文件的规范你就可以放在java虚拟机上去运行。
二、详解class文件的加载过程
接下来主要讲的是一个class文件是怎么从硬盘上到内存中,并开始执行的。
类加载主要有三个过程:loading 、linking 、initializing;其中linking又分为三个步骤:verification 、preparation 、resolution;
1、首先loading是什么意思呢?是把一个class问价load到内存中去;
2、接下来是linking分为了三小步:
- verification 是用来校验加载进来的class文件是否符合class文件标准,如果不符合直接就会被拒绝了;
- preparation 是将class文件静态变量赋默认值而不是初始值,例如static int i =8;这个步骤并不是将i赋值为8,而是赋值为默认值0;
- resolution 是把class文件常量池中用到的符号引用转换成直接内存地址,可以访问到的内容;
3、initializing 成为初始化,静态变量在这个时候才会被赋值为初始值;
下面为类加载过程的简化图:
类加载器的加载过程是分成不同的层次来加载的,不同的类加载器来加载不同的class文件, bootstrap >extension>application>custom(自定义类加载器)
1、第一个类加载器的层次为:bootstrap 称为启动类加载器,是java类加载层次中最顶层的类加载器,负责加载jdk中的核心类库。
2、第二个类加载器的层次为:extension 是用来加载扩展类的,主要负责加载java的扩展类库,默认加载java_home/jre/lib/ext/目录下的所有jar包。
3、第三个类加载器的层次为:application又称为系统类加载器,负责在jvm启动时,加载来自在命令java中的classpath或者java.class.path系统属性或者classpath操作系统属性所指定的jar类包和类路径。
4、第三个类加载器的层次为:customclassloader(自定义加载器)
package com.example.demo.classloader; public class classloaderscope { public static void main(string[] args) { system.out.println("-------------------bootstrap加载类-------------------"); string property = system.getproperty("sun.boot.class.path"); string s = property.replaceall(";", system.lineseparator()); system.out.println(s); system.out.println("-------------------ext加载类-------------------"); string property1 = system.getproperty("java.ext.dirs"); string s1 = property1.replaceall(";", system.lineseparator()); system.out.println(s1); system.out.println("-------------------app加载类-------------------"); string property2 = system.getproperty("java.class.path"); string s2 = property2.replaceall(";", system.lineseparator()); system.out.println(s2); } } /**输出结果只截取了部分*/ //e:\jdk\jdk1.8\jre\lib\resources.jar //e:\jdk\jdk1.8\jre\lib\rt.jar //e:\jdk\jdk1.8\jre\lib\sunrsasign.jar //e:\jdk\jdk1.8\jre\lib\jsse.jar //e:\jdk\jdk1.8\jre\lib\jce.jar //e:\jdk\jdk1.8\jre\lib\charsets.jar //e:\jdk\jdk1.8\jre\lib\jfr.jar //e:\jdk\jdk1.8\jre\classes //---------------------------------------------- //e:\jdk\jdk1.8\jre\lib\ext //c:\windows\sun\java\lib\ext //---------------------------------------------- //e:\jdk\jdk1.8\jre\lib\charsets.jar //e:\jdk\jdk1.8\jre\lib\deploy.jar //e:\jdk\jdk1.8\jre\lib\ext\access-bridge-64.jar //e:\jdk\jdk1.8\jre\lib\ext\cldrdata.jar //e:\jdk\jdk1.8\jre\lib\ext\dnsns.jar //e:\jdk\jdk1.8\jre\lib\ext\jaccess.jar //e:\jdk\jdk1.8\jre\lib\ext\jfxrt.jar
特别注意一点这个的层级关系并没有继承的关系在里面,只是单单纯纯的语法上的继承;
下图为类加载的一个全过程:
用比较通俗的话来解释这个过程,当有一个类需要被加载时,首先要判断这个类是否已经被加载到内存,判断加载与否的过程是有顺序的,如果有自己定义的类加载器,会先到custom class loader 的cache(缓存)中去找是否已经加载,若已加载直接返回结果,否则到app的cache中查找,如果已经存在直接返回,如果不存在,到extension中查找,存在直接返回,不存在继续向父加载器中寻找直到bootstrap顶层,如果依然没找到,那就是没有加载器加载过这个类,需要委派对应的加载器来加载,先看看这个类是否在自己的加载范围内,如果是直接加载返回结果,若不是继续向下委派,以此类推直到最下级,如果最终也没能加载,就会直接抛异常classnotfoundexception,这就是双亲委派模式。
理解双亲委派模式:
1、父加载器:不是类加载器的加载器,也不是类加载器的父类加载器(此处意思是没有父类与子类之间的继承关系)。
package com.example.demo.classloader; /** * 验证了父加载器不是加载器的加载器 */ public class parentandchild { public static void main(string[] args) { //appclassloader classloader classloader = parentandchild.class.getclassloader(); system.out.println(classloader); //null 这里appclassloader的加载器不是extclassloader 而是bootstrap classloader appclassloader = parentandchild.class.getclassloader().getclass().getclassloader(); system.out.println(appclassloader); //extclassloader appclassloader的父加载器是extclassloader classloader parent = parentandchild.class.getclassloader().getparent(); system.out.println(parent); //null classloader parentparent = parentandchild.class.getclassloader().getparent().getparent(); system.out.println(parentparent); //null classloader parentparentparent = parentandchild.class.getclassloader().getparent().getparent().getparent(); system.out.println(parentparent); /**输出结果*/ //sun.misc.launcher$appclassloader@18b4aac2 //null //sun.misc.launcher$extclassloader@23fc625e //null //exception in thread "main" java.lang.nullpointerexception at com.example.demo.classloader.parentandchild.main(parentandchild.java:22) } }
2、双亲委派:其工作原理的是,如果一个类加载器收到了类加载请求,并不会直接去加载,而是自下而上的向顶层类加载器查找是否已经被加载了,如果被加载就不用进行加载,如果未被加载过,则会自上而下的检查是否属于自己加载的范围,如果属于则加载,如果不属于则向下委托,直到类被加载进来才能叫做成功,如果加载不成功就会抛异常classnotfoundexeption,这就叫做双亲委派。
3、为什么要搞双亲委派模式?
主要是为了安全,这里可以使用反证法,如果任何类加载器都可以把class加载到内存中,我们就可以自定义类加载器来加载java.lang.string。在打包时可以把密码存储为string对象,偷偷摸摸的把密码发送到自己的邮箱,这样会造成安全问题。
三、自定义类加载器
package com.example.demo.classloader; public class classloaderbyhand { public static void main(string[] args) throws classnotfoundexception { class<?> clazz = classloaderbyhand.class.getclassloader(). loadclass("com.example.demo.threaddemo.juc_002.account"); string name = clazz.getname(); system.out.println(name); } } /** * 输出结果 */ //com.example.demo.threaddemo.juc_002.account
代码运行结果可以看出,就是你要加载一个类你只要调用classloader中的loadclass()方法就能把这个类加载到内存中,加载完成之后会给你返回一个class类的对象。
在硬盘上找到这个类的源码,把它load到内存,与此同时生成一个class对象,上述的小程序是通过classloaderbyhand 找到他的加载器appclassloader 然后调用它的loadclass()方法,让它帮我们把account类加载进来,返回一个clazz对象,使用clazz.getname()方法正常返回account类。
什么时候我们需要自己定义去加载一个类?
热部署时就是先把之前加载的类给干掉 ,然后使用的自定义类加载器来进行重新加载
spring的动态代理,一个新的class 当需要的时候就会把它load到内存中
我们还是来看一下源码吧,加载过程最主要的还是classloader中的loaderclass()方法:
结合上面给的类加载过程的图解一起看会更容易一些;
protected class<?> loadclass(string name, boolean resolve) throws classnotfoundexception { synchronized (getclassloadinglock(name)) { /** * 在加载之前先调用findloadedclass()方法查看是否已经加载过此类 * 若加载过 返回该对象 * 如果未加载则返回null 进行下一步 */ // first, check if the class has already been loaded class<?> c = findloadedclass(name); if (c == null) { long t0 = system.nanotime(); try { //判断有无父加载器 如果不为空说明还未到顶层bootstrap递归调用loadclass() if (parent != null) { c = parent.loadclass(name, false); } else { //如果没有父加载器说明调用的加载器为bootstrap class loader, 在此加载器内存中查找是否已经加载 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(); //调用自己的findclass() 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; } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 索泰非公RTX 2060/2070 Super显卡深度图解评测
下一篇: 咱们复合吧