jvm类加载

  • Post author:
  • Post category:其他



目录


一、class文件结构


二、jvm的加载、链接、初始化


1.加载(loading)


2.链接(linking)


3.初始化(initializing)


三、自定义类加载器


四、类的加载和执行


一、class文件结构

在了解jvm将class文件加载到虚拟机的过程之前,先大致了解一下class文件结构:

1)magic number:java文件目前在16进制class文件中的标识为cafe babe

2)minor version:子版本号

3)major version:主版本号,1.7为51,1.8为52

4)constant_pool_count:常量池中的内容数量

5)constant_pool:常量池,里面存放当前class使用到的所有常量信息

6)access_flag:对类的相关描述符,包含是否为public,是否为final,是否为借口等

7)this_class:指向常量池的指针,对该类的描述

8)super_class:指向常量池的指针,对父类的描述

9)interfaces_counts:接口数量

10)interfaces:接口信息,各个接口内容为指向常量池的指针

11)fields_counts:字段数量

12)fields:字段信息,每个字段信息包含:字段名称(指向常量池的指针),字段类型(指向常量池的指针),字段描述符

13)methods_counts:方法数量

14)methods:方法信息,每个方法信息包含:方法名称(指向常量池的指针),方法类型(指向常量池的指针),方法描述符

15)attributes_counts

16)attributes

介绍两款idea插件:

1)BinEd:可以查看class文件的16进制内容

2)jClassLib:可以查看class文件里面的内容

二、jvm的加载、链接、初始化

1.加载(loading)

jvm加载类使用的方式是双亲委派的方式,由父到子分别是:bootstrap->ext->app->customer。需要注意的是这里面的父加载器不等同于java中的父类,例如:app的父加载器是ext,但是AppClassLoader和ExtClassLoader加载器对java中的父类加载器是URLClassLoader,这两个类都是Launcher的内部类。

双亲委派的流程见下图:

大体的一个流程为:先从下往上进行判断类是否已经加载,如果加载就直接结束;如果都没有加载,到达最上层以后,再从上往下判断加载器是否可以加载,如果可以,则进行加载并结束,如果不能加载,则一直往下进行该流程。采用双亲委派机制的好处是安全,防止加载一些同名类,影响系统安全,也能保证每个类只加载一次。

每个类加载器都有属于自己的加载范围:

1)bootstrap类加载器的加载范围是(System.getProperty(“sun.boot.class.path”)):

/jdk1.8.0_192.jdk/Contents/Home/jre/lib/resources.jar

/jdk1.8.0_192.jdk/Contents/Home/jre/lib/rt.jar

/jdk1.8.0_192.jdk/Contents/Home/jre/lib/sunrsasign.jar

/jdk1.8.0_192.jdk/Contents/Home/jre/lib/jsse.jar

/jdk1.8.0_192.jdk/Contents/Home/jre/lib/jce.jar

/jdk1.8.0_192.jdk/Contents/Home/jre/lib/charsets.jar

/jdk1.8.0_192.jdk/Contents/Home/jre/lib/jfr.jar

/jdk1.8.0_192.jdk/Contents/Home/jre/classes

2)ext类加载器的加载范围(System.getProperty(“java.ext.dirs”)):

/Users/yangjianing/Library/Java/Extensions

/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext

/Library/Java/Extensions

/Network/Library/Java/Extensions

/System/Library/Java/Extensions

/usr/lib/java

3)app类加载器的加载范围(System.getProperty(“java.class.path”)):

/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/charsets.jar

/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/deploy.jar

/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/cldrdata.jar

/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/dnsns.jar

/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/jaccess.jar

/Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/jre/lib/ext/jfxrt.jar

4)customer类加载器由自己定义

类加载器的加载范围可以再Launcher类的源码中找到,可以自己进行打印查看。

2.链接(linking)

链接分为三步:

1.验证(verification):验证class文件格式是否正确,例如:java的class文件是否以cafe baby开头。

2.准备(preparation):将类的静态变量赋初始值,需要注意的是,这一步是将静态变量赋初始值,例如:类中的一个变量public static int = 12;在这一步完成的时候,int的值是0。

3.解析(resolution):将符号引用改为直接引用,在上面的class文件说明中,很多信息都是指向常量池,这一步就是将常量池中的符号引用解析为能够直接访问的指针,偏移量等。

3.初始化(initializing)

给静态变量赋初始值,这一步才会将上面的int的值赋为12。

三、自定义类加载器

自定义类加载器位于双亲委派中的最下层位置,也是可以由开发者自行处理的类加载器,可以用来加载项目以外需要特殊加载的类,自定义加载器的实现方法也比较简单啊,查看以下代码:

import java.io.*;

public class SelfClassLoader extends ClassLoader{

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 自定义加载器加载的路径
        File file = new File("/Users/yjn/Desktop/"+name+".class");

        try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
            // 指定文件的输入流
            FileInputStream stream = new FileInputStream(file);
            // 指定文件的输出流

            int b = 0;

            while ((b = stream.read()) != 0){
                byteArrayOutputStream.write(b);
            }
            byte[] bytes = byteArrayOutputStream.toByteArray();
            stream.close();
            return defineClass(name, bytes, 0, bytes.length-1);

        }catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.loadClass(name);

    }
}

继承ClassLoader类,然后重写其中的loadClass方法即可。

自定义加载器也可以打破双亲委派机制,先看一下ClassLoader源码中的实现双亲委派机制的代码,如下:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 先查看类是否被当前加载器加载,如果没有加载,则去找找它的父类加载器中是否加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                // 如果所有的类加载器中都没有加载,那么就开始尝试自己去加载
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

如果想要打破双亲委派机制,那么就只需要改变上面的查找和加载机制即可。

四、类的加载和执行

1)jvm中类的加载时采用lazyLoading的方式,只有当该类要被使用的时候才会去被加载,有以下五种情况会触发加载:

1.new getstatic putstatic invokestatic指令,访问final变量除外

2.java.lang.reflect对类进行反射调用时

3.初始化子类的时候,父类首先初始化

4.初始化子类的时候,父类首先初始化

5.动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化

2)java属于一种解释执行的语言,但是这不是绝对的,jit编译器可以实现java的编译执行,目前java一般是解释+编译混合执行。可以通过设置参数来指定java时如何执行,有以下三个参数:

-Xint:解释执行模式,启动速度快,执行效率较低

-Xcomp:编译模式,启动速度慢,执行效率较高

-Xmixed:混合执行,开始阶段使用解释执行,启动较快,在运行阶段对热点代码进行编译,可以加快执行效率



版权声明:本文为weixin_38612401原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。