技术干货代码改变世界程序员

Java 类加载机制分析

2017-05-24  本文已影响0人  EricAlpha

在编写 Java 程序时,我们所编写的 .java 文件经编译后,生成能被 JVM 识别的 .class 文件,.class 文件以字节码格式存储类或接口的结构描述数据。JVM 将这些数据加载至内存指定区域后,依此来构造类实例。

1. 类加载过程

JVM 将来自 .class 文件或其他途径的类字节码数据加载至内存,并对数据进行验证、解析、初始化,使其最终转化为能够被 JVM 使用的 Class 对象,这个过程称为 JVM 的类加载机制。

2. ClassLoader

ClassLoader 是 Java 中的类加载器,负责将 Class 加载到 JVM 中,不同的 ClassLoader 具有不同的等级,这将在稍后解释。

2.1 ClassLoader的作用

ClassLoader 的作用有以下 3点:

2.2 ClassLoader的主要方法

ClassLoader 中包含以下几个主要方法:

3. ClassLoader 的等级加载机制

上文已经提到 Java 中存在不同等级的 ClassLoader,且类加载过程中运用了等级加载机制,下面将进行详细解释。

3.1 Java 中的四层 ClassLoader

3.2 等级加载机制

​ 如同我们在抽象类 ClassLoader 的 loadClass 方法所看到那样,当通过一个 ClassLoader 加载类时,会先自底向上检查父加载器是否已加载过该类,如果加载过则直接返回 java.lang.Class 对象。如果一直到顶层的 BootstrapClassLoader 都未加载过该类,则又会自顶向下尝试加载。如果所有层级的 ClassLoader 都未成功加载类,最终将抛出 ClassNotFoundException。如下图所示:


3.3 为何采用等级加载机制

​ 首先,采用等级加载机制,能够防止同一个类被重复加载,如果父加载器已经加载过某个类,再次加载时会直接返回 java.lang.Class 对象。

​ 其次,不同等级的类加载器的存在能保证类加载过程的安全性。如果只存在一个等级的 ClassLoader,那么我们可以用自定义的 String 类替换掉核心类库中的 String 类,这会造成安全隐患。而现在由于在 JVM 启动时就会加载 String 类,所以即便存在相同 binary name 的 String 类,它也不会再被加载。

4. 从 JVM 角度看类加载过程

​ 在 JVM 加载类时,会将读取 .class 文件中的类字节码数据,并解析拆分成 JVM 能识别的几个部分,这些不同的部分都将被存储在 JVM 的 方法区。然后 JVM 会在 堆区 创建一个 java.lang.Class 对象,用来封装该类在方法区的数据。 如下图所示:

​ 上文提到 .class 文件中的类字节码数据,会被 JVM 拆分成不同部分存储在方法区,而方法区实际就是用于存储类结构信息的地方。我们看看方法区都有哪些东西:

-   类及其父类的 binary name
-   类的类型 (class or interface)
-   访问修饰符 (public,abstract,final 等)
-   实现的接口的全名列表
-   常量池
-   字段信息
-   方法信息
-   静态变量
-   ClassLoader 引用
-   Class 引用

​ 方法区存储的这些类的各部分结构信息,能通过 java.lang.Class 类中的不同方法获得,可以说 Class 对象是对类结构数据的封装。

5. 一个简单的自定义类加载器例子

// 传入 .class 文件的绝对路径,加载 Class
public class FileClassLoader extends ClassLoader {

    // 重写了 findClass 方法
    @Override
    public Class<?> findClass(String path) throws ClassNotFoundException {
        File file = new File(path);
        if (!file.exists()) {
            throw new ClassNotFoundException();
        }
        
        byte[] classBytes = getClassData(file);
        if (classBytes == null || classBytes.length == 0) {
            throw new ClassNotFoundException();
        }
        return defineClass(classBytes, 0, classBytes.length);
    }

    private byte[] getClassData(File file) {
        try (InputStream ins = new FileInputStream(file); ByteArrayOutputStream baos = new  
             ByteArrayOutputStream()) {
            byte[] buffer = new byte[4096];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new byte[] {};
    }
}
上一篇 下一篇

猜你喜欢

热点阅读