jvm类加载

深藏阁楼爱情的钟 2023-09-30 09:29 209阅读 0赞

目录

一、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进制内容

watermark_type_d3F5LXplbmhlaQ_shadow_50_text_Q1NETiBAd2VpeGluXzM4NjEyNDAx_size_7_color_FFFFFF_t_70_g_se_x_16

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

watermark_type_d3F5LXplbmhlaQ_shadow_50_text_Q1NETiBAd2VpeGluXzM4NjEyNDAx_size_16_color_FFFFFF_t_70_g_se_x_16

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

1.加载(loading)

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

双亲委派的流程见下图:

watermark_type_d3F5LXplbmhlaQ_shadow_50_text_Q1NETiBAd2VpeGluXzM4NjEyNDAx_size_20_color_FFFFFF_t_70_g_se_x_16

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

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

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。

三、自定义类加载器

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

  1. import java.io.*;
  2. public class SelfClassLoader extends ClassLoader{
  3. @Override
  4. public Class<?> findClass(String name) throws ClassNotFoundException {
  5. // 自定义加载器加载的路径
  6. File file = new File("/Users/yjn/Desktop/"+name+".class");
  7. try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
  8. // 指定文件的输入流
  9. FileInputStream stream = new FileInputStream(file);
  10. // 指定文件的输出流
  11. int b = 0;
  12. while ((b = stream.read()) != 0){
  13. byteArrayOutputStream.write(b);
  14. }
  15. byte[] bytes = byteArrayOutputStream.toByteArray();
  16. stream.close();
  17. return defineClass(name, bytes, 0, bytes.length-1);
  18. }catch (FileNotFoundException e) {
  19. e.printStackTrace();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }
  23. return super.loadClass(name);
  24. }
  25. }

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

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

  1. protected Class<?> loadClass(String name, boolean resolve)
  2. throws ClassNotFoundException
  3. {
  4. synchronized (getClassLoadingLock(name)) {
  5. // 先查看类是否被当前加载器加载,如果没有加载,则去找找它的父类加载器中是否加载
  6. Class<?> c = findLoadedClass(name);
  7. if (c == null) {
  8. long t0 = System.nanoTime();
  9. try {
  10. if (parent != null) {
  11. c = parent.loadClass(name, false);
  12. } else {
  13. c = findBootstrapClassOrNull(name);
  14. }
  15. } catch (ClassNotFoundException e) {
  16. // ClassNotFoundException thrown if class not found
  17. // from the non-null parent class loader
  18. }
  19. // 如果所有的类加载器中都没有加载,那么就开始尝试自己去加载
  20. if (c == null) {
  21. // If still not found, then invoke findClass in order
  22. // to find the class.
  23. long t1 = System.nanoTime();
  24. c = findClass(name);
  25. // this is the defining class loader; record the stats
  26. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  27. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  28. sun.misc.PerfCounter.getFindClasses().increment();
  29. }
  30. }
  31. if (resolve) {
  32. resolveClass(c);
  33. }
  34. return c;
  35. }
  36. }

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

四、类的加载和执行

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

发表评论

表情:
评论列表 (有 0 条评论,209人围观)

还没有评论,来说两句吧...

相关阅读

    相关 jvm

    1.类加载过程: 首先要加载某个类一定是出于某种目的,比如要运行java程序,那么久必须加载主类才能运行其中的方法,所以一般在这些情况下,如果类没有被加载,就会自动被加载

    相关 jvm

    文章目录 一. 问题背景 二. 类的生命周期 三. 类加载的时机 四. 类加载全过程(有哪些阶段) 4.1 加载阶段 4

    相关 JvmJvm机制

    类加载时机 > 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加