深入字节码理解invokeSuper无限循环的原因
来一段简单的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无限循环的原因的更多相关文章
- 学以致用,通过字节码理解:Java的内部类与外部类之私有域访问
目录: 内部类的定义及用处 打开字节码理解内部类 一.内部类的定义及用处 内部类(inner class)是定义在另一个类中的类.使用内部类,我们可以: 访问该类定义所在的作用域中的数据,包括私有的数 ...
- Java精通并发-透过字节码理解synchronized关键字
在上一次https://www.cnblogs.com/webor2006/p/11428408.html中对于synchronized关键字的作用做了一个实例详解,下面再来看一下这个程序: 请问下, ...
- java字节码理解-入门
前记:作为一名JAVA Developer,每次打开Eclipse,查找一个没有源码的类时,都会看到一个这样的画面: 大意是:这个jar文件,没有附带源码.紧接着后面的就看不懂了,很好奇下面的一部分是 ...
- 理解 vue-router的beforeEach无限循环的问题
在理解beforeEach无限循环之前,我们先来看一下beforeEach相关的知识点,该篇文章的项目是基于 express+vue+mongodb+session实现注册登录 这篇文章项目基础之上进 ...
- JVM学习——字节码(学习过程)
JVM--字节码 为什么要学字节码 字节码文件,有什么用? JVM虚拟机的特点:一处编译,多处运行. 多处运行,靠的是.class 字节码文件. JVM本身,并不是跨平台的.Java之所以跨平台,是因 ...
- 深入理解java虚拟机(5)---字节码执行引擎
字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...
- 深入理解JVM—字节码执行引擎
原文地址:http://yhjhappy234.blog.163.com/blog/static/3163283220122204355694/ 前面我们不止一次的提到,Java是一种跨平台的语言,为 ...
- 《深入理解Java虚拟机》学习笔记之字节码执行引擎
Java虚拟机的执行引擎不管是解释执行还是编译执行,根据概念模型都具有统一的外观:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 栈帧(Stack Frame) ...
- 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性
我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...
随机推荐
- hdu 6073
题意: 给出一个二部图,U.V分别是二部图的两个点集,其中,U中每个点会有两条边连到V中两个不同的点. 完美匹配定义为:所有点都成功匹配. 思路:已知一定是完美匹配了呀(也一定存在),我们先把度数为一 ...
- 再读c++primer plus 006
使用类: 1.重载限制:(1)重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符 (2)使用运算符时不能违反运算符原来的语法规则,不能修改运算符的优先级 (3)不能创 ...
- B+树和LSM比较(转)
出处:https://blog.csdn.net/u013928917/article/details/75912045 B+树和LSM比较 在关系型数据库mysql中普遍使用B+树作为索引,在实际中 ...
- SVN被锁定的几种解决方法
用SVN经常出现被锁定而无法提交的问题,选择解锁又提示没有文件被锁定,很是头疼.这里整理了一下SVN被锁定的几种解决方法: 1.出现这个问题后使用“清理”即"Clean up"功能 ...
- 深入理解,函数声明、函数表达式、匿名函数、立即执行函数、window.onload的区别.
一.函数声明.函数表达式.匿名函数1.函数声明:function fnName () {…};使用function关键字声明一个函数,再指定一个函数名,叫函数声明. 2.函数表达式 var fnNam ...
- C++编译器详解(一)
C/C++编译器-cl.exe的命令选项 和在IDE中编译相比,命令行模式编译速度更快,并可以避免被IDE产生的一些附加信息所干扰,本文将介绍微软C/C++编译器命令行模式设定和用法. 1.设置环境变 ...
- 知识点:定义input type=file 样式的方法(转)
——“当我们想要用css美化表单的时候,有两个控件就会和前段人员作对,一个是是大名鼎鼎的select,另一个就是我现在要说说的 input type=file” 为什么要美化file控件?试想一下,别 ...
- 1105 Spiral Matrix
This time your job is to fill a sequence of N positive integers into a spiral matrix in non-increasi ...
- RecyclerView怎么能没有ItemClickListener?加一个!
RecyclerView可以用来代替ListView来展现大量的数据.Google在RecyclerView中提升了性能,和更多好用的API. 简单介绍RecyclerView 使用RecyclerV ...
- kepware http接口 php
读取某变量的值(HttpRequest <?php $request = new HttpRequest(); $request->setUrl('http://127.0.0.1:393 ...