jvm如何加载class文件

原创
02/07 12:15
阅读数 645

编译期:

javac是JDK自带的编译器, 可以将java文件编译为class字节码文件,

javap是JDK自带的反编译器,将.class字节码反编译为.java文件,javap -help是javap常用指令,javap -c XXX.class可以反编译字节码文件,

还会输出一系列虚拟机指令,这些指令就是java虚拟机指令。

为什么不直接生成机器码去执行?

1.每次生成机器码都需要做大量准备,而不同系统需要生成不同的机器码。生成.class这样的中间码,可以交给虚拟机到处执行,不关心是什么机器语言。

2.也可以使用其他语言,来生成.class文件,由java虚拟机去执行,兼容性更好。

java虚拟机组成

ClassLoader:根据特定格式,加载class文件到内存。

Execution Engine:命令解释器,对加载的命令进行解析。

Native Interface:融合不同开发语言的原生库为java所用。在jdk中,可以存放一些其他语言编写的类库,java的一些原生方法中也会调用native修饰的方法,就是通过native interface,去调用这些其他语言类库的接口。

Runtime Data Area :JVM内存空间结构模型。

ClassLoader

classloader的作用:

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

classloader的种类:

BootStrapClassLoader:C++编写,加载核心库java.*,必须看JVM代码才能看的到    

ExtClassLoader:java编写,加载扩展库javax.*

AppClassLoader:java编写,加载程序所在目录,加载class.path路径下的所有class,而这些class又是由avac编译好的

自定义ClassLoader:java编写,定制化加载。

自定义ClassLoader的实现:

关键函数:

// 寻找根据名称和path,寻找class文件,并读取文件为二进制数据流

protected Class<?> findClass(String name) throws ClassNotFoundException{

        throw new ClassNotFoundException(name);

}

// 处理二进制数据流,并返回class对象

protected final Class<?> defineClass(byte[] b, int off, int len)throw ClassFormatError{

        return defineClass(null, b , off, len, null);

}

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 = loaderClassData(name);
        return defineClass(name, b, 0, b.length);
    }
    private byte[] loaderClassData(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{
            out.close();
            in.cloase();
        }
    }
}

类加载器双亲委派机制

1.BootStrapClassLoader,是基于C++写的,它的主要函数findBootStrapClassOrNull(name)方法是一个native方法,调用C++库去执行的。

2.ClassLoader类中的LoadClass方法的代码,会被Synchronized关键字修饰,防止多个加载器同时加载同一个类,这也是为什么类加载是单例的,因为加了同步锁。

3.ClassLoader类中有一个成员变量 private ClassLoader parent;该变量的意思就是,当此ClassLoader为CustomerClassLoader时,它的parent就是App ClassLoader;以此类推,AppClassLoader的parent就是ExtClassLoader等等。当当前加载器,加载不到类时,会调用parent的类加载器去加载。

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

因为它能防止多份同样的字节码文件加载,如果不用委派,而是每个加载器自己加载自己的,同样的字节码文件可能会加载多次;而是用双亲委派,就会从自定义加载器或者app加载器向上查找,看哪个加载器是否已经加载过这个字节码文件就好。

类的加载方式

隐式加载:new,调用构造器方法,支持传入参数,给对象赋值。

显示加载:loadClass,forName等,class.newInstance(),不支持传入参数,需要通过反射,给对象赋值。

loadClass和forName的区别

都能在运行时,知道一个类的属性和方法,对于任意一个类的对象,都能调用类的方法和属性。

类的装载过程

1.加载 通过classLoader加载class文件字节码,生成class对象

2.链接:校验:检查加载的class正确性和安全性

             准备:为类变量(static)分配存储空间并设置类变量初始值(类型的默认值)

             解析:JVM将常量池内的符号引用转换为直接引用

3.初始化:执行类变量赋值和静态代码块

ForName的特点

Class.forName("");会执行类的静态代码块代码,会对类进行初始化。

我们在获取JDBC连接的时候:Class.forName("com.mysql.jdbc.Driver")时,Driver的静态代码块里,就初始化了数据库连接

loadClass的特点

spring ioc因为大量使用了懒加载,所以spring ioc托管的类,会使用loadClass方法来加载,会生成class文件,但是不会初始化值。把类的初始化工作,留到实际使用到这个类的时候才去做。

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部