描述一下jvm加载,JVM 类加载机制

  描述一下jvm加载,JVM 类加载机制

  类是用Java代码加载的,类型的加载、连接和初始化都是在程序运行过程中完成的。

  它提供了更大的灵活性,增加了更多的可能性。

  # #对类装入器的深入分析

  Java虚拟机和程序的生命周期在下列情况下,Java虚拟机将结束其生命周期。

  1.已执行System.exit()方法。2.程序的正常执行结束。3.程序在执行过程中遇到异常或错误时被异常终止。4.由于操作系统中的错误,Java虚拟机进程终止了类的加载、连接和初始化。

  1.Load:查找并加载类的二进制数据。2.Connect:将已经读入内存的类的二进制数据合并到JVM运行时环境中。验证:确保加载类的正确性准备:为类的静态变量分配内存并初始化为默认值分析:JVM将常量池中的符号引用转换为直接引用3。初始化:给类的静态变量正确的初始值。

  4.用法:例如,若要创建类的对象,请调用类的方法。5.卸载:破坏驻留在内存中的类的数据结构,卸载后不能使用该类创建对象。Java程序可以以两种方式使用该类:1。积极使用;2.被动使用。所有Java虚拟机实现都必须在Java程序“第一次主动使用”每个类或接口时对其进行初始化。

  主动使用(七种)1、创建类的实例2、访问类或接口的静态变量,或者给静态变量赋值3、调用类的静态方法4、反射(如class . forname(" com . yibo . test ")5、初始化一个类的子类6、启动Java虚拟机时标记为启动类的类(包含main方法的类)7、 JDK1.7提供的动态语言支持:如果java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化。 被动使用除了以上六种情况,Java类的其他使用方式都被视为被动使用类,不会导致类初始化(可能是加载连接但没有初始化)。

  类别加载1。类加载是指读取类的。class文件放入内存,放在运行时数据区的方法区,然后在堆区创建一个java.lang.Class对象(规范没有指定类对象的位置,但是HotSpot虚拟机放在方法区)封装方法区类的数据结构。

  2.装载方法。类文件1。直接从本地系统2加载。下载。类文件(URLClassLoader)3。加载。来自zip和jar 4等归档文件的类文件。摘录。来自专有数据库5的类文件。将Java源文件编译成。动态分类3。装入类的最终产品是位于内存中的类对象。类在方法区封装了类的数据结构,为Java程序员提供了访问方法区数据结构的接口。情况1:对于静态字段,只有直接定义字段的类才会被初始化。当一个类被初始化时,它的所有父类都需要被初始化。/* * * * @描述:*对于静态字段,只有直接定义这个字段的类才会被初始化*当一个类被初始化时,要求它的所有父类都已经被初始化* -XX: TraceClassLoading用于跟踪该类的加载信息并打印出来* * JVM参数*三个配置参数:*-XX:option:option on on *-XX:-option:option off *-XX:option=value:将option选项的值设置为value */public class mytest1 { public static void main(string[]args){//子类类名。父类静态变量//system . out . println(my child 1 . str);//子类类名。子类静态变量system . out . println(my child 1 . str 2);} } class my parent 1 { public static String str= hello world ;static { system . out . println( my parent 1静态块);} }类MyChild1扩展my parent 1 { public static String str 2= welcome ;static { system . out . println( my child 1 static block );}}情况二:常量会在编译阶段存储在调用这个常量的方法所在的类的常量池中。本质上,调用类并不直接引用定义该常数的类,所以不会触发定义该常数的类的初始化。/* * * * @说明:*该常量在编译阶段会被存储在调用该常量的方法所在的类的常量池中*本质上,调用类并不直接引用定义该常量的类,因此,不会触发已定义常量类的初始化。*注意:这意味着常量存储在MyTest2的常量池中,然后MyTest2与MyParent2无关。*甚至,我们可以删除MyParent2 * *助记符的类文件:* ldc:表示推送int类型的常量值, float和String从常量池到栈顶* bipush:表示将单字节的常数值(-128-127)推到栈顶* sipush:表示将短整型常数值(-32768-32767)的常数值推到栈顶* iconst_1:表示将int类型的1推到栈顶(iconst _ m1-iconst _ 5)*/public class mytest 2 { public static void main(String[]arg system . out . println(my parent 2 . sh);system . out . println(my parent 2 . I);system . out . println(my parent 2 . in);} } class my parent 2 { public static final String str= hello world ;公共静态最终短sh=7;公共静态final int I=1;公共静态final int in=128static { system . out . println( my parent 2静态块);}}通过反编译可以看到更直观的信息。

  情况3:当一个常量的值在编译时无法确定时,那么它的值就不会被放入调用类的常量池中。这个时候程序运行的时候会导致常量所在的类被主动使用,很明显会导致这个类的初始化。/* * * * @描述:*当一个常量的值在编译时无法确定时,那么它的值就不会被放入调用类的常量池中。*此时,程序运行时,会导致该常量所在的类被主动使用,这显然会导致该类的初始化*/public class mytest 3 { public static void main(string[]args){ system . out . println(my parent 3 . str);} } class my parent 3 { public static final String str=uuid . random uuid()。toString();static { system . out . println( my parent 3静态代码);}}案例4:4:/** * * @描述:*对于一个数组实例,其类型是由JVM在运行时动态生成的,用[lcom . yibo . JVM . class loader . my parent 4 *的形式表示动态生成的类型,其父类型是Object *对于一个数组,JavaDoc往往把组成数组的元素作为组件,实际上就是将数组降维后的类型* *助记符:* anewarray:表示创建一个引用相似的数组(class, 接口、数组)并将其引用值推到栈顶* newarray:意思是创建一个指定基本类型(int、float、char等)的数组。 ),并将其引用值推送到栈顶* */public类mytest 4 { public static void main(string[]args){//my parent 4 my parent 4=new my parent 4();my parent 4[]my parent 4s=new my parent 4[1];system . out . println(my parent 4s . getclass());system . out . println(my parent 4s . getclass()。get super class());my parent 4[][]my parent 4s 1=new my parent 4[1][1];system . out . println(my parent 4s 1 . getclass());system . out . println(my parent 4s 1 . getclass()。get super class());int[]arr=new int[1];system . out . println(arr . getclass());system . out . println(arr . getclass()。get super class());} } class my parent 4 { static { system . out . println( my parent 4静态块);}}完整流程:

  1.加载:是将二进制java类型类文件读入java虚拟机。2.验证:a .准备:为类变量分配内存,设置默认值,但在初始化之前,类变量默认初始化。b .解析:解析过程是在类型的常量池中寻找类、接口、字段和方法的符号引用,并用直接引用替换这些符号引用的过程。3.初始化:初始化类变量的显示。4.类的实例化:a .为新对象分配内存。默认情况下初始化实例变量。c .初始化实例变量的显示。d、java编译器为其编译的每个类至少生成一个实例初始化方法。在java的类文件中,这种实例初始化方法称为init,java编译器为源代码中的每个类构造方法生成一个init方法。有两种类型的类装入器:

  Java虚拟机自带的加载器根类加载器(Bootstrap)(用C写的,程序员用Java代码无法得到这个类)扩展类加载器(Extension),用Java代码实现系统类加载器(application loader) (system)。使用Java代码实现用户定义的类加载器java.lang.ClassLoader的子类(所有用户定义的类加载器都是java.lang.ClassLoader的子类)。用户可以自定义类加载。

  类别载入器不需要等到类别「第一次被主动使用」才载入。该规范允许类加载器在预期要使用某个类时预加载该类。如果。类文件丢失或预加载过程中有错误,类加载器必须在程序第一次主动使用类时报告错误(LinkageError error错误)。如果该类还没有被程序主动使用,那么类加载器就不会报告该类的验证类已加载,然后进入连接阶段。连接是将已经读入内存的类的二进制数据合并到虚拟机的运行时环境中。

  类验证的内容。

  检查类文件的结构:确保类文件遵循Java类文件的固定格式。语义检查:确保类本身符合Java语言的语法,比如验证final类型的类没有子类,final类型的方法没有被字节码验证覆盖:确保字节码流可以被Java虚拟机安全执行。代码流表示Java方法(包括静态方法和实例方法),它是一个称为操作码的单字节指令序列。每个操作码是一个单字节指令序列,每个操作码后面跟一个或多个操作数。字节验证步骤检查每个操作码是否合法,即它是否有合法的操作数。二进制兼容性验证类的准备在准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认初始值。在解析阶段,Java虚拟机将类的二进制数据中的符号引用替换为直接引用。类初始化1。在初始化阶段,Java虚拟机执行类的初始化语句,给类的静态变量赋予初始值。在程序中,有两种方法初始化静态变量:

  a .在静态变量声明处初始化;b .在静态代码块中初始化。2.静态变量和静态代码块的声明被视为类的初始化语句,Java虚拟机按照初始化语句在类文件中的顺序依次执行。类初始化的步骤1。如果该类尚未加载和连接,请先加载并连接它。2.如果添加的类有一个直接父类,并且这个父类还没有初始化,那么先初始化直接父类。3.如果类中有初始化语句,那么依次执行这些初始化语句。当Java虚拟机初始化一个类时,它的所有父类都需要被初始化,但是这个规则不适用于接口。2.初始化类时,它实现的接口不会先初始化。3.初始化接口时,不会首先初始化其父接口。4.因此,父接口不会因为其子接口或实现类的初始化而被初始化。只有当程序第一次使用特定接口的静态变量时,才会引起接口的初始化。5.程序中子类的“主动使用”会导致父类被初始化;但是,父类的“主动”使用不会导致子类的初始化(不可能说生成一个对象类的对象就会导致系统中所有子类的初始化)。6.只有当程序访问的静态变量或静态方法实际定义在当前的类或接口中时,才可以认为是对类或接口的主动使用。7.调用ClassLoader类的loadClass方法来加载一个类不是对该类的主动使用,也不会导致该类的初始化。类加载器类加载器用于将类加载到Java虚拟机中。从1.2版本开始,类加载过程采用父委托机制,可以更好地保证Java平台的安全性。在这种委托机制中,除了Java虚拟机的根类加载器之外,所有的类加载器都只有一个父类加载器。

  ClassLoader ClassLoader,这是一个抽象类。ClassLoader的具体实例负责将java字节码读入JVM。还可以定制Classloader来满足不同字节码流的加载方式,比如从网络加载和从文件加载。ClassLoader负责整个类加载过程中的“加载”阶段。

  有两种类型的类装入器:

  java虚拟机自带的loader BootStrap ClassLoader:用来加载Java的核心库,用c语言写的,程序员在Java代码里拿不到这个类,也不继承java.lang.ClassLoader扩展ClassLoader:用来加载Java的扩展库。Java虚拟机的实现会提供一个扩展库目录。类装入器在这个目录中查找并装入Java类。类加载器(app class loader/system class loader):根据Java应用的类路径加载Java类。一般来说,Java应用类都是由它加载的。可以通过class loader . getsystemclass loader()获取。java.lang.ClassLoader的用户定义类加载器子类(用户定义类加载器是java.lang.ClassLoader的所有子类)用户可以自定义类加载。类加载作品的家长委托机制。父委托模式要求除了顶级启动类装入器之外,所有的类装入器都应该有自己的父类装入器。请注意,父委托模式下的父子关系不是通常的类继承关系,而是使用组合关系来复用父类加载器的相关代码。类别载入器之间的关系如下:

  Java 1.2之后引入了Parent-delegate模式,其工作原理是,如果类加载器收到类加载请求,它不会先加载,而是将请求委托给父类加载器执行。如果父类装入器还有它的父类装入器,它会进一步向上递归委托,请求最终会到达顶层启动类装入器。如果父类装入器能够完成类装入任务,它将成功返回。如果加载了父类加载器,

  ClassLoader的重要方法:加载并返回一个类。

  公共课?Class (string name)抛出ClassNotFoundException定义了一个类,这个方法是不公开调用的。

  受保护的最终类?定义class (byte [] b,int off,int len)查找类,loadClass的回调方法

  受保护阶层?查找类(字符串名)抛出ClassNotFoundException查找已经加载的类。

  受保护的最终类?FindLoadedClass(String name)每个类加载器都有另一个类加载器作为其父类加载器,但bootstrap类加载器除外,它没有父类加载器。

  类加载器的加载机制如下:

  检查类是否自下而上加载。一般情况下,首先从App ClassLoader调用findLoadedClass方法,看是否加载。如果没有加载,就交给父类,Extension ClassLoader会检查是否加载。如果它没有被加载,再次调用它的父类。BootstrapClassLoader检查是否已经加载,如果仍然没有,尝试从上到下加载类,然后尝试依次从Bootstrap ClassLoader加载到App ClassLoader。

  值得注意的是,即使两个类来自同一个类文件,如果用不同的类装入器装入,装入的对象也会完全不同。这种差异体现在equals()、isAssignableFrom()、isInstance()等方法的返回结果中。它还包括使用instanceof关键字对对象的隶属关系的判断结果。

  受保护阶层?loadClass(字符串名称,布尔解析)抛出ClassNotFoundException { synchronized(getClassLoadingLock(name)){//首先检查该类是否已经加载班级?c=findLoadedClass(name);if(c==null){ long t0=system。纳米时间();试试{ if(家长!=null) { c=parent.loadClass(name,false);} else { c=findBootstrapClassOrNull(name);} } catch(ClassNotFoundException e){//ClassNotFoundException如果从非空父类加载器中//找不到类时抛出} if (c==null) { //如果仍然找不到,则调用查找类以便//找到该类长t1=系统。纳米时间();c=查找类(名称);//这是定义类加载器;记录统计信息星期日杂项性能计数器。getparentdelegationtime().添加时间(t1-t0);星期日杂项性能计数器。getfindclasstime().addElapsedTimeFrom(t1);星期日杂项性能计数器。getfindclass().increment();} } if(resolve){ resolve class(c);}返回c;}}从代码上可以看出,首先查看这个类是否被加载,如果没有则调用父类的负载等级方法,直到BootstrapClassLoader(没有父类),我们把这个过程叫做双亲委派,或父类委托。

  创建用户自定义的类加载器公共类MyClassLoader扩展了类加载器{私有字符串类加载器名称;私有字符串路径;私有最终字符串file extension= . 1 类;public my class loader(String class loader name){ super();//将系统类加载器作为该类加载器的父加载器这个。类加载器名称=类加载器名称;} public my class loader(class loader parent,String class loader name){ super(parent);//显示指定该类加载器的父加载器这个。类加载器名称=类加载器名称;} public String getPath(){ return path;} public void set path(String path){ this。path=路径;} @将公共字符串重写为String(){ return mytest 15 { class loader name= class loader name \ } ;} @覆盖保护类?findClass(字符串类名)抛出ClassNotFoundException { system。出去。println( find class invoked: class name );System.out.println(类加载器名称:这个。类加载器名称’);byte[]data=this。加载类数据(类名);返回this.defineClass(类名,数据,0,数据。长度);} private byte[]loadClassData(字符串类名){ InputStream InputStream=nullbyte[]data=null;ByteArrayOutputStream bos=null请尝试{ this.path=path.replace(。,/);这个。类别载入器名称=类别载入器名称。替换(。,/);inputStream=新文件inputStream(新文件(这个。路径类名this。类加载器名称));Bos=new ByteArrayOutputStream();int chwhile((ch=inputstream . read())!=-1){ Bos。写(ch);}数据=Bos。tobytearray();} catch(Exception e){ e . printstacktrace();}最后{ if(inputStream!=null){ try { inputstream。close();} catch(io异常e){ e . printstacktrace();} } if(bos!=null){ try { Bos。close();} catch(io异常e){ e . printstacktrace();} } }返回数据;}公共静态void main(String[]args)引发异常{我的类加载器我的类加载器1=新建我的类加载器(加载器1 );myclass loader 1。设置路径( E:/grad project/JVM _ study/build/classes/Java/main/);班级?clazz 1=我的类装入器1。加载类( com。一博。JVM。类别载入器。我的测试1’);系统。出去。println( claz 1: claz 1 );对象object 1=clazz 1。新实例();系统。出去。println(对象1);系统。出去。println(我的类装入器1。getclass().get class loader());系统。出去。println(-);MyClassLoader myClassLoader2=新的MyClassLoader(myClassLoader1, loader 12 );我的类装入器2。设置路径( E:/grad project/JVM _ study/build/classes/Java/main/);班级?clazz 2=我的类装入器2。加载类( com。一博。JVM。类别载入器。我的测试1’);系统。出去。println( clazz 2: clazz 2 );对象对象2=clazz 2。新实例();系统。出去。println(对象2);系统。出去。println(-);MyClassLoader myClassLoader3=新的我的类加载器(“加载器12”);myclassloader 3。设置路径( E:/grad project/JVM _ study/build/classes/Java/main/);班级?clazz 3=myclassloader 3。加载类( com。一博。JVM。类别载入器。我的测试1’);系统。出去。println( clazz 3: clazz 3 );对象对象3=clazz 3。新实例();系统。出去。println(对象3);}}类加载器的命名空间每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。运行时包由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一运行时包,不仅要看他们的包名是否相同,还要看定义类加载器是都相同。只有属于同一运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的可见成员。

  假设用户自己定义了一个java.lang.Spy类,由用户定义的类加载器加载。由于java.lang.Spy和核心类库java.lang.*由不同的加载器加载,属于不同的运行时包,所以java.lang.Spy无法访问核心类库java.lang包中的包可见成员。

  不同类装入器的命名空间关系。同一命名空间中的类是相互可见的。子加载器的名称空间包含所有父加载器的名称空间。所以子装载器装载的类可以看到父装载器装载的类。例如,系统类装入器装入的类可以看到根类装入器装入的类。父加载程序加载的类看不到子加载程序加载的类。如果两个加载器之间没有直接或间接的父子关系,那么它们所加载的类对彼此是不可见的。类的卸载可以通过反射来访问。1.当一个类被加载、连接和初始化时,它的生命周期就开始了。当代表这个类的类对象不再被引用,也就是不可触及的时候,这个类对象就会结束它的生命周期,这个类在方法区的数据就会被卸载,从而结束这个类的生命周期。因此,一个类何时结束其生命周期取决于代表它的类对象何时结束其生命周期。2.Java虚拟机自己的类加载器加载的类在虚拟机的生命周期中永远不会被卸载。如前所述,Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。Java本身总是引用这些类装入器,这些类装入器也总是引用它们装入的类的类对象,所以这些类对象总是可达的。3.可以卸载由用户定义的类加载器加载的类。父模式问题顶级类加载器无法加载底层类加载器的类。spring Java (rt.jar)如何加载应用类?

  例如,javax.xml.parsers包定义了xml解析的类接口。

  服务接口SPI位于rt.jar中.

  也就是说,接口在启动类加载器中。

  SPI的实现类在AppLoader中。

  这样,BootstrapClassLoader不能用于加载SPI实现类。

  JDK提供了一个解决方案:

  1、线程。setContextClassLoader()

  用于解决顶级类加载器无法访问底层类加载器的类的问题;

  基本思想是,在顶层类加载器中,引入底层类加载器的一个实例。# #获取类加载器的方法

  1.获取当前的类加载器:clazz.getclassloader () 2。获取当前线程上下文的类加载器:thread.currentthread()。getcontextclassloader () 3。获取系统的类加载器:class loader . getsystemclassloader()4、获取调用方的类加载器:driver manager . getcallerclassloader()jar hell问题及解决方法。当一个类或一个资源文件存在于多个jar中时,就会出现Jarhell问题。以下代码可用于诊断问题:

  class loader cl=thread . current thread()。getContextClassLoader();string resourcename= Java/lang/string . class ;枚举URL资源;try { resources=cl . get resources(resource name);while(resources . hasmoreelements()){ URL next element=resources . next element();system . out . println(next element);}} catch (IOException e) {}

  # # # #打破家长模式家长模式是默认模式,但不是必须这样做;Tomcat的WebappClassLoader会先加载自己的类,再委托父类;如果找不到;OSGi的类加载器形成了一个网络结构,类可以根据需要自由加载。参考:[https://blog.csdn.net/weixin _ 43907332/文章/详情/86625277](3359blog.csdn.net/weixin _ 43907332/文章/详情/86625277)[3359 www . luck good/p/8981508 . html](https://www . cn blogs . com/luck good/p/8981508 . html)[3359 www . cn blogs . com/mybatis/p/9396135

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: