JVM学习-类加载

本文详细介绍了Java类文件结构、类加载器的工作原理,包括加载、链接(验证、准备、解析)和初始化阶段,以及如何使用反射机制创建实例、获取方法并调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.类文件结构

 2.类加载器 

3.类加载的三个阶段

        3.1加载

        3.2链接 

                3.2.1验证

                3.2.2准备阶段

                3.2.3解析阶段

        3.3初始化

4.拓展:反射

        4.1获取类对象

        4.2创建实例

        4.3获取方法

        4.4方法调用


1.类文件结构

 2.类加载器 

        类加载器用来将类文件的二进制字节码加载到JVM的方法区中。有四种类加载器:

        连接数据库驱动调用的类加载器:在连接数据库时会调用DriverManager类进行驱动,而DriverManager是核心类,所以会使用到启动类加载器;但数据库连接的驱动包并不在核心类库中,所以DriverManager类中有一个loadInitialDrivers()方法,内部使用了两种方式加载驱动:
          第一种是SPI机制加载驱动,约定是在jar包中添加一个META-INF/services目录,在其中添加一个配置文件,文件的名称就是接口的全限定名称,数据库连接驱动就是java.sql.Driver,文件内容就是接口的实现类 ;通过ServiceLoader.load()方法根据约定的路径找到实现类;这个load方法的内部调用的是线程上下文类加载器,由于在创建线程时默认分配的是应用类加载器,所以这种机制实际上调用的是应用类加载器。第二种是使用系统变量jdbc.drivers定义的驱动类的类名加载驱动,调用Class.forName()方法加载和初始化驱动类,使用的是系统类加载器,也就是应用类加载器。

        数据库连接驱动是先调用的系统类加载器再调用的应用类加载器,所以在某些情况下会打破双亲委派机制。

3.类加载的三个阶段

        类加载分为三个阶段:加载、链接、初始化。链接又分为三个阶段:验证、准备、解析。

        3.1加载

        

        要注意的是,一个类的.class文件加载到方法区后变成了C++的instanceKlass文件,这个文件中包含了这个类的各种信息,然后再在堆中生成一个Class类型的对象(区分class,class是定义一个类用的;区分.class文件,这是编译生成的一个类的二进制字节码,还没有被加载),因为java不能直接访问方法区的instanceKlass,所以需要这个Class副本来供我们使用,通过反射拿到的一个类的Class对象就是这么来的,这也就是为什么这个Class对象被叫做类镜像了,下图的Person.class应该是Person的Class对象,InstanceKlass中的_java_mirror存的是Class对象的指针。至于Class对象里有静态成员变量是因为在JDK8和以上版本中静态成员变量就被放在了Class对象的末尾,也就是放到堆中了。

        3.2链接 

                3.2.1验证

        验证这个类转换的字节码是否符合JVM规范,并进行安全性检查,比如检查字节码中的魔数是否是Java文件的魔数。

                3.2.2准备阶段

        给静态变量分配空间,并设置默认值: 

  • 静态变量在JDK7和之前的版本中是放在instanceKlass的末尾也就是方法区中,而从JDK8开始则是放在了类镜像末尾,也就是堆内存中
  • 静态变量的空间分配在准备阶段完成,而赋值则是在初始化阶段,但是final类型的静态变量比较特殊:如果是final的基本类型或字符串类型的静态变量,则分配空间和赋值都在准备阶段完成,因为对于这些类型的变量而言final说明值不会改变,已经确定了静态变量的值,所以在准备阶段会直接赋值;而如果是final的引用类型的静态变量则赋值会放在初始化阶段,因为new一个对象需要类先初始化完成后才能创建。
                 3.2.3解析阶段

        将常量池的符号引用解析为直接引用。符号引用就是这个类虽然被加载了,但由于还没有进行解析,也就不知道这个类在内存中的位置,相当于只是一个符号;而直接引用就是经过了解析之后,知道了其在内存中的具体位置,就可以访问这个类了。

        3.3初始化

        类的初始化是为了确保类的结构正确并且所有的数据都已初始化为预期的状态,只有在类的初始化完成后才能在系统中正常使用这个类及其方法和属性。初始化过程主要包括给静态变量赋值、静态代码块的执行等,只有首次主动使用时才会触发初始化,初始化是懒惰的,且只进行一次。
        初始化发生的时机:

  • main方法所在的类会先进行初始化。
  • 首次访问这个类的静态变量或静态方法时。
  • 子类进行初始化时,若父类还没有初始化,则会先进行父类的初始化再进行子类的初始化。
  • 当子类访问父类的静态变量时,只会触发父类的初始化。
  • 当执行Class.forName方法时,会执行类加载并默认进行初始化;当然也可以给参数initialize设置为false表示不执行初始化。
  • 通过new创建实例化对象时会触发初始化。

        以下情况不会触发初始化:

  • 访问静态常量时,因为静态常量的空间分配和赋值均在链接时的准备阶段完成。
  • 使用类加载器的loadClass方法时,loadClass方法只进行加载阶段。
  • 访问类对象的.class文件时不会触发初始化,因为Class对象在class文件加载到方法区后就会生成,所以在加载阶段时就已经生成。
  • 创建该类的数组不会触发初始化,因为在JVM中会生成一个其他的类来表示数组类型,与原本的类无关,所以不会触发原来的类的初始化。

4.拓展:反射

        反射:通过使用类对象(即堆中的Class对象)来创建实例、调用方法等。   

        4.1获取类对象

Class c=类名.forName();
或
Class<?> c=类名.class;
或
Class<?> c=类名.getClass();

        以下操作都建立在获取了Class对象的基础上

        4.2创建实例

Object o=c.newInstance();

        也可以通过指定的构造器来创建实例,比如使用String的构造器来创建实例:  

//先获取String类的带一个String类型参数的构造器
Constructor cst=c.getConstructor(String.class);  

//再通过调用构造器的newInstance方法来创建实例  
Object o=cst.newInstance("abc");                         

        4.3获取方法

//获取这个类的除继承父类的方法外的其他所有方法
Method[] m1=c.getDeclaredMethods();

//获取这个类的所有公有方法               
Method[] m2=c.getMethod();   
                         
//获取指定方法 
Method m=c.getMethod("方法名");                    

         4.4方法调用

//先获取指定的方法 
Method m=c.getMethod("方法名"); 

//调用方法:
//当所调用的方法既有参数也有返回值时
Object result=m.invoke(参数集);

//当所调用的方法没有返回值且无参数时
m.invoke(null);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值