关于JVM的类加载过程,可以分为以下3个阶段:加载、连接和初始化。下面分别介绍:
加载
类加载的过程就是将类的.class文件中的二进制码读入到内存中,将其放在方法区(JDK 1.8之前称为永久代,JDK 1.8之后将永久代移除,改为元空间)内,然后在堆区构造一个java.lang.Class对象,用来封装类在方法区内的数据结构。
类的加载阶段由类加载器完成,Java虚拟机中内置了三个类加载器:
- 引导类加载器(Bootstrap ClassLoader):负责加载Java核心库,是用本地代码实现的,并不继承自java.lang.ClassLoader;
- 扩展类加载器(Extension ClassLoader):负责加载JDK的扩展库;
- 应用程序类加载器(Application ClassLoader):负责加载应用程序classpath目录下的类库,是最常用的ClassLoader。
在加载阶段,会执行以下过程:
- 通过类的全限定名获取二进制字节流;
- 将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
- 在内存中生成一个代表该类的java.lang.Class对象,作为该类访问入口;
- Class对象的数据结构中包含了来自编译时的所有常量池、字段、方法等信息。
连接
类的连接分为三个阶段:验证、准备、解析。
1. 验证
验证阶段是为了确保被加载的类满足Java虚拟机的约束条件,以确保程序的正确性和安全性。其中包括以下4个部分:
- 文件格式验证:判断.class文件是否符合规范、格式是否正确等;
- 元数据验证:对字节码描述的信息进行语义分析,保证其符合Java语言规范;
- 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的;
- 符号引用验证:验证类自身以外的信息是否能够被正确地链接。
2. 准备
准备阶段是为类的静态变量分配内存并设置初始值的阶段。变量所使用的内存都将在方法区中进行分配,初始值为默认值。
3. 解析
解析阶段是将常量池中的符号引用替换成直接引用的过程。
初始化
初始化阶段是类加载过程的最后一个阶段,是执行类构造器
示例1:
public class ClassLoaderDemo {
public static void main(String[] args) {
System.out.println("----------1---------------");
new A();
System.out.println("----------2---------------");
new B();
}
}
class A {
public A() {
System.out.println("A类的无参构造函数");
}
static {
System.out.println("A类的静态代码块");
}
{
System.out.println("A类的普通代码块");
}
}
class B {
static {
System.out.println("B类的静态代码块");
}
public B() {
System.out.println("B类的无参构造函数");
}
}
输出结果:
----------1---------------
A类的静态代码块
A类的普通代码块
A类的无参构造函数
----------2---------------
B类的静态代码块
B类的无参构造函数
可以看出:在Java程序中,类的初始化阶段是在执行类的构造函数之前,先执行静态代码块和静态变量(即按照JVM根据类加载器链加载的顺序依次调用静态代码块和静态变量)。在当前示例中,先调用A类的静态代码块和普通代码块,然后再调用A类的无参构造方法,最后调用B类的静态代码块和无参构造方法。
示例2:
public class ClassLoaderDemo {
public static void main(String[] args) throws Exception {
ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
Class<?> cls = classLoader.loadClass("java.lang.String");
System.out.println(cls);
}
}
输出结果:
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at java.lang.ClassLoader.loadClass(ClassLoader.java:429)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at ClassLoaderDemo.main(ClassLoaderDemo.java:6)
可以看出:在Java程序中,虽然类加载器可以将类动态地加载进来,但是Java虚拟机对Java核心库的保护措施非常严格,不允许通过自定义类加载器来加载Java核心库中的类,以保证Java虚拟机的安全性。因此,在当前示例中,由于尝试通过ClassLoader加载Java核心库中的String类,导致安全检查抛出异常。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JVM的类加载过程详细说明 - Python技术站