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

深入理解Java虚拟机:JVM类加载机制

时间:2023-04-01 19:12:35 Java

类加载首先我们要明确JVM是动态加载class类的,而不是一次加载程序中的所有class文件(延迟加载机制)。当我们需要这个类的时候,比如程序运行过程中classA需要调用classB的另一个方法,但是classB并没有被加载,那么我们就需要通过类加载机制动态加载classB到内存中,只有在classB被加载的时候对于内存来说,它的方法可以被classA调用。类加载:类文件中的信息通过加载、链接和初始化三个过程加载到JVM内存中。一、加载:1、通过类的全限定类名获取本类的二进制流;如何找到它?在系统类和指定的类路径中搜索。如果是class文件的根目录,直接查看是否有对应的子目录和文件。如果是jar文件,现在在内存中解压该文件,然后查看是否有对应的子目录。种类。对于数组类,它没有对应的字节流,而是由Java虚拟机直接生成的,只不过数组类的元素类型是由类加载器加载的。对于其他的类,Java虚拟机需要使用类加载器来完成寻找字节流的过程。2.将二进制流的静态存储结构转换为方法区的运行时数据结构;3.生成一个java。类加载器在这个过程中起着重要的作用。类加载器主要有两个作用:一:用于动态加载类类,将类的字节码形式转换为内存形式的类对象。为什么是动态的?这就涉及到JVM的延迟加载机制。JVM并不是一次性加载程序中的所有类,而是按需加载。即当程序中遇到新的类时,调用ClassLoader加载它。Class,加载完成后,Class对象会保存在ClassLoader中,下次就不需要加载了。一个编写良好的Java程序可能有很多类。这些类有很多方法。程序运行时,经常需要从另一个类中调用一个类中的方法,但系统启动时并不会一次性加载程序中的所有方法。所有的class文件,只是根据需要通过JVM类加载机制动态加载一个class文件到内存中。只有当class文件加载到内存中后,才能被其他类引用,所以使用classLoader来动态加载类。文件存入内存。一个更形象的例子是调用某个类的静态方法,那么这个类需要加载(因为类创建后静态方法就存在),但是类的实例不需要加载,而instance被程序调用时会加载。二:是类的命名空间,起到类隔离的作用。同一个ClassLoader加载的类名是唯一的,不能重复。在不同的类加载器中具有相同名称的类是两个不同的类。类的唯一性由类加载器实例和类的全名决定。即使通过不同的类加载器加载同一串字节流,也会得到两个不同的类。在大型应用程序中,我们经常使用这个特性来运行同一个类的不同版本。类加载器:启动类加载器:在Java8中主要加载java的基础类,即JAVA_HOME/jre/lib/rt.jar下的所有类。Java9之后,负责在启动时加载基础模块类(如java.base;java.manager;java.xml)。它是用C++实现的,在JVM系统中不存在,所以输出为null。JVM启动时,通过BootstrapClassLoader加载rt.jar,并初始化sun.misc.Launcher,创建ExtensionClassLoader和ApplicationClassLoader实例。扩展类加载器主要加载\lib\ext或java.ext.dirs系统变量指定路径下的所有类库;加载一些扩展的系统类,比如XML、加密、压缩相关的功能类。它由sun.misc.Launcher$ExtClassLoader实现;向上继承自URLClassLoader,URLClassLoader向上继承自SecueClassLoader,SecueClassLoader向上继承自ClassLoader。Java9之后,取而代之的是平台类加载器,加载平台相关的模块,如java.scripting、java.compiler、java.corba等。应用类加载器在Java8中主要加载类路径下的所有jar包和类,在Java9中用于加载应用级模块,包括jdk.compiler;jdk.jartool;jdk.jshell。它由sun.misc.Launcher$AppClassLoader实现。如果应用程序没有定义自己的加载器,加载器也是默认的类加载器。loader可以通过java.lang.ClassLoader.getSystemClassLoader获取。双亲委托机制首先判断类是否已经自底向上加载。如果已经加载,则直接返回。如果未加载,它会尝试从顶部加载该类。当父类没有需要的类无法加载时,子加载器会尝试完成加载。如果JVM内置的类加载器无法加载,有自定义的加载器进行加载。具体来说:当一个ClassLoader实例需要加载某个类时,它会尝试自己去寻找某个类。这个任务委托给它的父类加载器。这个过程从上到下依次检查。首先,顶级类加载器BootstrapClassLoader尝试加载。如果没有加载,则将任务转到ExtensionClassLoader去尝试加载。如果不加载,则交给AppClassLoader加载。如果没有加载,则返回给委托的发起者,由它加载指定文件系统或网络的URL中的类。如果它们都没有加载到此类中,则会抛出ClassNotFoundException。否则,为找到的类生成一个类定义,加载到内存中,并执行后续步骤,最后返回内存中该类的Class实例对象。双亲委派模式:双亲委派模式可以简单理解为优先和共享这两个关键词。为了防止Java核心API被替换,根加载器首先加载。如果不是,则从上到下加载。当多个子类加载器共享同一个父类时,可以认为这个父类中包含的类是所有子类加载器共享的。这也是为什么BootstrapClassLoader被所有类加载器视为祖先加载器,JVM核心类库自然应该共享的原因。2、连接1的验证主要是保证class文件的字节流中包含的信息符合Java虚拟机规范,保证这些信息在使用时不会危及虚拟机本身的安全作为代码。主要是格式校验、元数据校验(是否符合Java语言规范)、字节码校验(保证程序语义合法、合乎逻辑)、符号校验(保证接下来的分析能正常进行)。这很重要,但不是强制性的。2准备在方法区为静态变量分配内存,并设置初始默认值。只有类变量(即静态变量),不包括实例变量,实例变量在对象实例化后随对象一起分配到Java堆中。JDK7之前类变量存放在方法区,7以后和Class对象一起存放在Java堆中。(这里可以测试默认值,int0,long0等)。3虚拟机将常量池中的符号引用替换为直接引用。解析动作主要针对7类符号引用进行:类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符。符号引用是一组描述目标的符号,可以是任何文字。直接引用是直接指向目标的指针、相对偏移量或间接定位目标的句柄。关于多态性的更多问题注意:Java虚拟机规范不要求在链接期间进行解析。它只是规定,如果某些字节码使用了符号引用,则需要在执行这些字节码之前完成对这些符号引用的解析。3.初始化就是给标记为常量值的字段赋值并执行方法的过程。当存在继承关系时,先初始化父类,再初始化子类,所以创建子类时,内存中实际上有两个对象实例。如果我们要初始化一个静态字段,可以在声明的时候直接赋值,也可以在静态代码块中赋值。如果直接赋值的静态字段被final修饰,其类型为基本类型或字符串,则该字段会被Java编译器标记为常量值(ConstantValue),其初始化将直接由Java虚函数完成机器。其他的直接赋值操作,以及所有静态代码块中的代码,都会被Java编译器放在同一个方法中,并命名为。Java虚拟机使用锁来保证一个类的方法只执行一次,利用这个特性来实现单例的惰性初始化。