前言

之前看到一篇文章, java 反射调用 private 相关 

里面 大佬大致截图 截了一下 为什么运行时生成的 GeneratedMethodAccessor 可以访问所有的方法 

呵呵 说实话 这个问题 之前还没有考虑到 

但是 大佬的截图 说是话 还是有些抽象, 没有和具体的 运行时的来龙去脉 连接在一起 

呵呵呵 本文就来 理一下 这些东西 

本文 主要是以 反射调用 为例来走一下 大致的流程  

有一些基础知识 可以参考一下文章, 以及一下文章的参考文章(recurse) 

21 方法调用的流程(invokestatic为例)

14 HelloWorld的字节码的编译执行的调试

15 main方法的栈帧信息

07 运行时常量池索引的 rewrite

以下代码, 截图 基于 jdk9 

测试用例

package com.hx.test05;

import com.hx.test03.Test26MethodOrder;

import java.lang.reflect.Method;

/**
 * GenerateMethodAccessor
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2020-04-25 19:17
 */
public class Test16GenerateMethodAccessor {

  // Test16GenerateMethodAccessor
  public static void main(String[] args) throws Exception {

    Method method = Test26MethodOrder.class.getDeclaredMethod("func008");
    method.setAccessible(true);
    for(int i=0; i<=16; i++) {
      method.invoke(null);
    }

  }

}

Test26MethodOrder.func008 如下

// funcN
  private static int counter = 0;
  private static void func008() {
    System.out.println(counter++);
    if((counter == 16) || (counter == 17)) {
      Test25SynchronizeObject.doClone(new Test25SynchronizeObject());
    }
  }

基于 clion 的调试

首先在 Reflection::verify_class_access, Reflection::verify_field_access 里面打上断点 

在 jvm.cpp JVM_Clone 上面打上断点 

1. 然后会出现两三次 断点的 hit, 这里一句带过 

24 MagicAccessorImpl 可以访问所有的方法的调试_常量池

24 MagicAccessorImpl 可以访问所有的方法的调试_d3_02

这上面 两个 check_klass_accessbility 主要是来自于 NativeMethodAccessorImpl.invoke 里面 访问了这两个类, 然后 触发类加载, 以及校验等等, 这两次 check_klass_accessbility 于本文而言, 不是那么重要 

p ((Method*)0x010f1c2f78)->print()
{method}
 - this oop:          0x000000010f1c2f78
 - method holder:     'jdk/internal/reflect/NativeMethodAccessorImpl'
 - constants:         0x000000010f1c2a18 constant pool [97] {0x000000010f1c2a18} for 'jdk/internal/reflect/NativeMethodAccessorImpl' cache=0x000000010f1c3170
 - access:            0x1  public 
 - name:              'invoke'
 - signature:         '(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;'
 - max stack:         8
 - max locals:        4
 - size of params:    3
 - method size:       11
 - vtable index:      5
 - i2i entry:         0x000000011302b700
 - adapters:          AHE@0x00007fa181041690: 0xbbb00000 i2c: 0x00000001131d37e0 c2i: 0x00000001131d3922 c2iUV: 0x00000001131d38f5
 - compiled entry     0x00000001131d3922
 - code size:         104
 - code start:        0x000000010f1c2ec0
 - code end (excl):   0x000000010f1c2f28
 - checked ex length: 2
 - checked ex start:  0x000000010f1c2f72
 - linenumber start:  0x000000010f1c2f28
 - localvar length:   4
 - localvar start:    0x000000010f1c2f40

2. 另外还有一次 校验是来自于 创建了 GeneratedMethodAccessor1之后, 加载的时候 校验 是否能够访问 父类 

24 MagicAccessorImpl 可以访问所有的方法的调试_magic_03

3. 然后之后还有两次校验 是在创建 GeneratedMethodAccessor1 实例的时候, 需要 访问父类, 以及 其构造方法 

