类加载的生命周期?
1. 加载阶段(Loading)
在Java程序中,当需要使用某个类时,JVM会使用类加载器来查找并加载该类文件。类加载器会首先从文件系统或网络中查找相应的 .class 文件,读取类的二进制数据,并将其存储在JVM的内存中。在这个过程中,JVM会为该类创建一个对应的Java类对象。这个Java类对象包含了该类的类名、父类名、接口名、访问修饰符、成员变量和成员方法等信息。
2. 链接阶段(Linking)
在类加载完成后,JVM会对该类进行链接。链接分为三个步骤:验证、准备和解析。
验证阶段:在验证阶段,JVM会对类文件的格式、依赖关系、语义等进行检查。如果验证失败,JVM会抛出相应的异常。
准备阶段:在准备阶段,JVM会为类的静态成员分配内存空间,并设置默认初始值。在Java程序中,静态变量的默认初始值是0或null,而不是程序员在代码中定义的初始值。如果类的静态成员是常量,则在编译期间就已经分配了内存空间,并设置了正确的初始值。因此,在准备阶段,常量并不需要分配内存空间。
解析阶段:在解析阶段,JVM会将类中的符号引用转换为直接引用。在Java程序中,类中的方法调用、变量引用等都是通过符号引用实现的。而在JVM中,这些符号引用需要被解析为直接引用,以便JVM能够正确地执行程序。在解析阶段,JVM会将符号引用转换为直接引用,并生成相应的调用指令。
3. 初始化阶段(Initialization)
在初始化阶段,JVM会为类的静态成员赋予正确的值。在Java程序中,静态变量和静态代码块的初始化操作都是在初始化阶段完成的。当JVM完成初始化操作后,Java程序才能正常地使用该类。需要注意的是,初始化操作只会执行一次。如果类已经被初始化过,JVM不会再次执行初始化操作。
总体来说,Java类加载器的生命周期可以分为加载、链接和初始化三个阶段。在这三个阶段中,JVM会对类文件进行验证、解析、分配内存空间、设置初始值等操作,以确保Java程序的正确执行。
2.类加载器的层次?
在Java中,类加载器的层次结构可以分为以下三个层次:
启动类加载器(Bootstrap ClassLoader)
启动类加载器是JVM内置的类加载器,它负责加载JRE核心库中的类,包括 rt.jar 和 resources.jar 中的类。它是Java类加载器中唯一没有父类加载器的加载器,由JVM自身实现。在Java程序中,我们无法直接获取到启动类加载器的引用。
扩展类加载器(Extension ClassLoader)
扩展类加载器是用来加载JRE扩展目录中的类,通常位于 jre/lib/ext 目录下。它的父类加载器是启动类加载器。扩展类加载器的实现由JVM提供,它可以通过 java.lang.ClassLoader.getSystemClassLoader().getParent() 获取到其父类加载器。
应用程序类加载器(Application ClassLoader)
应用程序类加载器(也称为系统类加载器)是用来加载应用程序类路径上的类,通常位于 classpath 下的类和库。它的父类加载器是扩展类加载器。应用程序类加载器的实现也由JVM提供,可以通过 java.lang.ClassLoader.getSystemClassLoader() 获取到其引用。
自定义类加载器
除了这三个基本的类加载器,Java还支持自定义类加载器。自定义类加载器可以继承自 java.lang.ClassLoader 类,通过重写其中的 findClass() 方法和 loadClass() 方法实现类的加载。这样可以实现特定的类加载需求,比如从网络或数据库中动态加载类。在实现自定义类加载器时,需要注意遵循双亲委派模型的原则,保证类的唯一性和正确性。
3.Class.forName()和ClassLoader.loadClass()区别?
在Java中,Class.forName() 和 ClassLoader.loadClass() 都可以用来加载类,但二者有一些区别。
类加载器的区别
Class.forName() 方法使用的是当前线程的类加载器(即调用 Class.forName() 方法的类的类加载器)来加载指定的类。如果没有指定类加载器,那么默认使用的是系统类加载器。如果这个类还没有被加载,那么该方法将加载并初始化这个类。如果这个类已经被加载,那么该方法将返回这个类的 Class 对象。此外,Class.forName() 还可以指定是否要初始化这个类。
ClassLoader.loadClass() 方法是一个实例方法,需要通过类加载器的实例来调用。它只是简单地加载指定的类,但不会对其进行初始化操作。如果想对这个类进行初始化,需要调用 Class.forName() 或 Class.newInstance() 方法。
返回值的不同
Class.forName() 方法返回一个 Class 对象,该对象包含了类的所有信息,包括类的名称、方法、属性等。
ClassLoader.loadClass() 方法只是简单地加载类,返回的是一个 Class<?> 类型的对象,只包含类的类型信息。如果需要使用该类的实例对象,还需要通过反射或者其他方式来获取。
加载类的方式的不同
Class.forName() 方法会在加载类的同时对其进行初始化操作,这意味着会执行静态代码块和初始化类变量等操作。
ClassLoader.loadClass() 方法只会简单地加载类,不会执行任何初始化操作。如果需要对类进行初始化,需要手动调用 Class.forName() 或者 Class.newInstance() 方法。
需要注意的是,Class.forName() 和 ClassLoader.loadClass() 在加载类时都会遵循双亲委派模型。即当一个类加载器需要加载一个类时,它首先委派给其父类加载器去加载,如果父类加载器无法加载该类,再由该类加载器自己尝试加载。这样可以保证类的唯一性和正确性。
4.JVM有哪些类加载机制?
Java虚拟机(JVM)的类加载机制主要包括以下几种:
全盘负责机制(Bootstrap ClassLoader)
Java虚拟机内置的启动类加载器(Bootstrap ClassLoader)会负责加载Java平台核心库中的类,包括 java.lang、java.util 等类。由于这些类都是由JVM实现的,因此在加载这些类时,不需要考虑类的版本和安全性问题。
父类委托机制(Parent Delegation Model)
父类委托机制是Java类加载机制中的核心机制。当一个类需要被加载时,它会先委托给父类加载器去尝试加载。如果父类加载器无法加载这个类,那么它才会由当前类加载器自己去加载。这个过程会一直持续到最顶层的启动类加载器为止,如果仍然无法加载该类,那么就会抛出 ClassNotFoundException 异常。
父类委托机制可以保证Java虚拟机中的类不会出现重名的情况,而且可以保证Java类库的安全性。
缓存机制(Cache Mechanism)
缓存机制是指,在类加载器加载一个类之后,会将这个类的Class对象缓存起来,下次再加载这个类时,就可以直接从缓存中取出,避免重复加载。
缓存机制可以提高类的加载效率,避免重复加载类的字节码,但同时也可能会导致内存泄漏问题。
双亲委派机制(Double-Delegation Mechanism)
双亲委派机制是一种更加严格的父类委托机制。在双亲委派机制中,除了顶层的启动类加载器,每个类加载器都有且只有一个父类加载器,并且会优先委托给父类加载器去加载类。如果父类加载器无法加载该类,才会尝试自己去加载。这样可以保证类的唯一性和正确性,避免了重复加载和安全问题。
当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException