学习网站建设论文,淄博云天网站建设推广,哪些企业需要做网站建设,注册100万的公司一年缴纳多少税文章目录1、字节码结构1.1、基本结构1.2、实际观测2、内存表示3、方法调用指令4、invokedynamicEND结语Java真的是长盛不衰#xff0c;拥有顽强的生命力。其中#xff0c;字节码机制功不可没。字节码#xff0c;就像是 Linux 的 ELF。有了它#xff0c;JVM直接摇身一变拥有顽强的生命力。其中字节码机制功不可没。字节码就像是 Linux 的 ELF。有了它JVM直接摇身一变变成了类似操作系统的东西。
要学习字节码不能仅仅靠看枯燥的文档。本文会介绍几个有用的工具可以非常容易的上手来实际观测class文件这个小魔兽助你搲的更深一些。
1、字节码结构
1.1、基本结构
在开始之前我们先简要的介绍一下class文件的内容。这个结构可以使用jclasslib工具来查看。 上图是class文件基本内容。这部分内容枯燥乏味关于它的细节在Java的官方都能非常容易的找到。
如下图展示了一个简单方法的字节码描述我们可以看到真正的执行指令在整个文件结构中的具体位置。 1.2、实际观测
为了让大家避免避免枯燥的二进制对比分析直接定位到真正的数据结构这里介绍一个小工具使用这种方式学习字节码会节省很多时间。
https://wiki.openjdk.java.net/display/CodeTools/asmtools这个工具就是asmtools执行下面的命令将看到类的 JCED 语法结果。
java -jar asmtools-7.0.jar jdec LambdaDemo.class输出的结果类似于下面的结构它与我们上面介绍的字节码组成是一一对应的对照官网或者书籍学习速度飞快。
class LambdaDemo {0xCAFEBABE;0; // minor version52; // version[] { // Constant Pool; // first element is emptyMethod #8 #25; // #1InvokeDynamic 0s #30; // #2InterfaceMethod #31 #32; // #3Field #33 #34; // #4String #35; // #5Method #36 #37; // #6class #38; // #7class #39; // #8Utf8 init; // #9Utf8 ()V; // #10Utf8 Code; // #11了解了类的文件组织方式下面我们来看一下类文件在加载到内存中以后是一个什么表现形式。
2、内存表示
准备以下代码使用javac -g InvokeDemo.java进行编译。然后使用java命令执行。程序将阻塞在sleep函数上我们来看一下它的内存分布。
interface I {default void infMethod() { }void inf();
}abstract class Abs {abstract void abs();
}public class InvokeDemo extends Abs implements I {static void staticMethod() { }private void privateMethod() { }public void publicMethod() { }Overridepublic void inf() { }Overridevoid abs() { }public static void main(String[] args) throws Exception{InvokeDemo demo new InvokeDemo();InvokeDemo.staticMethod();demo.abs();((Abs) demo).abs();demo.inf();((I) demo).inf();demo.privateMethod();demo.publicMethod();demo.infMethod();((I) demo).infMethod();Thread.sleep(Integer.MAX_VALUE);}
}为了更加明显的看到这个过程下面介绍一下 jhsdb 这个工具这是在 Java9 之后 JDK 先加入的调试工具我们可以在命令行使用 jhsdb hsdb 来启动它。注意要加载相应的进程时必须确保是同一个版本的应用进程否则会产生报错。 attach启动后的Java进程后可以在 Class Browser 菜单查看加载的所有类信息。我们在搜索框输入 InvokeDemo找到要查看的类。 ***符号后面的就是具体的内存地址我们可以复制一个然后在*Inspector 视图查看具体的属性。可以大体认为这就是类在方法区的具体存储。 在Inspector视图中我们找到方法相关的属性 _methods可惜的是它无法点开也无法查看。 接下来可以使用命令行来检查这个数组里面的值。打开菜单中Console然后输入examine命令。可以看到这个数组里的内容对应的地址就是Class视图中的方法地址。
examine 0x000000010e650570/10我们可以在Inspect视图看到方法所对应的内存信息这确实是一个Method方法的表示。 相比较起来对象就简单的它只需要保存一个到达Class对象的指针即可。我们需要先从对象视图进入然后找到它一步步进入Inspect视图。 由以上的这些分析我们可以得出下面这张图。执行引擎想要运行某个对象的方法需要先在栈上找到这个对象的引用然后再通过的对象的指针找到相应的方法字节码。 3、方法调用指令
关于方法的调用Java一共提供了5个指令用来调用不同类型的函数。
invokestatic 用来调用静态方法的。invokevirtual 用于调用非私有实例方法比如public和protected。大多数方法调用属于这一种。invokeinterface 和上面这条指令类似不过是作用于接口类。invokespecial 用于调用私有实例方法、构造器以及super关键字等。invokedynamic 用于调用动态方法。
我们依然使用上面的代码片段看一下前四个指令的使用场景。代码中包含一个接口I一个抽象类Abs一个实现和继承了两者的类InvokeDemo。
参考Java的类加载机制在class文件被加载到方法区以后就完成了从符号引用到具体地址的转换过程。
我们可以看一下编译后的main方法字节码。尤其需要注意的是对于接口方法的调用。使用实例对象直接调用和强制转化成接口调用所调用的字节码指令分别是 invokevirtual 和invokeinterface它们是不同的。
public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack2, locals2, args_size10: new #2 // class InvokeDemo3: dup4: invokespecial #3 // Method init:()V7: astore_18: invokestatic #4 // Method staticMethod:()V11: aload_112: invokevirtual #5 // Method abs:()V15: aload_116: invokevirtual #6 // Method Abs.abs:()V19: aload_120: invokevirtual #7 // Method inf:()V23: aload_124: invokeinterface #8, 1 // InterfaceMethod I.inf:()V29: aload_130: invokespecial #9 // Method privateMethod:()V33: aload_134: invokevirtual #10 // Method publicMethod:()V37: aload_138: invokevirtual #11 // Method infMethod:()V41: aload_142: invokeinterface #12, 1 // InterfaceMethod I.infMethod:()V47: return另外还有一点和我们想象中的不同大多数普通方法调用使用的是 invokevirtual 指令它其实是和invokeinterface 一类的都属于虚方法调用。很多时候JVM需要根据调用者的动态类型来确定调用的目标方法这就是动态绑定的过程。
invokevirtual指令有多态查找的机制该指令的运行时解析过程步骤如下
找到操作数栈顶的第一个元素所指向的对象的实际类型记做c。如果在类型c中找到与常量中的描述符和简单名称都相符的方法则进行访问权限校验如果通过则返回这个方法的直接引用查找过程结束不通过则返回java.lang.IllegalAccessError。否则按照继承关系从下往上依次对c的各个父类进行第二步的搜索和验证过程。始终没找到合适的方法抛出java.lang.AbstractMethodError异常。这就是java语言中方法重写的本质。
相对比invokestatic指令加上invokespecial指令就属于静态绑定过程。
所以静态绑定指的是能够直接识别目标方法的情况而动态绑定指的是需要在运行过程中根据调用者的类型来确定目标方法的情况。
可以想象相对于静态绑定的方法调用来说动态绑定的调用就更加耗时一些。由于方法的调用非常的频繁JVM对动态调用的代码进行了比较多的优化。比如使用方法表来加快对具体方法的寻址以及使用更快的缓冲区来直接寻址 内联缓存。
4、invokedynamic
有时候在写一些python脚本或者js脚本的时候会特别羡慕这些动态语言。如果把查找目标方法的决定权从虚拟机转嫁给用户代码我们就会有更高的自由度。
我们单独把invokedynamic抽离出来介绍是因为它比较复杂。和反射类似它用于一些动态的调用场景但它和反射有着本质的不同效率也比反射要高的多。
这个指令通常在lambda语法中出现我们来看一下一小段代码。
public class LambdaDemo {public static void main(String[] args) {Runnable r () - System.out.println(Hello Lambda);r.run();}
}使用javap -p -v 命令可以在main方法中看到invokedynamic指令。
public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack1, locals2, args_size10: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;5: astore_16: aload_17: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V12: return另外我们在javap的输出中找到了一些奇怪的东西。
BootstrapMethods:0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;Method arguments:#28 ()V#29 invokestatic LambdaDemo.lambda$main$0:()V#28 ()VBootstrapMethods属性在Java1.7以后才有位于类文件的属性列表中这个属性用于保存 invokedynamic 指令引用的引导方法限定符。
和上面介绍的四个指令不同invokedynamic并没有确切的接收对象取而代之的是一个叫做 CallSite 的对象。
static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type);其实invokedynamic指令的底层是使用方法句柄MethodHandle来实现的。方法句柄是一个能够被执行的引用它可以指向静态方法和实例方法以及虚构的get和set方法从IDE中可以看到这些函数。 句柄类型MethodType就是我们对方法的具体描述配合方法名称能够定位到一类函数。访问方法句柄和调用原来的指令是基本一致的但它的调用异常包括一些权限检查是在运行时才能被发现的。
lambda语言实际上是通过方法句柄来完成的在调用链上自然也多了一些调用步骤那么在性能上是否就意味着lambda性能低呢对于大部分“非捕获”的lambda表达式来说JIT编译器的逃逸分析能够优化这部分差异性能和传统方式无异但对于“捕获型”的表达式来说就需要通过方法句柄不断的生成适配器性能自然就低了很多不过和便捷性相比一丁点性能损失是可接受的。
除了lambda表达式我们还没有其他的方式来产生invokedynamic指令。但是我们可以使用一些外部的字节码修改工具比如ASM来生成一些带有这个指令的字节码这通常能够完成一些非常酷的功能比如完成一门弱类型检查的JVM-Base语言。
END
本文从Java字节码的顶层结构介绍开始通过一个实际代码了解了类加载以后在JVM内存里的表现形式并了解了jhsdb对Java进程的观测方式。
我们了解到Java7之后的invokedynamic指令它实际上是通过方法句柄来实现的。和我们关系最大的就是Lambda语法了解了这些原理可以忽略那些对Lambda性能高低的争论要尽量写一些“非捕获”的Lambda表达式。
什么你问什么叫非捕获那就需要你自己搲了。 原创小姐姐味道 https://mp.weixin.qq.com/s/c2CAAUgjnYwI0_1w58Q-ww 结语
如果这篇文章对您有所帮助或者有所启发的话求一键三连点赞、评论、收藏➕关注您的支持是我坚持写作最大的动力。