24 MagicAccessorImpl 可以访问所有的方法的调试_magic_04

24 MagicAccessorImpl 可以访问所有的方法的调试_常量池_05

4. 然后 之后断点来到了 JVM_Clone 里面  

这是 Test26MethodOrder.func008 里面的 counter == 16 条件进入的断点 


 


p obj()->print()
com.hx.test04.Test25SynchronizeObject 
{0x00000007bfb6f6b8} - klass: 'com/hx/test04/Test25SynchronizeObject'
 - ---- fields (total size 5 words):
 - 'f01' 'I' @12  0
 - 'f02' 'I' @16  0
 - 'f03' 'I' @20  0
 - 'f04' 'I' @24  0
 - 'f05' 'I' @28  0
 - private 'identStr' 'Ljava/lang/String;' @32  "xyz"{0x00000007bfb6f6e0} (f7f6dedc 0)

5. 然后之后 便是 GeneratedMethodAccessor1 需要访问 Test26MethodOrder.func008 的相关校验了 

24 MagicAccessorImpl 可以访问所有的方法的调试_magic_06

24 MagicAccessorImpl 可以访问所有的方法的调试_常量池_07

这的两个 verify_class_access, verify_field_access 一个是确保 能够访问 Test26MethodOrder, 一个是确保能够访问 Test26MethodOrder.func008 

这个对应的是 21 方法调用的流程(invokestatic为例) 里面的 加载相关类, 方法, 将常量池里面的 符号引用 替换为 直接引用, 以及一些调用方法相关的准备工作 

6. 最后一个断点便是 这是 Test26MethodOrder.func008 里面的 counter == 17 条件进入的断点 

24 MagicAccessorImpl 可以访问所有的方法的调试_常量池_08

p obj()->print()
com.hx.test04.Test25SynchronizeObject 
{0x00000007bfb6f7d8} - klass: 'com/hx/test04/Test25SynchronizeObject'
 - ---- fields (total size 5 words):
 - 'f01' 'I' @12  0
 - 'f02' 'I' @16  0
 - 'f03' 'I' @20  0
 - 'f04' 'I' @24  0
 - 'f05' 'I' @28  0
 - private 'identStr' 'Ljava/lang/String;' @32  "xyz"{0x00000007bfb6f6e0} (f7f6dedc 0)

纵览一下上面的断点

前面四个断点 是在 NativeMethodAccessorImpl 的相关业务处理里面 

24 MagicAccessorImpl 可以访问所有的方法的调试_magic_09

后面两个断点 是在 GeneratedMethodAccessor1 里面触发的 

24 MagicAccessorImpl 可以访问所有的方法的调试_magic_10

为什么 MagicAccessorImpl 可以访问所有的类, 方法

24 MagicAccessorImpl 可以访问所有的方法的调试_magic_11

24 MagicAccessorImpl 可以访问所有的方法的调试_magic_12

LinkResolver::resolve_invoke 里面的 index ?

如下图是 GeneratedMethodAccessor1 访问 Test26MethodOrder.func008 的时候 触发的解析 

24 MagicAccessorImpl 可以访问所有的方法的调试_d3_13

呵呵 妈的 invokestatic 后面不是跟的一个 MethodRef 的索引么?, 怎么这么大? 

获取索引的地方, 可以看到 本来 index 应该是 0 才对, 但是 实际传递过去的是 65536 

24 MagicAccessorImpl 可以访问所有的方法的调试_常量池_14

关于 CP_CACHE_INDEX_TAG 定义如下 

24 MagicAccessorImpl 可以访问所有的方法的调试_d3_15

程序里面调试的时候 CPCACHE_INDEX_TAG 显示的是 0, 但是 真实 使用的 确实 0x10000, 呵呵 不知道是不是 bug 哦 

那么传递了一个 这么大的索引, 后续怎么计算 操作数 对应的 在常量池的索引呢? 

