图解类加载

4 篇文章 0 订阅
1 篇文章 0 订阅

类加载

类加载阶段

加载

  • 将类的字节码载入方法区(1.8后为元空间,在本地内存中)中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:
    _java_mirror 即 java 的类镜像,例如对 String 来说,它的镜像类就是 String.class,作用是把 klass 暴露给 java 使用,镜像起到一个桥梁的作用,Java对象不能直接访问instanceKlass 的信息,它得通过镜像_java_mirror 来访问。例如,对于String,Java只能先找到String.class,String.class实际上就是instanceKlass 的一个对象,它们互相持有指向对方的指针。我们通过java对象,想访问instanceKlass ,需要先访问镜像对象。

  • 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:

    • _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴 露给 java 使用
    • _super 即父类
    • _fields 即成员变量
    • _methods 即方法
    • _constants 即常量池
    • _class_loader 即类加载器
    • _vtable 虚方法表
    • _itable 接口方法表
  • 如果这个类还有父类没有加载,先加载父类

  • 加载和链接可能是交替运行的

  • image-20210727110745965

  • 类被加载到方法区,方法区的实现就是元空间的实现,所以类的字节码都被加载到元空间中,就构成了instanceKlass数据结构,加载的同时就会在堆内存中生成镜像,就是Person.class对象,这个类对象就是在堆内存中,但是它持有了instanceKlass的地址。反之,instanceKlass也持有了Person.class的地址。如果以后用new关键字创建了一系列的person实例对象。那么它们是怎么联系起来的,每个实例对象都有自己的对象头,16字节,其中,8字节对应着对象的class地址,如果你想通过对象获取class信息,它就会访问对象的对象头。然后通过地址找到java_mirror,然后去元空间找到获取类的信息,如fields,methods等。
    instanceKlass保存在方法区。JDK 8以后,方法区位于元空间中,而元空间又位于本地内存中
    _java_mirror则是保存在堆内存中
    InstanceKlass和.class(JAVA镜像类)互相保存了对方的地址
    类的对象在对象头中保存了.class的地址。让对象可以通过其找到方法区中的instanceKlass,从而获取类的各种信息**

链接

验证
  • image-20210727111229340

  • 第一步 :验证class文件格式是否正确

    • 1、文件格式验证
    • 2、元数据验证
    • 3、字节码验证
    • 4、符号引用验证
准备
  • 为类的静态变量分配内存,并将其赋默认值

    • static 变量在JDK 7 之前存储于instanceKlass 末尾, 从JDK1.7开始,存储于_java_mirror末尾
    • static 变量分配空间和赋初始值是两个步骤,分配完空间之后会给变量赋默认值,分配空间在准备阶段完成,赋初始值是在初始化阶段完成
    • 如果static变量是final的基本类型,以及字符串常量,那么编译阶段值就确定了,那么赋值就是在准备阶段完成的。
    • 如果static变量是final的,但属于引用类型,那么赋初始值也会在初始化阶段完成
    • 但是无论如何在准备阶段都会赋默认值,(如0、0L、null、false等)。
  • 实例变量是在创建对象的时候完成赋值的,没有赋默认值一说

  • 对static修饰的静态变量进行内存分配、赋默认值(如0、0L、null、false等)。

  • image-20210727131140907

  • 静态变量跟类对象存在一起,它们都是存储在堆中的,早期在方法区

解析
  • 将常量池中的符号引用(仅仅是符号,并不知到这个类或者方法 在内存中的具体位置)解析为直接引用(解析为内存中的地址引用)

  • import java.io.IOException;
    
    /**
     * @Author: sunyang
     * @Date: 2021/7/26
     * @Description:
     */
    public class Test {
        public static void main(String[] args) throws ClassNotFoundException, IOException {
            ClassLoader classLoader = Test.class.getClassLoader();
            //  loadClass 加载类C, 只会加载,不会触发其他过程,不会导致类的解析和初始化,就不造成类D的加载
            Class<?> c = classLoader.loadClass("Test");
            System.in.read();
        }
    
    
    }
    
    class C {
        D d = new D();
    }
    
    class D {
    
    }
    
  • image-20210727133353855

  • image-20210727133609405

  • 类的加载都是懒加载,用到了才会加载

  • import java.io.IOException;
    
    /**
     * @Author: sunyang
     * @Date: 2021/7/26
     * @Description:
     */
    public class Test {
        public static void main(String[] args) throws ClassNotFoundException, IOException {
            new C(); // 会加载解析类C和D
            System.in.read();
        }
    
    
    }
    
    class C {
        D d = new D();
    }
    
    class D {
    
    }
    
  • image-20210727133948778

  • image-20210727134101381

