笔记-JVM的类加载过程

Java代码运行在JVM之上,JVM的运行情况对于Java程序至关重要。因此掌握JVM中的关键机制会对编写稳定的,高性能的Java程序至关重要。

JVM规范中定义的标准结构如下图所示:


JVM负责装载class文件并执行,class文件通常由类加载器(ClassLoader)来完成加载。class的执行在Sun JDK中有解析执行和编译为机器码执行两种方式。其中编译执行又分为client和server两种模式。

类加载机制可在运行时动态加载外部的类,远程网络下载过来的class文件等。除了动态加载,还可以通过JVM的类加载机制来达到类隔离的效果。

JVM将类加载分为三个步骤:装载,链接和初始化。装载和连接过程完成后,即将二进制的字节码转换为Class对象。初始化过程不是加载类时必须触发的,但必须在初次使用该对象前完成。如下图所示:


装载

装载时,JVM通过类的全名和类加载器来完成类的加载。同时也采用【类全名+ClassLoader实例ID】来标识一个被加载的类。

数组类型名称为:"[" +(基本类型或L+引用类型类名)。

链接

链接过程对二进制字节码格式进行校验,初始化装载类中的静态变量以及解析类中调用的接口,类。

二进制字节码的格式校验遵循Java Class File Format规范,如果格式不符合,则抛出VerifyError。校验过程中如果碰到要引用的其他类和接口,也会进行加载。如果加载失败,则会抛出NoClassDefFoundError。

完成校验后,JVM初始化类中的静态变量,并将其值赋为默认值。

最后对类中的所有属性,方法进行验证,以确保其调用的属性和方法存在,以及具备相应的权限。如果这个阶段失败,会抛出NoSuchMethodError,NoSuchFieldError等异常。

初始化

初始化过程即执行类中的静态初始化代码,构造期待吗以及静态属性的初始化。以下四种情况下初始化过程会被触发:

  • 调用了new。
  • 反射调用了类中的方法。
  • 子类调用了初始化。
  • JVM启动过程中指定的初始化类。

JVM的类加载通过ClassLoader及其子类来完成,分为Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader以及User-Defined ClassLoader。这四种ClassLoader的关系如下图所示:


Bootstrap ClassLoader

Sun JDK采用C++实现此类,此类并非ClassLoader的子类,在代码中没有办法拿到这个对象。Sun JDK启动时会初始化这个ClassLoader,并由ClassLoader完成$JAVA_HOME中jre/lib/rt.jar里所有class文件的加载,jar中包含了Java规范定义的所有的接口和实现。

Extension ClassLoader

JVM用此ClassLoader来加载扩展功能的一些jar包,如Sun JDK中目录下有dns工具jar包等,在Sun JDK中ClassLoader对应的类名为ExtClassLoader。

System ClassLoader

JVM用此ClassLoader来加载启动参数中指定的ClassPath中的jar包及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。

User-Defined ClassLoader

它是由开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar及目录。还可以在加载之前对class文件做一些动作,例如解密等。

JVM的ClassLoader采用的是树形结构,如上图所示。加载类时通常按照从上到下的原则进行。也就是说,首先应从parent ClassLoader中加载。如果parent ClassLoader中没有,再从子ClassLoader加载。值得注意的是,由于JVM是采用类名+ClassLoader实例进行加载的,因此加载时不采用上述顺序也是可以的。这样可能会造成多个不同的ClassLoader都加载了某Class,这样使用时可能会带来ClassCastException异常。因此在加载类顺序上需合理把握,尽量保证从根到最下层的ClassLoader上的Class只加载了一次。

ClassLoader抽象类提供了几个关键的方法用于继承:

loadClass

此方法负责加载指定名字的类,ClassLoader的实现方法为:先从已经加载的类中寻找,如没有,则继续从parent ClassLoader中寻找;如果仍然没找到,则从System ClassLoader中寻找,最后在调用findClass方法来寻找。如果要改变类的加载顺序,则可覆盖此方法;如果加载顺序相同,则可以通过覆盖findClass来做特殊处理,例如解密,寻找固定路径等。当通过整个寻找类的过程仍然未获取Class对象时,则抛出ClassNotFoundException。如果类需要resolve,则调用resolveClass进行链接。

findLoadedClass

此方法负责从当前ClassLoader实例对象的缓存中寻找已经加载的类,调用的为native的方法。

findClass

此方法直接抛出ClassNotFoundException,因此要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。

findSystemClass

此方法负责从SystemClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如果仍然未找到,则返回null。

defineClass

此方法负责将二进制字节码转换为Class对象。如果二进制字节码不符合JVM Class文件格式,则抛出ClassFormatError;如果生成的类名和二进制字节码中的不同,则抛出NoClassDefFoundError;如果加载的类是受保护的,采用不同签名的,或者类名是以java.开头的,则抛出SecurityException;如果加载的class在此ClassLoader中已加载,则抛出LinkageError。

resolveClass

此方法完成Class对象的链接,如果链接过,则直接返回。

当Java开发人员调用Class.forName来获取一个对应名称的class对象时,JVM会从方法栈上寻找第一个ClassLoader,并使用此ClassLoader来加载。JVM为了保护加载,执行的类的安全,不允许ClassLoader直接卸载加载了的类。只有JVM才能卸载。在Sun JDK中,只有当ClassLoader对象没有引用时,此CLassLoader对象加载的类才会被卸载。

类加载方面常见的异常有哪些呢?

ClassNotFoundException

原因是当前ClassLoader加载类时没有找到相应的类文件。这里要注意自定义ClassLoader。如果有的话需要具体看一下加载过程。

NoClassDefFoundError

造成此异常的主要原因是加载的类中引用到的另外的类不存在。因此,对于这个异常,需要先看看是加载哪个类报出的,然后再确定该类中引用的类是否存在于当前ClassLoader能加载到的位置。

LinkageError

该异常在自定义ClassLoader的情况下更容易出现,主要原因是此类已经在ClassLoader加载过了,重复的加载会造成该异常,因此要避免在并发的情况下出现这样的问题。由于JVM这个保护机制,使得JVM中没办法直接更新一个已经load的Class,只能创建一个新的ClassLoader来加载更新的class,然后将新的请求转入该ClassLoader中来获取类。其他更多的原因是对象状态的肤质,依赖的设置等。

ClassCastException

该异常由多种原因,在JVM支持范型以后,合理使用范型会减少此异常的触发。这些原因中比较难查的是两个A对象由不同的ClassLoader加载的情况,如果一个对象当另一个不同ClassLoader加载的相同对象使用,也会报ClassCastException。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值