前文地址

https://www.cnblogs.com/tera/p/13336627.html

本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的态度,于是对java动态代理的本质原理做了一些研究,于是便有了这个系列的文章

上一篇文章详细分析了class字节码结构中的field_info和method_info,以及对应的Proxy的源码。本文将会更详细的分析method_info中的方法执行体部分

因为方法的字节码涉及到了jvm的操作指令,因此我们先做一个基础性的了解

原文地址:https://dzone.com/articles/introduction-to-java-bytecode
jvm指令文档:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html
文中开始介绍的堆、栈、方法区等概念这里就不详细描述了,主要看它后面对一些简单方法的字节码的解析
首先我们定义一个简单的类

public class Test {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = a + b;
}
}

编译生成Test.class

javac Test.java

查看字节码结构

javap -v Test.class

我们关注其中的main方法部分

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: return
LineNumberTable:
line 3: 0
line 4: 2
line 5: 4
line 6: 8

其中的Code正是方法的执行体,下面按照顺序图解具体操作

iconst_1:将常量1压入操作栈

istore_1:弹出栈顶的操作数,存入栈的本地变量数组的索引1,也就是变量a

iconst_2:将常量2压入操作栈

istore_2:弹出栈顶的操作数,存入栈的本地变量数组的索引2,也就是变量b

iload_1:从本地变量索引1种读取值,并压入操作栈

iload_2:从本地变量索引2种读取值,并压入操作栈

iadd:弹出栈顶的2个操作数,相加后将结果压入操作栈

 istore_3:弹出栈顶的操作数,存入栈的本地变量数组的索引3,也就是变量c

return:从方法返回

如果我们在类中定义一个方法

public class Test {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = calc(a, b);
}
static int calc(int a, int b) {
return (int) Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
}
}

得到的字节码如下,这次我把部分Constant pool也展示在下面

Constant pool:
#1 = Methodref #8.#19 // java/lang/Object."<init>":()V
#2 = Methodref #7.#20 // Test.calc:(II)I
#3 = Double 2.0d
#5 = Methodref #21.#22 // java/lang/Math.pow:(DD)D
#6 = Methodref #21.#23 // java/lang/Math.sqrt:(D)D
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: invokestatic #2 // Method calc:(II)I
9: istore_3
10: return
LineNumberTable:
line 3: 0
line 4: 2
line 5: 4
line 6: 10 static int calc(int, int);
descriptor: (II)I
flags: ACC_STATIC
Code:
stack=6, locals=2, args_size=2
0: iload_0
1: i2d
2: ldc2_w #3 // double 2.0d
5: invokestatic #5 // Method java/lang/Math.pow:(DD)D
8: iload_1
9: i2d
10: ldc2_w #3 // double 2.0d
13: invokestatic #5 // Method java/lang/Math.pow:(DD)D
16: dadd
17: invokestatic #6 // Method java/lang/Math.sqrt:(D)D
20: d2i
21: ireturn
LineNumberTable:
line 8: 0

这里我们主要看一下一些新出现的操作指令
在main方法中,编号6
invokestatic #2:调用静态方法,方法在Constant Pool中索引为2,表示Test.calc方法(这里特别注意,调用的方法目标必须是常量池中的一个有效索引)
在cacl方法中
i2d:将int类型的转换成double类型的
ldc2_w:将long型或者double型(思考一下为何是这2种类型放在同一个操作指令中)从静态池中压入栈
dadd:将double相加
d2i:将double类型转换成int类型
ireturn:返回一个int

将上面的jvm指令结合java代码,就可以初步理解每一行java代码究竟是如何被jvm执行的了

接下去我们可以通过Proxy的代码结合实际来看看

方法还是generateClassFile()
在上一篇文章的第三部分字节与方法字节码的写入中,有提到

这里的第一行,正是写入构造器的字节码,这一部分因为涉及到jvm的执行指令,我们放到下篇文章再详细看,所以这里先跳过

this.methods.add(this.generateConstructor());

此时我们就可以详细看下generateConstructor方法究竟干了什么

private ProxyGenerator.MethodInfo generateConstructor() throws IOException {
ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
DataOutputStream var2 = new DataOutputStream(var1.code);
this.code_aload(0, var2);
this.code_aload(1, var2);
var2.writeByte(183);
var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
var2.writeByte(177);
var1.maxStack = 10;
var1.maxLocals = 2;
var1.declaredExceptions = new short[0];
return var1;
}

接下一行一行分析

初始化MethodInfo对象,3个参数分别是,方法名、方法描述、access_flag,1表示public(参见Modifier.java)

因为是构造函数,所以方法名为<init>

方法的描述表示,该方法获取一个java.lang.reflect.InvocationHandler类型的参数,返回值为V(表示void)

方法的access_flag为1,表示public

ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);

写入aload_0和aload_1操作指令

this.code_aload(0, var2);
this.code_aload(1, var2);

写入183号操作指令,查文档得:invokespecial

调用实例方法,特别用来处理父类的构造函数

var2.writeByte(183);

写入需要调用的方法名和方法的参数

注意,这里的方法是通过this.cp.getMethodRef方法得到的,也就是说,这里写入的最终数据,其实是一个符合该方法描述的常量池中的一个有效索引(这部分知识可以参看之前的3篇文章)

var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));

写入177号指令,查文档得:return

返回void

var2.writeByte(177);

和上一篇文章中提到的一样,最后还需要写入栈深和本地变量数量,以及方法会抛出的异常数量,因为构造函数不主动抛出异常,所以异常数量直接为0