24 MagicAccessorImpl 可以访问所有的方法的调试_magic_16

附上 GeneratedMethodAccessor1 的常量池信息 

{constant pool}
 - holder: 0x00000007c0098430
 - cache: 0x000000011200e5c8
 - resolved_references: 0x0000000000000000
 - reference_map: 0x0000000000000000
 -   1 : Utf8 : 'jdk/internal/reflect/GeneratedMethodAccessor1'
 -   2 : Unresolved Class : 'jdk/internal/reflect/GeneratedMethodAccessor1'
 -   3 : Utf8 : 'jdk/internal/reflect/MethodAccessorImpl'
 -   4 : Class : 'jdk/internal/reflect/MethodAccessorImpl' {0x00000007c0009b18}
 -   5 : Utf8 : 'com/hx/test03/Test26MethodOrder'
 -   6 : Unresolved Class : 'com/hx/test03/Test26MethodOrder'
 -   7 : Utf8 : 'func008'
 -   8 : Utf8 : '()V'
 -   9 : NameAndType : name_index=7 signature_index=8
 -  10 : Method : klass_index=6 name_and_type_index=9
 -  11 : Utf8 : 'invoke'
 -  12 : Utf8 : '(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;'
 -  13 : Utf8 : 'java/lang/Throwable'
 -  14 : Unresolved Class : 'java/lang/Throwable'
 -  15 : Utf8 : 'java/lang/ClassCastException'
 -  16 : Unresolved Class : 'java/lang/ClassCastException'
 -  17 : Utf8 : 'java/lang/NullPointerException'
 -  18 : Unresolved Class : 'java/lang/NullPointerException'
 -  19 : Utf8 : 'java/lang/IllegalArgumentException'
 -  20 : Unresolved Class : 'java/lang/IllegalArgumentException'
 -  21 : Utf8 : 'java/lang/reflect/InvocationTargetException'
 -  22 : Unresolved Class : 'java/lang/reflect/InvocationTargetException'
 -  23 : Utf8 : '<init>'
 -  24 : Utf8 : '()V'
 -  25 : NameAndType : name_index=23 signature_index=24
 -  26 : Method : klass_index=18 name_and_type_index=25
 -  27 : Method : klass_index=20 name_and_type_index=25
 -  28 : Utf8 : '(Ljava/lang/String;)V'
 -  29 : NameAndType : name_index=23 signature_index=28
 -  30 : Method : klass_index=20 name_and_type_index=29
 -  31 : Utf8 : '(Ljava/lang/Throwable;)V'
 -  32 : NameAndType : name_index=23 signature_index=31
 -  33 : Method : klass_index=22 name_and_type_index=32
 -  34 : Method : klass_index=4 name_and_type_index=25
 -  35 : Utf8 : 'java/lang/Object'
 -  36 : Unresolved Class : 'java/lang/Object'
 -  37 : Utf8 : 'toString'
 -  38 : Utf8 : '()Ljava/lang/String;'
 -  39 : NameAndType : name_index=37 signature_index=38
 -  40 : Method : klass_index=36 name_and_type_index=39
 -  41 : Utf8 : 'Code'
 -  42 : Utf8 : 'Exceptions'
 -  43 : Utf8 : 'valueOf'
 -  44 : Utf8 : 'java/lang/Boolean'
 -  45 : Unresolved Class : 'java/lang/Boolean'
 -  46 : Utf8 : '(Z)Ljava/lang/Boolean;'
 -  47 : NameAndType : name_index=43 signature_index=46
 -  48 : Method : klass_index=45 name_and_type_index=47
 -  49 : Utf8 : 'booleanValue'
 -  50 : Utf8 : '()Z'
 -  51 : NameAndType : name_index=49 signature_index=50
 -  52 : Method : klass_index=45 name_and_type_index=51
 -  53 : Utf8 : 'java/lang/Byte'
 -  54 : Unresolved Class : 'java/lang/Byte'
 -  55 : Utf8 : '(B)Ljava/lang/Byte;'
 -  56 : NameAndType : name_index=43 signature_index=55
 -  57 : Method : klass_index=54 name_and_type_index=56
 -  58 : Utf8 : 'byteValue'
 -  59 : Utf8 : '()B'
 -  60 : NameAndType : name_index=58 signature_index=59
 -  61 : Method : klass_index=54 name_and_type_index=60
 -  62 : Utf8 : 'java/lang/Character'
 -  63 : Unresolved Class : 'java/lang/Character'
 -  64 : Utf8 : '(C)Ljava/lang/Character;'
 -  65 : NameAndType : name_index=43 signature_index=64
 -  66 : Method : klass_index=63 name_and_type_index=65
 -  67 : Utf8 : 'charValue'
 -  68 : Utf8 : '()C'
 -  69 : NameAndType : name_index=67 signature_index=68
 -  70 : Method : klass_index=63 name_and_type_index=69
 -  71 : Utf8 : 'java/lang/Double'
 -  72 : Unresolved Class : 'java/lang/Double'
 -  73 : Utf8 : '(D)Ljava/lang/Double;'
 -  74 : NameAndType : name_index=43 signature_index=73
 -  75 : Method : klass_index=72 name_and_type_index=74
 -  76 : Utf8 : 'doubleValue'
 -  77 : Utf8 : '()D'
 -  78 : NameAndType : name_index=76 signature_index=77
 -  79 : Method : klass_index=72 name_and_type_index=78
 -  80 : Utf8 : 'java/lang/Float'
 -  81 : Unresolved Class : 'java/lang/Float'
 -  82 : Utf8 : '(F)Ljava/lang/Float;'
 -  83 : NameAndType : name_index=43 signature_index=82
 -  84 : Method : klass_index=81 name_and_type_index=83
 -  85 : Utf8 : 'floatValue'
 -  86 : Utf8 : '()F'
 -  87 : NameAndType : name_index=85 signature_index=86
 -  88 : Method : klass_index=81 name_and_type_index=87
 -  89 : Utf8 : 'java/lang/Integer'
 -  90 : Unresolved Class : 'java/lang/Integer'
 -  91 : Utf8 : '(I)Ljava/lang/Integer;'
 -  92 : NameAndType : name_index=43 signature_index=91
 -  93 : Method : klass_index=90 name_and_type_index=92
 -  94 : Utf8 : 'intValue'
 -  95 : Utf8 : '()I'
 -  96 : NameAndType : name_index=94 signature_index=95
 -  97 : Method : klass_index=90 name_and_type_index=96
 -  98 : Utf8 : 'java/lang/Long'
 -  99 : Unresolved Class : 'java/lang/Long'
 - 100 : Utf8 : '(J)Ljava/lang/Long;'
 - 101 : NameAndType : name_index=43 signature_index=100
 - 102 : Method : klass_index=99 name_and_type_index=101
 - 103 : Utf8 : 'longValue'
 - 104 : Utf8 : '()J'
 - 105 : NameAndType : name_index=103 signature_index=104
 - 106 : Method : klass_index=99 name_and_type_index=105
 - 107 : Utf8 : 'java/lang/Short'
 - 108 : Unresolved Class : 'java/lang/Short'
 - 109 : Utf8 : '(S)Ljava/lang/Short;'
 - 110 : NameAndType : name_index=43 signature_index=109
 - 111 : Method : klass_index=108 name_and_type_index=110
 - 112 : Utf8 : 'shortValue'
 - 113 : Utf8 : '()S'
 - 114 : NameAndType : name_index=112 signature_index=113
 - 115 : Method : klass_index=108 name_and_type_index=114

参考

java 反射调用 private 相关

21 方法调用的流程(invokestatic为例)

14 HelloWorld的字节码的编译执行的调试

15 main方法的栈帧信息

07 运行时常量池索引的 rewrite