初始化

《clinit》()v方法
  • 初始化即调用《clinit》()v
发生的时机
  • main方法所在的类,总会被首先初始化
  • 首次访问这个类的静态变量或静态方法
  • 子类初始化,如果父类还没初始化,会引发
  • 子类访问父类静态变量(如果是常量则不会,因为静态常量是在准备阶段就已经赋值了的),只会触发父类的初始化
  • Class.forName
  • new 会导致初始化
不会发生的情况
  • 访问类的static final 静态常量(基本类型,和字符串常量)不会触发初始化
  • 类对象.class 不会触发初始化(因为是在类加载时就创建了java_mirror对象在堆中)
  • 创建该类的数组不会触发初始化
  • 类加载器的loadClass方法
  • Class.forName的参数2为false时
实验
  • import java.io.IOException;
    
    /**
     * @Author: sunyang
     * @Date: 2021/7/26
     * @Description:
     */
    public class Test {
    
        static  {
            System.out.println("main init");
        }
    
        public static void main(String[] args) throws ClassNotFoundException, IOException {
           // 1. 静态常量不会触发初始化
            System.out.println(B.b);
            // 2.类对象.class 不会触发初始化
            System.out.println(B.class);
            // 3.创建该类的数组不会触发初始化,只是预留空间,并没有new 对象
            System.out.println(new B[0]);
            // 4. 不会初始化类B,但会加载B,A
            ClassLoader c1 = Thread.currentThread().getContextClassLoader();
            c1.loadClass("B");
            // 5. 不会初始化类B,但会加载B,A
            ClassLoader c2 = Thread.currentThread().getContextClassLoader();
            Class.forName("B", false, c2);
    
    
    
            //1. 首次访问这个类的静态变量或静态方法时
            System.out.println(A.a);
            // 2. 子类初始化,如果父类还没初始化,会引发
            System.out.println(B.c);
            // 3.子类访问父类静态变量,只触发父类初始化
            System.out.println(B.a);
            // 4. 会初始化类B,并先初始化类A
            Class.forName("B");
        }
    
    
    }
    
    class A {
        static int a = 0;
        static {
            System.out.println("a init");
        }
    }
    
    class B extends A {
        final static double b = 5.0;
        static boolean c = false;
        static {
            System.out.println("b init");
        }
    }
    
  • import javax.swing.plaf.PanelUI;
    import java.io.IOException;
    
    /**
     * @Author: sunyang
     * @Date: 2021/7/26
     * @Description:
     */
    public class Test {
        public static void main(String[] args) {
            System.out.println(A.a);
            System.out.println(A.b);
            System.out.println(A.c);
        }
    
    
    }
    
    class A {
        public static final int a = 10;
        public static final String b = "hello";
        public static final Integer c = 20;
        static {
            System.out.println("init E");
        }
    
    
  • D:\ideaworkspace\untitled\out\production\untitled>javap -v A.class
    Classfile /D:/ideaworkspace/untitled/out/production/untitled/A.class
      Last modified 2021-7-27; size 657 bytes
      MD5 checksum f3d468c45fc55c43249bc1cdc6771bb0
      Compiled from "Test.java"
    class A
      minor version: 0
      major version: 52
      flags: ACC_SUPER
    Constant pool:
       #1 = Methodref          #8.#28         // java/lang/Object."<init>":()V
       #2 = Methodref          #29.#30        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       #3 = Fieldref           #7.#31         // A.c:Ljava/lang/Integer;
       #4 = Fieldref           #32.#33        // java/lang/System.out:Ljava/io/PrintStream;
       #5 = String             #34            // init E
       #6 = Methodref          #35.#36        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #7 = Class              #37            // A
       #8 = Class              #38            // java/lang/Object
       #9 = Utf8               a
      #10 = Utf8               I
      #11 = Utf8               ConstantValue
      #12 = Integer            10
      #13 = Utf8               b
      #14 = Utf8               Ljava/lang/String;
      #15 = String             #39            // hello
      #16 = Utf8               c
      #17 = Utf8               Ljava/lang/Integer;
      #18 = Utf8               <init>
      #19 = Utf8               ()V
      #20 = Utf8               Code
      #21 = Utf8               LineNumberTable
      #22 = Utf8               LocalVariableTable
      #23 = Utf8               this
      #24 = Utf8               LA;
      #25 = Utf8               <clinit>
      #26 = Utf8               SourceFile
      #27 = Utf8               Test.java
      #28 = NameAndType        #18:#19        // "<init>":()V
      #29 = Class              #40            // java/lang/Integer
      #30 = NameAndType        #41:#42        // valueOf:(I)Ljava/lang/Integer;
      #31 = NameAndType        #16:#17        // c:Ljava/lang/Integer;
      #32 = Class              #43            // java/lang/System
      #33 = NameAndType        #44:#45        // out:Ljava/io/PrintStream;
      #34 = Utf8               init E
      #35 = Class              #46            // java/io/PrintStream
      #36 = NameAndType        #47:#48        // println:(Ljava/lang/String;)V
      #37 = Utf8               A
      #38 = Utf8               java/lang/Object
      #39 = Utf8               hello
      #40 = Utf8               java/lang/Integer
      #41 = Utf8               valueOf
      #42 = Utf8               (I)Ljava/lang/Integer;
      #43 = Utf8               java/lang/System
      #44 = Utf8               out
      #45 = Utf8               Ljava/io/PrintStream;
      #46 = Utf8               java/io/PrintStream
      #47 = Utf8               println
      #48 = Utf8               (Ljava/lang/String;)V
    {
      public static final int a;
        descriptor: I
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
        ConstantValue: int 10
    
      public static final java.lang.String b;
        descriptor: Ljava/lang/String;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
        ConstantValue: String hello
    
      public static final java.lang.Integer c;
        descriptor: Ljava/lang/Integer;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    
      A();
        descriptor: ()V
        flags:
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 47: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   LA;
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=2, locals=0, args_size=0
             0: bipush        20
             2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             5: putstatic     #3                  // Field c:Ljava/lang/Integer;
             8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
            11: ldc           #5                  // String init E
            13: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            16: return
          LineNumberTable:
            line 50: 0
            line 52: 8
            line 53: 16
    }
    SourceFile: "Test.java"
    
    
  • 典型应用 - 完成懒惰初始化单例模式

    • public final class Singleton {
      private Singleton() { }
      // 内部类中保存单例
      private static class LazyHolder {
      static final Singleton INSTANCE = new Singleton();
      }
      // 第一次调用 getInstance 方法,才会导致内部类加载和初始化其静态成员
      public static Singleton getInstance() {
      return LazyHolder.INSTANCE;
      }
      }
      
      
    • 以上的实现特点是:

      • 懒惰实例化
      • 初始化时的线程安全是有保障的

类加载器

  • image-20210727151750147

  • 启动类加载器,扩展类加载器,应用程序累加载器,自定义类加载器

启动类加载器

  • 用 Bootstrap 类加载器加载类:

    • package cn.itcast.jvm.t3.load;
      public class F {
      static {
      System.out.println("bootstrap F init");
      }
      }
      
    • package cn.itcast.jvm.t3.load;
      public class Load5_1 {
      public static void main(String[] args) throws ClassNotFoundException {
      Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.F");
      System.out.println(aClass.getClassLoader());
      }
      }
      
      
    • E:\git\jvm\out\production\jvm>java -Xbootclasspath/a:.
      cn.itcast.jvm.t3.load.Load5
      bootstrap F init
      null
      
  • -Xbootclasspath 表示设置 bootclasspath

  • 其中 /a:. 表示将当前目录追加至 bootclasspath 之后

  • 可以用这个办法替换核心类

    • java -Xbootclasspath:
    • java -Xbootclasspath/a:<追加路径>
    • java -Xbootclasspath/p:<追加路径>

扩展类加载器

  • public class G {
    static {
    System.out.println("classpath G init");
    }
    }
    
  • public class Load5_2 {
    public static void main(String[] args) throws ClassNotFoundException {
    Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.G");
    System.out.println(aClass.getClassLoader());
    }
    }
    
    
  • classpath G init
    sun.misc.Launcher$AppClassLoader@18b4aac2
    
    
  • 写一个同名类

    • package cn.itcast.jvm.t3.load;
      public class G {
      static {
      System.out.println("ext G init");
      }
      }
      
      
    • // 打个jar包 
      E:\git\jvm\out\production\jvm>jar -cvf my.jar cn/itcast/jvm/t3/load/G.class
      已添加清单
      正在添加: cn/itcast/jvm/t3/load/G.class(输入 = 481) (输出 = 322)(压缩了 33%)
      
    • 将 jar 包拷贝到 JAVA_HOME/jre/lib/ext 重新执行 Load5_2 输出

    • ext G init
      sun.misc.Launcher$ExtClassLoader@29453f44
      
      

双亲委派模式

源码分析
  • 所谓的双亲委派,就是指调用类加载器的loadClass方法时,查找类的规则

  • 这里的双亲,翻译为上级或父级更为合适,因为他们并没有继承关系,只是级别不同而已。

  • /*加载具有指定二进制名称的类。 此方法的默认实现按以下顺序搜索类:
    调用findLoadedClass(String)来检查类是否已经加载。
    在父类加载器上调用loadClass方法。 如果 parent 为null ,则使用虚拟机内置的类加载器。
    调用findClass(String)方法来查找类。
    如果使用上述步骤找到了该类,并且解析标志为真,则此方法将在生成的Class对象上调用resolveClass(Class)方法。
    鼓励ClassLoader 的子类覆盖findClass(String) ,而不是这个方法。
    除非被覆盖,此方法在整个类加载过程中同步getClassLoadingLock方法的结果。
    参数:
    name - 类的二进制名称
    解决 - 如果为真,则解决类
    返回:
    生成的Class对象
    抛出:
    ClassNotFoundException – 如果找不到类
    */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            //  首先检查该类是否已经被加载
            Class<?> c = findLoadedClass(name);
            // 如果没有被加载
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 没有被加载 且父加载器不为空
                    if (parent != null) {
                        // 调用父类的loadClass方法 递归
                        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
                }
    			//  如果都没找到 则按顺序调用findClass 去查找类
                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;
        }
    }
    
  • 执行流程为:

    • sun.misc.Launcher$AppClassLoader //1 处, 开始查看已加载的类,结果没有
    • sun.misc.Launcher A p p C l a s s L o a d e r / / 2 处 , 委 派 上 级 s u n . m i s c . L a u n c h e r AppClassLoader // 2 处,委派上级 sun.misc.Launcher AppClassLoader//2sun.misc.LauncherExtClassLoader.loadClass()
    • sun.misc.Launcher$ExtClassLoader // 1 处,查看已加载的类,结果没有
    • BootstrapClassLoader 是在 JAVA_HOME/jre/lib 下找 H 这个类,显然没有
    • sun.misc.Launcher E x t C l a s s L o a d e r / / 4 处 , 调 用 自 己 的 f i n d C l a s s 方 法 , 是 在 J A V A H O M E / j r e / l i b / e x t 下 找 H 这 个 类 , 显 然 没 有 , 回 到 s u n . m i s c . L a u n c h e r ExtClassLoader // 4 处,调用自己的 findClass 方法,是在 JAVA_HOME/jre/lib/ext 下找 H 这个类,显然没有,回到 sun.misc.Launcher ExtClassLoader//4findClassJAVAHOME/jre/lib/extHsun.misc.LauncherAppClassLoader 的 // 2 处
    • 继续执行到 sun.misc.Launcher$AppClassLoader // 4 处,调用它自己的 findClass 方法,在 classpath 下查找,找到了
