一、类的加载过程
Java虚拟机在运行时会动态加载需要用到的类,称为“类的加载”。类的加载可以分三个步骤:加载、连接和初始化。
1、加载:在加载阶段,类加载器首先通过类的全限定名来读取类的二进制数据,并将其存放在JVM的方法区中。类的全限定名是包含了包名的全称,如"java.lang.Object"。
2、连接:在连接阶段,类的验证、准备和解析都会在此阶段进行。
(1)验证:验证是确保被加载的类符合JVM规范的一个重要步骤。如果类的验证不通过,则会抛出异常并终止程序运行。
public class VerifyTest { public static void main(String[] args) { System.out.println("hello"); } }
(2)准备:准备是为静态变量分配内存空间并设置默认值的过程。在此阶段,类中声明的所有static变量都会赋值为默认值。
public class PrepareTest { private static int num; private static String str; public static void main(String[] args) { System.out.println(num); // 输出 0 System.out.println(str); // 输出 null } }
(3)解析:解析是将符号引用转换为直接引用的过程。在解析阶段,会将类、接口、字段和方法的符号引用转换为直接引用。
public class ResolveTest { public static void main(String[] args) { System.out.println("hello world"); } }
3、初始化:在初始化阶段,类的静态变量和静态块会按照程序中的顺序依次执行。
public class StaticBlockTest { private static String str = "hello world"; static { System.out.println(str); } public static void main(String[] args) { System.out.println("main method"); } }
二、类的加载器
Java虚拟机中有三类类加载器:
1、启动类加载器:负责加载JRE/lib目录下的核心类库,如rt.jar等。
2、扩展类加载器:负责加载JRE/lib/ext目录下的扩展类库。
3、应用程序类加载器:负责加载用户类路径上所指定的类库,一般是项目中的class文件和jar包。
除了这三个类加载器之外,还可以通过编写自定义类加载器来实现特定的加载需求。
三、类的加载机制
类的加载机制是指类的加载器在加载类时的一系列策略和规则。主要有以下几种:
1、双亲委派模型:Java虚拟机中的类加载器采用了双亲委派模型。即在类加载器加载某个类时,先将这个任务委托给父类加载器完成,如果父类加载器无法完成,则由子类加载器尝试去加载。
public class ClassLoaderTest { public static void main(String[] args) { ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while (loader != null) { System.out.println(loader.getClass().getName()); loader = loader.getParent(); } } }
2、负责命名空间:每个类加载器都拥有自己的命名空间。命名空间是由该加载器及其所有父类加载器所加载的类所组成的集合。同一个命名空间内的类是有相同的类全称的,不同命名空间内的类是由不同的类全称的,即使class文件的字节码相同。
public class NameSpaceTest { public static void main(String[] args) throws Exception { MyClassLoader loader1 = new MyClassLoader("loader1"); loader1.setPath("C:\\myapp\\loader1\\"); MyClassLoader loader2 = new MyClassLoader(loader1, "loader2"); loader2.setPath("C:\\myapp\\loader2\\"); Class clazz1 = loader1.loadClass("com.myapp.Test"); Class clazz2 = loader2.loadClass("com.myapp.Test"); System.out.println("clazz1的类加载器为:" + clazz1.getClassLoader().getClass().getName()); System.out.println("clazz2的类加载器为:" + clazz2.getClassLoader().getClass().getName()); System.out.println("clazz1和clazz2是否相等:" + (clazz1 == clazz2)); } }
3、父类加载器对子类加载器不可见:子类加载器可以访问父类加载器所加载的类,而父类加载器无法访问子类加载器所加载的类。
4、系统类加载器和应用程序类加载器都是有启动类加载器所加载的。