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 【Java类加载机制】执行类初始化的代码逻辑,包括静态字段赋值,执行类中静态代码块的逻辑。
在编译阶段会把这部分的逻辑整理好,父类型初始化逻辑要优先于当前类型。
什么是双亲委派模型 当类加载器试图加载某个类时,如果每个类都自己加载需要的类型,会出现重复加载的情况,因此除非父类加载器找不到相应的类型,否则尽量将这个任务代理给当前加载器的父加载器去做。
这样可以避免加载重复类型,减少资源浪费。
准备阶段给静态变量赋值,那么普通静态变量,静态常量(原始类型和引用类型)在加载时有什么区别?
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.class0: bipush10 2: putstatic#2 // Field a:I 5: sipush1000 8: invokestatic#3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 11: putstatic#4 // Field INTEGER_CONSTAN:Ljava/lang/Integer;

可以看到普通原始类型静态变量引用类型常量,需要额外调用putstatic指令,而原始类型常量不需要。
内建的类加载器有哪些?
以JDK8为例:
Java类加载机制
文章图片
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程序启动慢,类加载器的开销如何减小?

    推荐阅读