线程上下文类加载器
  • 默认就是程序类加载器

  • 具体见文档SPI ,

自定义类加载器

  • 想加载非classpath 随意路径下的类文件
  • 都是通过接口来使用实现,希望解耦时,常用在框架设计
  • 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于tomcat容器
步骤
  • 继承classLoader父类

  • 要遵守双亲委派机制,重写findClass方法

    • 注意不是重写loadClass方法,否则不会走双亲委派机制
  • 读取类文件的字节码

  • 调用父类的defineClass方法来加载类

  • 使用者调用该类加载器的loadClass方法

  • import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    
    /**
     * @program: jvmstudy
     * @description: Demo
     * @author: SunYang
     * @create: 2021-07-27 19:31
     **/
    public class MyClassLoader extends ClassLoader{
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            String path = "e:\\" + name + ".class";
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            try {
                Files.copy(Paths.get(path), os);
                byte[] bytes = os.toByteArray();
                return defineClass(name, bytes, 0, bytes.length);
            } catch (IOException e) {
                e.printStackTrace();
                throw new ClassNotFoundException("类文件未找到", e);
            }
        }
    }
    

命名空间

  • 命名空间是由该类加载器以及其父类加载器所构成的,其中父类加载器加载的类对其子类可见,但是反过来子类加载的类对父类不可见,同一个命名空间中一定不会出现同一个类(全限定名一模一样的类)多个Class对象,换句话说就是在同一命名空间中只能存在一个Class对象,所以当你听别人说在内存中同一类的Class对象只有一个时其实指的是同一命名空间中,当然也不排除他压根就不知道这个概念。
    原文链接:https://blog.csdn.net/yuge1123/article/details/99945983

