来一段简单的cglib代码

 public class SampleClass {
public void test(){
System.out.println("hello world");
} public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before method run...");
Object result = proxy.invokeSuper(obj, args);
result = proxy.invoke(obj, args);
System.out.println("after method run...");
return result;
}
});
SampleClass sample = (SampleClass) enhancer.create();
sample.test();
}
}

代码中使用 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进行增强,生成的关键代码如下:

 public class SampleClass$$EnhancerByCGLIB$$8ed28f extends SampleClass implements Factory {
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$test$0$Method;
private static final MethodProxy CGLIB$test$0$Proxy;
private static final Object[] CGLIB$emptyArgs; static void CGLIB$STATICHOOK1() {
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.example.demo.proxy.SampleClass$$EnhancerByCGLIB$$8ed28f");
Class var1;
CGLIB$test$0$Method = ReflectUtils.findMethods(new String[]{"test", "()V"}, (var1 = Class.forName("com.example.demo.proxy.SampleClass")).getDeclaredMethods())[0];
CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0");
} final void CGLIB$test$0() {
super.test();
} public final void test() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if(this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
} if(var10000 != null) {
var10000.intercept(this, CGLIB$test$0$Method, CGLIB$emptyArgs, CGLIB$test$0$Proxy);
} else {
super.test();
}
} private static final void CGLIB$BIND_CALLBACKS(Object var0) {
SampleClass$$EnhancerByCGLIB$$8ed28f var1 = (SampleClass$$EnhancerByCGLIB$$8ed28f)var0;
if(!var1.CGLIB$BOUND) {
var1.CGLIB$BOUND = true;
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
if(var10000 == null) {
var10000 = CGLIB$STATIC_CALLBACKS;
if(CGLIB$STATIC_CALLBACKS == null) {
return;
}
} var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
} } public void setCallback(int var1, Callback var2) {
switch(var1) {
case 0:
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
default:
}
} static {
CGLIB$STATICHOOK1();
}
}

测试代码第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");

     public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}

只是记录了一些类信息。

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

     public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}

