zoukankan      html  css  js  c++  java
  • 深入字节码理解invokeSuper无限循环的原因

    来一段简单的cglib代码

     1 public class SampleClass {
     2     public void test(){
     3         System.out.println("hello world");
     4     }
     5 
     6     public static void main(String[] args) {
     7         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\classes");
     8         Enhancer enhancer = new Enhancer();
     9         enhancer.setSuperclass(SampleClass.class);
    10         enhancer.setCallback(new MethodInterceptor() {
    11             @Override
    12             public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    13                 System.out.println("before method run...");
    14                 Object result = proxy.invokeSuper(obj, args);
    15                 result = proxy.invoke(obj, args);
    16                 System.out.println("after method run...");
    17                 return result;
    18             }
    19         });
    20         SampleClass sample = (SampleClass) enhancer.create();
    21         sample.test();
    22     }
    23 }

     代码中使用 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\classes")设置环境变量,此设置可以打印生成的字节码文件。

    受影响的方法为:org.springframework.cglib.core.DebuggingClassWriter#toByteArray这里使用了spring的cglib包:spring的cglib包仅仅修改了cglib的类路径,实现完全相同

    运行过程中,cglib会生成3个class文件,第一个class文件的生成触发点在测试类第20行,对SampleClass进行增强,生成的关键代码如下:

     1 public class SampleClass$$EnhancerByCGLIB$$8ed28f extends SampleClass implements Factory {
     2      private static final Callback[] CGLIB$STATIC_CALLBACKS;
     3      private MethodInterceptor CGLIB$CALLBACK_0;
     4      private static final Method CGLIB$test$0$Method;
     5      private static final MethodProxy CGLIB$test$0$Proxy;
     6      private static final Object[] CGLIB$emptyArgs;
     7  
     8      static void CGLIB$STATICHOOK1() {
     9          CGLIB$emptyArgs = new Object[0];
    10          Class var0 = Class.forName("com.example.demo.proxy.SampleClass$$EnhancerByCGLIB$$8ed28f");
    11          Class var1;
    12          CGLIB$test$0$Method = ReflectUtils.findMethods(new String[]{"test", "()V"}, (var1 = Class.forName("com.example.demo.proxy.SampleClass")).getDeclaredMethods())[0];
    13          CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0");
    14      }
    15  
    16      final void CGLIB$test$0() {
    17          super.test();
    18      }
    19  
    20      public final void test() {
    21          MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    22          if(this.CGLIB$CALLBACK_0 == null) {
    23              CGLIB$BIND_CALLBACKS(this);
    24              var10000 = this.CGLIB$CALLBACK_0;
    25          }
    26  
    27          if(var10000 != null) {
    28              var10000.intercept(this, CGLIB$test$0$Method, CGLIB$emptyArgs, CGLIB$test$0$Proxy);
    29          } else {
    30              super.test();
    31          }
    32      }
    33  
    34      private static final void CGLIB$BIND_CALLBACKS(Object var0) {
    35          SampleClass$$EnhancerByCGLIB$$8ed28f var1 = (SampleClass$$EnhancerByCGLIB$$8ed28f)var0;
    36          if(!var1.CGLIB$BOUND) {
    37              var1.CGLIB$BOUND = true;
    38              Object var10000 = CGLIB$THREAD_CALLBACKS.get();
    39              if(var10000 == null) {
    40                  var10000 = CGLIB$STATIC_CALLBACKS;
    41                  if(CGLIB$STATIC_CALLBACKS == null) {
    42                      return;
    43                  }
    44              }
    45  
    46              var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
    47          }
    48  
    49      }
    50  
    51      public void setCallback(int var1, Callback var2) {
    52          switch(var1) {
    53          case 0:
    54              this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
    55          default:
    56          }
    57      }
    58  
    59      static {
    60          CGLIB$STATICHOOK1();
    61      }
    62  }

    测试代码第10行:enhancer.setCallback(**)将拦截器设置到增强代码中。

    执行test()方法,实际上调用的是增强代码的20行test()方法,增强的方法会调用注册的拦截器。方法参数为:

    Object obj 增强的SampleClass$$EnhancerByCGLIB$$8ed28f实例
    Method method 原生test方法
    Object[] args 此处没有参数,为空
    MethodProxy proxy 生成的methodProxy

    接下来我们看下methodProxy的生成:增强类静态块中调用了CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0");

    1     public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
    2         MethodProxy proxy = new MethodProxy();
    3         proxy.sig1 = new Signature(name1, desc);
    4         proxy.sig2 = new Signature(name2, desc);
    5         proxy.createInfo = new CreateInfo(c1, c2);
    6         return proxy;
    7     }

    只是记录了一些类信息。

    测试代码执行:proxy.invokeSuper(obj, args);

    1     public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    2         try {
    3             init();
    4             FastClassInfo fci = fastClassInfo;
    5             return fci.f2.invoke(fci.i2, obj, args);
    6         } catch (InvocationTargetException e) {
    7             throw e.getTargetException();
    8         }
    9     }

    展开init方法

     1 private void init()
     2     {
     3         if (fastClassInfo == null)
     4         {
     5             synchronized (initLock)
     6             {
     7                 if (fastClassInfo == null)
     8                 {
     9                     CreateInfo ci = createInfo;
    10 
    11                     FastClassInfo fci = new FastClassInfo();
    12                     fci.f1 = helper(ci, ci.c1);
    13                     fci.f2 = helper(ci, ci.c2);
    14                     fci.i1 = fci.f1.getIndex(sig1);
    15                     fci.i2 = fci.f2.getIndex(sig2);
    16                     fastClassInfo = fci;
    17                     createInfo = null;
    18                 }
    19             }
    20         }
    21     }
    
    
    12                     fci.f1 = helper(ci, ci.c1);
    13                     fci.f2 = helper(ci, ci.c2);

    这2行分别生成的2个fastClass类,通过类的signature快速定位方法
    12                     fci.f1 = SampleClass$$FastClassByCGLIB$$4f454a14 
     1 public class SampleClass$$FastClassByCGLIB$$4f454a14 extends FastClass {
     2 
     3     public int getIndex(Signature var1) {
     4         String var10000 = var1.toString();
     5         switch(var10000.hashCode()) {
     6         case -1422510685:
     7             if(var10000.equals("test()V")) {
     8                 return 1;
     9             }
    10             break;
    11         
    12         return -1;
    13     }
    14 
    15     public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
    16         SampleClass var10000 = (SampleClass)var2;
    17         int var10001 = var1;
    18 
    19         try {
    20             switch(var10001) {
    21             case 1:
    22                 var10000.test();
    23                 return null;
    24             }
    25         } catch (Throwable var4) {
    26             throw new InvocationTargetException(var4);
    27         }
    28 
    29         throw new IllegalArgumentException("Cannot find matching method/constructor");
    30     }
    31 }
    13                     fci.f2 = SampleClass$$EnhancerByCGLIB$$8ed28f$$FastClassByCGLIB$$520b645b
     1 public class SampleClass$$EnhancerByCGLIB$$8ed28f$$FastClassByCGLIB$$520b645b extends FastClass {
     2 
     3     public int getIndex(Signature var1) {
     4         String var10000 = var1.toString();
     5         switch(var10000.hashCode()) {
     6         case -1659809612:
     7             if(var10000.equals("CGLIB$test$0()V")) {
     8                 return 16;
     9             }
    10             break;
    11         case -1422510685:
    12             if(var10000.equals("test()V")) {
    13                 return 7;
    14             }
    15             break;
    16         return -1;
    17     }
    18 
    19     public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
    20         8ed28f var10000 = (8ed28f)var2;
    21         int var10001 = var1;
    22 
    23         try {
    24             switch(var10001) {
    25             case 7:
    26                 var10000.test();
    27                 return null;
    28             case 16:
    29                 var10000.CGLIB$test$0();
    30                 return null;
    31         } catch (Throwable var4) {
    32             throw new InvocationTargetException(var4);
    33         }
    34 
    35         throw new IllegalArgumentException("Cannot find matching method/constructor");
    36     }
    37 }
    
    

    invokeSuper调用fci.f2.invoke(fci.i2, obj, args),使用的是第三个生成类,方法签名是:CGLIB$test$0

    通过方法签名的hashcode映射后得到索引为16

     6         case -1659809612:
     7             if(var10000.equals("CGLIB$test$0()V")) {
     8                 return 16;
     9             }
    10             break;

    invoke调用的时候

    28             case 16:
    29                 var10000.CGLIB$test$0();
    30                 return null;
    走的这段逻辑。对比增强类可以得知CGLIB$test$0()是对原生方法的存根,执行的是最原始的逻辑。


    invoke调用
    
    
     1     public Object invoke(Object obj, Object[] args) throws Throwable {
     2         try {
     3             init();
     4             FastClassInfo fci = fastClassInfo;
     5             return fci.f1.invoke(fci.i1, obj, args);
     6         } catch (InvocationTargetException e) {
     7             throw e.getTargetException();
     8         } catch (IllegalArgumentException e) {
     9             if (fastClassInfo.i1 < 0)
    10                 throw new IllegalArgumentException("Protected method: " + sig1);
    11             throw e;
    12         }
    13     }
    
    
    fci.f1.invoke(fci.i1, obj, args)使用的是第二个生成类,方法签名是:test

    通过方法签名的hashcode映射后得到索引为1

     6         case -1422510685:
     7             if(var10000.equals("test()V")) {
     8                 return 1;
     9             }
    10             break;

    invoke调用的时候

    
    
    
    
    21             case 1:
    22                 var10000.test();
    23                 return null;
    24             }

    走的这段逻辑。对比增强类可以得知test()是增强方法,注册了拦截调用,所以才会出现循环调用,最终导致栈深操作过大范围,出现内存溢出。
    
    
     
     
  • 相关阅读:
    什么是内存泄漏,为什么会导致内存溢出?
    深入了解Redis(1)字符串底层实现
    深入了解Redis(3)对象
    201871010135 张玉晶《面向对象程序设计(java)》第十一周学习总结
    201871010135 张玉晶 《面向对象程序设计(java)》第二周学习总结
    201871010135 张玉晶《面向对象程序设计(java)》第十二周学习总结
    20187101035 张玉晶《面向对象程序设计(java)》第八周学习总结
    201871010135 张玉晶 《2019面向对象程序设计(java)课程学习进度条》
    201871010135张玉晶《面向对象程序设计(Java)》第四周学习总结
    201871010135 张玉晶《面向对象程序设计(java)》第七周学习总结
  • 原文地址:https://www.cnblogs.com/yangmengdx3/p/8824726.html
Copyright © 2011-2022 走看看