运行期优化

即时编译

分层编译
  • public class JIT1 {
        public static void main(String[] args) {
            for (int i = 0; i < 200; i++) {
                long start = System.nanoTime();
                for (int j = 0; j < 1000; j++) {
                    new Object();
                }
                long end = System.nanoTime();
                System.out.printf("%d\t%d\n",i,(end - start));
            }
        }
    }
    
  • 0 96426
    1 52907
    2 44800
    3 119040
    4 65280
    5 47360
    6 45226
    72 19200
    73 15360
    74 18347
    75 19627
    76 17067
    146 853
    147 854
    148 853
    149 853
    150 854
    
  • 原因是什么呢? JVM 将执行状态分成了 5 个层次:

    • 0 层,解释执行(Interpreter)
    • 1 层,使用 C1 即时编译器编译执行(不带 profiling)
    • 2 层,使用 C1 即时编译器编译执行(带基本的 profiling)
    • 3 层,使用 C1 即时编译器编译执行(带完全的 profiling)
    • 4 层,使用 C2 即时编译器编译执行
    • profiling 是指在运行过程中收集一些程序执行状态的数据,例如【方法的调用次数】,【循环的回边次数】等
  • 即时编译器(JIT)与解释器的区别

    • 解释器是将字节码解释为机器码,下次即使遇到相同的字节码,仍会执行重复的解释
    • JIT 是将一些字节码编译为机器码,并存入 Code Cache,下次遇到相同的代码,直接执行,无需再编译
    • 解释器是将字节码解释为针对所有平台都通用的机器码
    • JIT 会根据平台类型,生成平台特定的机器码
    • 对于占据大部分的不常用的代码,我们无需耗费时间将其编译成机器码,而是采取解释执行的方式运行;另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,以达到理想的运行速度。 执行效率上简单比较一下 Interpreter < C1 < C2,总的目标是发现热点代码(hotspot名称的由来),优化之刚才的一种优化手段称之为【逃逸分析】,发现新建的对象是否逃逸(是在c2阶段进行的优化)。可以使用 -XX:-DoEscapeAnalysis 关闭逃逸分析,再运行刚才的示例观察结果

