Java类加载机制

Java类加载过程

Java虚拟机规范
Java类加载器

一般我们把Java的类加载分为三个过程: 加载、连接、初始化

加载 Loading

Java将字节码数据从不同的数据源读取到JVM中,并映射为JVM认可的Class对象。这里的数据源可以是jar文件,class文件,网络数据等。如果数据源类型不是ClassFile的结构,则会抛出ClassFormatError。

我们可以自定义类加载器来实现类加载的过程

链接 Linking

可以分为3步。

  • 验证 Verification

    为了保证虚拟机的安全,JVM需要检验字节信息是否符合Java虚拟机规范,否则会认为是VerifyError。

  • 准备 Preparation

    创建类和接口中的静态变量,并初始化静态变量的初始值,非配所需要的内存空间。不会进一步执行JVM指令。

  • 解析 Resolution

    将常量池中的符号引用symbolic reference替换为直接引用。

初始化 Initializa

执行类初始化的代码逻辑,包括静态字段赋值,执行类中静态代码块的逻辑。

在编译阶段会把这部分的逻辑整理好,父类型初始化逻辑要优先于当前类型。

什么是双亲委派模型

当类加载器试图加载某个类时,如果每个类都自己加载需要的类型,会出现重复加载的情况,因此除非父类加载器找不到相应的类型,否则尽量将这个任务代理给当前加载器的父加载器去做。

这样可以避免加载重复类型,减少资源浪费。


准备阶段给静态变量赋值,那么普通静态变量,静态常量(原始类型和引用类型)在加载时有什么区别?

public class CLPerparation {
  public static int a = 10;
  public static final int INT_CONSTANT = 100;
  public static final Integer INTEGER_CONSTAN = Integer.valueOf(1000);
}

编译和反编译一下

Javac CLPreparation.java
Javap –v CLPreparation.class

0: bipush        10
2: putstatic     #2 // Field a:I
5: sipush        1000
8: invokestatic  #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;        
11: putstatic     #4 // Field INTEGER_CONSTAN:Ljava/lang/Integer;

可以看到普通原始类型静态变量引用类型常量,需要额外调用putstatic指令,而原始类型常量不需要。

内建的类加载器有哪些?

以JDK8为例:

Java类加载机制_第1张图片
jdk8类加载机制.png
  • 引导类加载器 Bootstrap Class-Loader

    负责加载jre/lib 下面的jar包,如rt.jar 用原始C++来实现,并不是继承java.lang.ClassLoader

    public final ClassLoader getParent()
    可以通过该方法获取父类的加载器,在扩展类加载器中返回null

  • 扩展类加载器 Extension or Ext Class-Loader

    负责加载放在jre/lib/ext下的jar包,这就是extension机制。

    可以通过java.ext.dirs覆盖

    java -Djava.ext.dirs=your_ext_dir HelloWorld
    
  • 应用类加载器 Application or App Class-Loader

    加载classpath中的内容

    系统类加载器 System Class-Loader 是JDK默认的应用类加载器,可以被修改
    java -Djava.system.class.loader=com.yourpkg.YourClassLoader HelloWorld
    类加载机制有三个基本特征:

  • 双亲委派模型 parent delegation model

    不是每个类加载都遵循这个机制。

    有的时候可能出现加载用户代码的情况。比如JDK中的ServiceProvider/ServiceLoader机制,用户可以在标准API基础上提供自己的实现类,典型的有JDBC,JNDI,Cipher,文件系统等,这些用到的就是所谓的上下文加载器。

  • 可见性

    子类加载器可以访问父类加载器的加载类型,但是反过来不可以,不然会因为缺少隔离性,无法用类加载器实现容器的逻辑

  • 单一性

    由于父类的加载器类型对子类可见,所以父类加载过放类型,在子类中就不会再次加载。

感谢杨晓峰老师

自定义类加载器如何定义?

package classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader2 extends ClassLoader {

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {

        byte[] b = loadClassData(name);
        return defineClass(null, b, 0, b.length);
    }

    private byte[] loadClassData(String name) {
        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        System.out.println(name);
        try {
            inputStream = new FileInputStream(new File(name));
            byteArrayOutputStream = new ByteArrayOutputStream();
            int len = 0;
            byte[] b = new byte[1024];
            while ((len = inputStream.read(b)) != -1) {
                byteArrayOutputStream.write(b, 0, len);
            }
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;

    }

}

测试

package classloader;

public class MyTest {

    public static void main(String[] args) {
        String pathname = "文件路径";
        MyClassLoader2 classLoader = new MyClassLoader2();
        try {
            Class object = classLoader.loadClass(pathname);
            System.out.println(object.getClassLoader());
        } catch (ClassNotFoundException e) {
      e.printStackTrace();
        }
    }
}

Java程序启动慢,类加载器的开销如何减小?

你可能感兴趣的:(Java类加载机制)