当前位置: 首页 > 后端技术 > Java

一、JVM的类加载机制

时间:2023-04-01 15:23:38 Java

1.java中加载类的流程图。主要步骤:加载:在硬盘上查找并读取字节码文件,通过IO读取字节码文件。只有在使用类的时候才会加载,比如调用类的main()方法,新建对象等,加载阶段会在内存中生成一个文件。代表该类的java.lang.Class对象作为该类各种数据在方法区的访问入口。Verify:验证字节码文件的准确性。准备工作:为该类的静态变量分配内存并赋默认值。分析:用直接引用替换符号引用。在这个阶段,一些静态方法(符号引用,例如main()方法)将被替换为指向存储数据的内存的指针或句柄(直接引用)。这就是所谓的静态链接过程(在类加载期间),动态链接是在程序运行期间完成的,用直接引用代替符号引用。初始化:将类的静态变量初始化为指定值,并执行静态代码块。注意:如果主类在运行过程中使用了其他类,这些类会逐渐加载。jar包或者war包中的类并不是一下子全部加载,而是在使用的时候才加载。2、JavaBootstrap类加载器中对应的类加载器:负责加载支持JVM运行的核心类库,位于JRE的lib目录下,如rt.jar、charsets.jar等。Extended类加载器:负责加载支持JVM运行的JRE的lib目录下ext扩展目录下的JAR类包。应用类加载器:负责加载ClassPath路径下的类包,主要是加载自己写的类。自定义类加载器:负责加载用户自定义路径下的类包。类加载器创建流程图:Launch类的相关源码如下://调用getLauncher返回一个Launcher类的实例对象,由JVM创建publicstaticLaunchergetLauncher(){returnlauncher;}//在创建Launcher实例对象时将调用此构造函数/***1,*/publicLauncher(){Launcher.ExtClassLoadervar1;try{/***创建了一个ExtClassLoader(扩展类加载器),这个方法里面会赋值ExtClassLoader*parent属性,这里会赋值为null*/var1=Launcher.ExtClassLoader.getExtClassLoader();}catch(IOExceptionvar10){thrownewInternalError("无法创建扩展类加载器",var10);}try{/***创建了一个AppClassLoader(扩展类加载器),这个方法会给AppClassLoader*parent属性赋值,这里会直接赋值上面创建的ExtClassLoader,并且给class*loader属性赋值一个AppClassLoader的实例对象*/this.loader=Launcher.AppClassLoader.getAppClassLoader(var1);}catch(IOExceptionvar9){thrownewInternalError("无法创建应用程序类加载器",var9);}//设置当前线程的类加载器为AppClassLoaderThread.currentThread().setContextClassLoader(this.loader);//。....省略一些不必要的代码以上代码的主要逻辑是创建两个类加载器ExtClassLoader和AppClassLoader,并分别为null和ExtClassLoader的实例对象赋值对应的parent属性。3.双亲委托机制Java中主要用来加载类的类是java.lang.ClassLoader#loadClass(java.lang.String),所以要看双亲委托机制是如何工作的,我们需要看一下loadClass方法对应的逻辑.loadClass方法的主要源码如下:publicClassloadClass(Stringname)throwsClassNotFoundException{returnloadClass(name,false);}protectedClassloadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){//这里会先判断当前类是否已经加载,如果已经加载则不返回空类c=findLoadedClass(名称);如果(c==null){longt0=System.nanoTime();try{//首先会判断当前类的父类加载器是否为空。刚进来的时候,我们的类型加载器是AppClassLoader,调用父类ExtClassLoader.loadClass,ExtClassLoader调用parent==nullif(parent!=null){c=parent.loadClass(name,false);}else{//当前ExtClassLoader对应的parent为nullc=findBootstrapClassOrNull(name);}}赶上(ClassNotFoundExceptionne){//如果在非空父类加载器中找不到类//则抛出ClassNotFoundException}if(c==null){//如果仍未找到,则调用findClass以//找到该类。长t1=System.nanoTime();//如果之前对应的父类没有加载,就会调用findClass方法。该方法为空实现,由对应的子类c=findClass(name)实现;//这是定义类加载器;记录统计数据sun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}如果(解决){resolveClass(c);}返回c;双亲委托机制简单来说就是先找到父类加载,然后由儿子自己加载。下面我们来看一下应用类加载器AppClassLoader的双亲委派机制的源码。AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法。该方法的大致逻辑如下:首先检查指定名称的类是否已经被加载,如果已经加载,则无需再次加载,直接返回。如果这个类没有被加载过,则判断是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name,false);)或者调用bootstrap类加载器加载。如果父加载器和引导类加载器都没有找到指定的类,则调用当前类加载器的fIndClass方法完成类加载。从上面的代码可以看出,当调用AppClassLoader的loadClass方法时,会先调用父类,然后父类再调用父类,直到parent==null。如果对应的父类没有加载到,则自己加载,这就是双亲委托对应的逻辑。4.为什么要设计双亲委托机制沙箱安全机制:防止JDK核心API类库被外界篡改,比如自己写的java.lang.String.class类不会被加载。避免重复加载类:当父类加载过一次后,子类就不需要再次加载,保证加载类的唯一性。示例://自定义String类,如果包名与内置String一致,会报如下错误。packagejava.lang;publicclassString{publicstaticvoidmain(String[]args){System.out.println("***************我的字符串类***************”);}}运行结果:错误:java.lang.String类中找不到main方法,请将main方法定义为:publicstaticvoidmain(String[]args)否则JavaFX应用类必须扩展javafx.application.Application5,自定义类loader从上面ClassLoader类的源码可以看出,自定义类加载器只需要继承java.lang.ClassLoader类,它有两个核心方法,一个是loadClass(String,boolean),实现了parental委托机制,还有一个方法是findClass,它的默认实现是一个空方法,所以我们自定义的类加载器主要是重写了findClass方法。示例:publicclassMyClassLoadextendsClassLoader{privateStringclassPath;publicMyClassLoad(StringclassPath){this.classPath=classPath;}@OverrideprotectedClassfindClass(Stringname)throwsClassNotFoundException{try{byte[]data=loadByte(名称);返回定义类(名称,数据,0,数据长度);}catch(Exceptionex){ex.printStackTrace();抛出新的ClassNotFoundException();}}}5.1打破双亲委托机制,加入沙箱安全机制例如尝试打破双亲委托机制,使用自定义类加载器加载我们自己的com.jvm.entity.MyUser@OverrideprotectedClassloadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){//首先检查类是否已经加载Classc=findLoadedClass(name);如果(c==null){longt0=System.nanoTime();//删除双亲委派代码尝试{if(this.getParent()!=null&&!name.equals("com.jvm.entity.MyUser")){c=this.getParent().loadClass(name);}}catch(ClassNotFoundExceptione){//如果从非空父类加载器中找不到类//则抛出ClassNotFoundException}if(c==null){//如果仍未找到,则调用findClass以//找到类。长t1=System.nanoTime();c=findClass(名字);//这是定义类加载器;记录统计数据PerfCounter.getParentDelegationTime().addTime(t1-t0);PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);PerfCounter.getFindClasses().increment();}}如果(解决){resolveClass(c);}返回c;}}6、tomcat加载jar包或war包时是否可以使用双亲委派机制?是什么原因?从tomcat实现来看,tomcat负责加载对应部署项目的jar或war包中对应类的自定义类加载器,打破了双亲委派机制,部署在同一个tomcat中的每个项目对应tomcat的自定义类加载器是独立的,即每个项目都会有一个,原因如下:假设有两个项目,一个项目使用spring-expression4.3版本,另一个项目使用spring-expression的版本是5.5.如果tomcat中使用了双亲委派机制,当我们需要加载spring-expression使用的类时,两个项目中最先使用的会先加载(加载前使用),这样会导致后面的项目找不到父类,加载的时候加载,不会加载,所以这里会出问题,会导致后面工程使用的spring-expression的版本和指定的不一样项目本身。如果部署在tomcat中的所有项目共享同一个类加载器,也会出现上述问题。7、本文主要围绕以下五个环节展开:java中如何加载一个类,主要有哪些步骤,每个步骤的作用是什么?java中有哪些类加载器,这些类加载器进程是如何创建的?java中如何实现双亲委派机制?为什么要设计双亲委派机制?如何自定义类加载器,实现自定义类加载器打破相应的双亲委派机制。想一想,能否利用双亲委托机制在tomcat中加载jar或者war包呢?是什么原因?