var1.maxStack = 10;
var1.maxLocals = 2;
var1.declaredExceptions = new short[0];

到此,一个构造函数的结构就完成了

此时我们总结一下该构造函数的结构

aload_0;
aload_1;
invokespecial #x //这里x对应Constant pool中构造函数的编号
return;

验证一下,我们建立一个类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy; public class Test extends Proxy {
protected TestClass(InvocationHandler h) {
super(h);
}
}

查看其字节码

protected Test(java.lang.reflect.InvocationHandler);
descriptor: (Ljava/lang/reflect/InvocationHandler;)V
flags: ACC_PROTECTED
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokespecial #1 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return
LineNumberTable:
line 6: 0
line 7: 5

正和我们之前总结的一模一样

结合之前的一些jvm指令的基本描述,我们就可以对method_info的正题结构有了更深入的了解

本文中我们初步了解了方法执行体Code的结构,jvm指令的基本概念,那么在下一篇文章中,我们将会继续探究Proxy的最核心的部分,代理方法的实际实现

java动态代理——jvm指令集基本概念和方法字节码结构的进一步探究及proxy源码分析四的更多相关文章

  1. 深入浅出Java动态代理

    文章首发于[博客园-陈树义],点击跳转到原文深入浅出Java动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理 ...

  2. Java动态代理 深度详解

    代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 今天我将用非常 ...

  3. Java动态代理:一个面包店的动态代理帝国

    文章首发于[博客园-陈树义],点击跳转到原文大白话说Java动态代理:一个面包店的动态代理帝国 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中, ...

  4. Java 动态代理是基于什么原理

    动态代理 ①动态代理概念理解 动态代理是一种方便运行时动态构建代理.动态处理代理方法调用的机制,很多场景都利用类似机制做到的,比如用来包装RPC调用.面向切面的变成(AOP) 实现动态代理的方式很多, ...

  5. Java 动态代理机制详解

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...

  6. java动态代理原理

    我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...

  7. java 动态代理示例,带主要注释

    Java proxy是基于反射,仅仅支持基于接口的动态代理. java 动态代理是一切架构的基础,必须了解. 废话少说,先上代码获得感性认识. 示例代码有主要注释. 接口: public interf ...

  8. Java 动态代理

    被代理的接口特点: 1. 不能有重复的接口,以避免动态代理类代码生成时的编译错误. 2. 这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败. 3. 需被代理的所有非 pub ...

  9. java动态代理模式

    java动态代理机制详解 Spring的核心AOP的原理就是java的动态代理机制. 在java的动态代理机制中,有两个重要的类或接口: 1.InvocationHandler(Interface): ...

随机推荐

  1. 小师妹学JVM之:cache line对代码性能的影响

    目录 简介 一个奇怪的现象 两个问题的答案 CPU cache line inc 和 add 总结 简介 读万卷书不如行万里路,讲了这么多assembly和JVM的原理与优化,今天我们来点不一样的实战 ...

  2. 测试必备工具之抓包神器 Charles 如何抓取 https 数据包?

    之前发过一篇文章讲解了Charles抓包工具的基本使用(有需要的小伙伴可以去看上一篇文章),  讲的数据包主要是http协议,大家可以看到数据包并直接显示具体详细的内容: 但是如果抓到的是https的 ...

  3. AHP(使用于某项目设备重要度评估测试)

    用层次法和蒙特卡洛模型计算权重系数,然后建立判断矩阵进行随机一致性检验,最后求出重要度指数. string calculateStr = "1,2,3,2,1,|1,2,3,2,1,|1,2 ...

  4. day45 如何完全删除mysql服务

    卸载mysql之后,mysql的服务无法删除 解决方案 在我们在卸载mysql后会有一些东西没有删除干净,当我们把这些内容清除干净后,服务自然就消失了 步骤一: 如果是默认安装的话 在这三个文件内都有 ...

  5. Python函数02/函数的动态参数/函数的注释/名称空间/函数的嵌套/global以及nolocal的用法

    Python函数02/函数的动态参数/函数的注释/名称空间/函数的嵌套/global以及nolocal的用法 目录 Python函数02/函数的动态参数/函数的注释/名称空间/函数的嵌套/global ...

  6. 【Python】抽象工厂模式

    前言 接着上一篇的故事工厂模式继续,手机要出厂,显然光一个手机肯定是不行的,还需要包装盒.充电器等等东西.我们按照上一篇提到的工厂模式,去建立新的工厂是一点都没有问题的.但是思考一下这样子做会带来的问 ...

  7. 开会时CPU 飙升100%同事们都手忙脚乱记一次应急处理过程

    告警 正在开会,突然钉钉告警声响个不停,同时市场人员反馈客户在投诉系统登不进了,报504错误.查看钉钉上的告警信息,几台业务服务器节点全部报CPU超过告警阈值,达100%. 赶紧从会上下来,SSH登录 ...

  8. React js ReactDOM.render 语句后面不能加分号

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title&g ...

  9. 一篇文章,学会jmeter模拟文件上传、下载操作

    最近很多同学都在问jmeter上传,下载文件的脚本怎么做? 正巧这阵子忙完有时间,就来“折腾”一番,哈哈 现整理出来和大家分享 到底该怎么做? 一.准备工作: 上传接口一个(自行开发解决了) 下载接口 ...

  10. 【Python学习笔记三】一个简单的python爬虫

    这里写爬虫用的requests插件 1.一般那3.x版本的python安装后都带有相应的安装文件,目录在python安装目录的Scripts中,如下:   2.将scripts的目录配置到环境变量pa ...