java虚拟机 JVM如何加载 .class 文件&反射

  • 1. 原理解析
  • 1.1 原理图
  • 2. 反射机制
  • 2.1 解释
  • 2.2 写一个反射的例子
  • 3. 类从编译到执行的过程
  • 3.1 简述
  • 3.2 谈谈ClassLoader
  • 3.2.1 概念
  • 3.2.2 ClassLoader的种类
  • 3.2.3 自定义一个ClassLoader
  • 3.2.3 类加载器的双亲委派机制
  • 3.2.4 为什么要使用双亲委派机制去加载类
  • 3.2.5 类的加载方式
  • 3.2.5 loadClass 和 forName 的区别


1. 原理解析

1.1 原理图

java怎么加载lua脚本 java如何加载外部class文件_初始化

  1. Class Loader:依据特定格式 ,加载class文件到内存
  2. Execution Engine:对命令进行解析
  3. Native Interface:融合不同开发语言的原生库为java所用
  4. Runtime Data Area:JVM内存结构空间模型
    解释:jvm首先将符合其格式要求的class文件通过ClassLoader加载到内存中,再由Execution Engine对命令解析class中的字节码,并提交给操作系统去执行

2. 反射机制

2.1 解释

Java反射机制是在运行过程中,对任何一个类,都能知道这个类的所有属性和方法;对于任何一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能成为java语言的反射机制。

2.2 写一个反射的例子

1.首先定义一个实体类

public class Robot {
    private String name;

    public void sayHello(String hello) {
        System.out.println(hello + " " + name);
    }

    public String throwHello(String tag) {
        return "hello " + tag;
    }
}

2.反射实例

package com.wangjzm.jvm.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectSample {
    public static void main(String[] args) throws Exception {
        Class<?> rc = Class.forName("com.wangjzm.jvm.reflect.Robot");
        Robot r = (Robot) rc.newInstance();
        // 获取类名
        System.out.println("Class name is " + rc.getName());

        // getDeclaredMethod 能够获取private、public、protected,但是获取不到继承的方法和所实现的接口的方法
        Method throwHello = rc.getDeclaredMethod("throwHello", String.class);
        throwHello.setAccessible(true);
        Object wangjzm = throwHello.invoke(r, "wangjzm");
        System.out.println("getHello result is " + wangjzm);

        Field name = rc.getDeclaredField("name");
        name.setAccessible(true);
        name.set(r, "wangjzm");
        // getDeclaredMethod 能够获取public,继承的public方法和所实现的接口的public方法,但是获取不到private、protected
        Method sayHello = rc.getMethod("sayHello", String.class);
        sayHello.invoke(r, "hello");

    }
}

3. 类从编译到执行的过程

3.1 简述

通过上面反射的例子进行说明:

  1. 编译器将 Robot.java 源文件编译为 Robot.class 的字节码文件;
  2. ClassLoader 将字节码转换成JVM中的 Class< Robot >对象
  3. JVM 利用 Class< Robot > 对象实例化为 Robot 对象

3.2 谈谈ClassLoader

3.2.1 概念

主要工作在Class装载的阶段,主要作用是从系统外部获得Class二进制数据流。它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化操作。

3.2.2 ClassLoader的种类

  1. BootStrapClassLoader:c++编写,加载核心库java.*
  2. ExtClassLoader:Java编写,加载扩展库javax.*
  3. AppClassLoader:Java编写,加载程序所在目录
  4. 自定义ClassLoader:Java编写,定制化加载

3.2.3 自定义一个ClassLoader

/**
* 主要是继承ClassLoader类并实现findClass方法定义自己的规则
* 可以做的事:字节码增强,对class进行加解密操作、aop实现参考
**/
public class MyClassLoader extends ClassLoader {
    private String path;
    private String classLoaderName;

    public MyClassLoader(String path, String classLoaderName) {
        this.path = path;
        this.classLoaderName = classLoaderName;
    }

    //用于寻找类文件
    @Override
    public Class findClass(String name) {
        byte[] b = loadClassData(name);
        return defineClass(name, b, 0, b.length);
    }

    //用于加载类文件
    private byte[] loadClassData(String name) {
        name = path + name + ".class";
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = new FileInputStream(new File(name));
            out = new ByteArrayOutputStream();
            int i = 0;
            while ((i = in.read()) != -1) {
                out.write(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return out.toByteArray();
    }
}

3.2.3 类加载器的双亲委派机制

java怎么加载lua脚本 java如何加载外部class文件_java_02

3.2.4 为什么要使用双亲委派机制去加载类

避免多份同样字节码的加载

3.2.5 类的加载方式

  1. 隐式加载:new
  2. 显示加载:loadClass、forName

3.2.5 loadClass 和 forName 的区别

  1. 类的装载方式

通过查看Class.forName方法可以发现其中forName0方法调用时传入了一个true,表示进行上图第三步初始化操作

@CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

而ClassLoader的loadClass方法调用的liadClass第二个参数传入false ,并没有进行链接和初始化操作

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

验证方式:可以定义一个实体类,其中加上一段static代码段进行测试

总结: Class.forName得到的Class是已经初始化完成的,而ClassLoader.loadClass得到的class没有进行链接和初始化操作

使用总结:

  1. ClassLoader.loadClass 使用场景:SpringIoc里面在资源加载器获取要读入的资源及读取一些bean的配置文件时就需要使用ClassLoader.loadClass 进行加载,之所有这样做是为了Lazy Load延迟加载,加快spring的加载速度,将实际的初始化工作留给使用时再进行初始化
  2. Class.forName使用场景:使用mysql-jdbc包时初始化驱动使用