方法内联

  • private static int square(final int i) {
    return i * i;
    }
    
  • System.out.println(square(9));
    
  • 如果发现 square 是热点方法,并且长度不太长时,会进行内联,所谓的内联就是把方法内代码拷贝、 粘贴到调用者的位置:

  • System.out.println(9 * 9);// 这个是进行了常量折叠优化
    
  • 实验

    • public class JIT2 {
          // -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining (解锁隐藏参数)打印
          inlining 信息
          // -XX:CompileCommand=dontinline,*JIT2.square 禁止某个方法 inlining
      // -XX:+PrintCompilation 打印编译信息
          public static void main(String[] args) {
              int x = 0;
              for (int i = 0; i < 500; i++) {
                  long start = System.nanoTime();
                  for (int j = 0; j < 1000; j++) {
                      x = square(9);
                  }
                  long end = System.nanoTime();
                  System.out.printf("%d\t%d\t%d\n",i,x,(end - start));
              }
          }
          private static int square(final int i) {
              return i * i;
          }
      }
      
      // 前几次是5位6位数,到最后会变成0;
      

字段优化

  •     public void test1() {
    			// elements.length 首次读取会缓存起来 -> int[] local
            for (int i = 0; i < elements.length; i++) { // 后续 999 次 求长度 <- local
                sum += elements[i]; // 1000 次取下标 i 的元素 <- local
            }
        }
    
  • @Benchmark
    public void test1() {
           // 运行期优化
        for (int i = 0; i < elements.length; i++) {
            doSum(elements[i]);
        }
    }
    @Benchmark
    public void test2() {
         // 这种方式和上一种一样,只不过是一种是我们自己手动优化,一种是JVM优化
     
        int[] local = this.elements;
        for (int i = 0; i < local.length; i++) {
            doSum(local[i]);
        }
    }
    @Benchmark
    public void test3() {
        // 编译期优化
        for (int element : elements) {
            doSum(element);
        }
    }
    
  • 可以节省 1999 次 Field 读取操作 但如果 doSum 方法没有内联,则不会进行上面的优化