展开init方法

 private void init()
{
if (fastClassInfo == null)
{
synchronized (initLock)
{
if (fastClassInfo == null)
{
CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}

12                     fci.f1 = helper(ci, ci.c1);
13 fci.f2 = helper(ci, ci.c2); 这2行分别生成的2个fastClass类,通过类的signature快速定位方法
12                     fci.f1 = SampleClass$$FastClassByCGLIB$$4f454a14 
 public class SampleClass$$FastClassByCGLIB$$4f454a14 extends FastClass {

     public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -1422510685:
if(var10000.equals("test()V")) {
return 1;
}
break; return -1;
} public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
SampleClass var10000 = (SampleClass)var2;
int var10001 = var1; try {
switch(var10001) {
case 1:
var10000.test();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
} throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}
13                     fci.f2 = SampleClass$$EnhancerByCGLIB$$8ed28f$$FastClassByCGLIB$$520b645b
 public class SampleClass$$EnhancerByCGLIB$$8ed28f$$FastClassByCGLIB$$520b645b extends FastClass {

     public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -1659809612:
if(var10000.equals("CGLIB$test$0()V")) {
return 16;
}
break;
case -1422510685:
if(var10000.equals("test()V")) {
return 7;
}
break;
return -1;
} public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
8ed28f var10000 = (8ed28f)var2;
int var10001 = var1; try {
switch(var10001) {
case 7:
var10000.test();
return null;
case 16:
var10000.CGLIB$test$0();
return null;
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
} throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}

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调用

     public Object invoke(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (IllegalArgumentException e) {
if (fastClassInfo.i1 < 0)
throw new IllegalArgumentException("Protected method: " + sig1);
throw e;
}
}

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()是增强方法,注册了拦截调用,所以才会出现循环调用,最终导致栈深操作过大范围,出现内存溢出。

 
 

深入字节码理解invokeSuper无限循环的原因的更多相关文章

  1. 学以致用,通过字节码理解:Java的内部类与外部类之私有域访问

    目录: 内部类的定义及用处 打开字节码理解内部类 一.内部类的定义及用处 内部类(inner class)是定义在另一个类中的类.使用内部类,我们可以: 访问该类定义所在的作用域中的数据,包括私有的数 ...

  2. Java精通并发-透过字节码理解synchronized关键字

    在上一次https://www.cnblogs.com/webor2006/p/11428408.html中对于synchronized关键字的作用做了一个实例详解,下面再来看一下这个程序: 请问下, ...

  3. java字节码理解-入门

    前记:作为一名JAVA Developer,每次打开Eclipse,查找一个没有源码的类时,都会看到一个这样的画面: 大意是:这个jar文件,没有附带源码.紧接着后面的就看不懂了,很好奇下面的一部分是 ...

  4. 理解 vue-router的beforeEach无限循环的问题

    在理解beforeEach无限循环之前,我们先来看一下beforeEach相关的知识点,该篇文章的项目是基于 express+vue+mongodb+session实现注册登录 这篇文章项目基础之上进 ...

  5. JVM学习——字节码(学习过程)

    JVM--字节码 为什么要学字节码 字节码文件,有什么用? JVM虚拟机的特点:一处编译,多处运行. 多处运行,靠的是.class 字节码文件. JVM本身,并不是跨平台的.Java之所以跨平台,是因 ...

  6. 深入理解java虚拟机(5)---字节码执行引擎

    字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...

  7. 深入理解JVM—字节码执行引擎

    原文地址:http://yhjhappy234.blog.163.com/blog/static/3163283220122204355694/ 前面我们不止一次的提到,Java是一种跨平台的语言,为 ...

  8. 《深入理解Java虚拟机》学习笔记之字节码执行引擎

    Java虚拟机的执行引擎不管是解释执行还是编译执行,根据概念模型都具有统一的外观:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 栈帧(Stack Frame) ...

  9. 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性

    我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...

随机推荐

  1. Desktop Central帮助您升级Windows 10,获取更新的五大增强功能

  2. 2018.10.30 NOIP模拟 字胡串(单调栈+容斥)

    传送门 对于每个点,用单调栈求出它左右第一个比他大的位置. 然后对每个点O(logai)O(log_{a_i})O(logai​​)求出第一个拥有跟它不同二进制位的位置. 然后容斥一下就行了. 代码

  3. 程序员面试50题—sizeof的用法(6)

    以下为Windows下的32 位C++程序,请计算sizeof 的值void Func ( char str[100] ){sizeof( str ) = ?}void *p = malloc( 10 ...

  4. android PendingIntent 使用通知传递多个参数,及不覆盖的问题

    Intent updateIntent = new Intent(GetNoticeService.this, DetailGonggaoActivity.class); updateIntent.p ...

  5. libtool 创建库的工具

    libtool 创建库的工具 1. 背景 在不同的系统中建立动态链接库的方法有很大的差别,这主要是因为每个系统对动态链接库的用法和实现并不相同,以及编译器对动态链接库支持的选项也不太一样. 对于开发人 ...

  6. Codeforces Round #265 (Div. 2) D. Restore Cube 立方体判断

    http://codeforces.com/contest/465/problem/D 给定8个点坐标,对于每个点来说,可以随意交换x,y,z坐标的数值.问说8个点是否可以组成立方体. 暴力枚举即可, ...

  7. js-图片轮播

    <!DOCTYPE HTML><html><head><meta http-equiv="Content-Type" content=&q ...

  8. python之基础1

    一.python介绍 介绍 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,Guido开始写能够解释Python语言语法的解释器.Python这个名字 ...

  9. AngularJS 单元测试 Karma jasmine

    当AngularJS项目越来越大时候,需要进行单元测试,可以先开发功能再进行测试,也可以先进行测试. 一.karma  是一个基于Node.js(先要安装)的JavaScript测试执行过程管理工具( ...

  10. 8.正则表达式和XPath

    1.使用正则表达式爬取内涵段子 import requests import re def loadPage(page): url = "http://www.neihan8.com/art ...