反射优化

  • foo.invoke 前面 0 ~ 15 次调用使用的是 MethodAccessor 的 NativeMethodAccessorImpl 实现

  • 当调用到第 16 次(从0开始算)时,会采用运行时生成的类代替掉最初的实现,可以通过 debug 得到 类名为 sun.reflect.GeneratedMethodAccessor1

  • 注意 通过查看 ReflectionFactory 源码可知 sun.reflect.noInflation 可以用来禁用膨胀(直接生成 GeneratedMethodAccessor1,但首 次生成比较耗时,如果仅反射调用一次,不划算) sun.reflect.inflationThreshold 可以修改膨胀阈值

ark
public void test2() {
// 这种方式和上一种一样,只不过是一种是我们自己手动优化,一种是JVM优化

  int[] local = this.elements;
  for (int i = 0; i < local.length; i++) {
      doSum(local[i]);
  }

}
@Benchmark
public void test3() {
// 编译期优化
for (int element : elements) {
doSum(element);
}
}


- 可以节省 1999 次 Field 读取操作 但如果 doSum 方法没有内联,则不会进行上面的优化

### 反射优化

- foo.invoke 前面 0 ~ 15 次调用使用的是 MethodAccessor 的 NativeMethodAccessorImpl 实现
- 当调用到第 16 次(从0开始算)时,会采用运行时生成的类代替掉最初的实现,可以通过 debug 得到 类名为 sun.reflect.GeneratedMethodAccessor1

- 注意 通过查看 ReflectionFactory 源码可知 sun.reflect.noInflation 可以用来禁用膨胀(直接生成 GeneratedMethodAccessor1,但首 次生成比较耗时,如果仅反射调用一次,不划算) sun.reflect.inflationThreshold 可以修改膨胀阈值

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值