- 浏览: 3052528 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
原帖在http://www.iteye.com/topic/477934?page=3#1185374,顺手转进来
对,说得一点也没错,所以MethodHandles的API就是这样的:
如果只是要做Java的method overload resolution,当然只要参数类型不要返回值类型就够了,但了解class文件及JVM内部数据组织方式的话就会知道,方法的签名(signature)在class文件里是以方法描述符(method descriptor)的形式存在,而该描述符上是有返回值类型的。MethodHandles的API这么设计就是为了快,能更直接的访问VM里的信息,以最快的方式找到目标方法。
文章中说了,作为重量级的method reflection的替代品还是不错的。比之策略模式来说,这个method handler的调用方式还是比较恶心。
java的反射现在还重吗?
试试反射调用10w次花多少时间?
这个method handler的使用方式上的确没多少创意,显得还是不够简洁,跟自己写反射工具类使用上区别不大
API上看起来区别不大那就对了,这样API就能很快上手。关键的差异都在VM内部,把重点无视掉就不太好了。自己写反射工具类是深入不到VM内部的。
至于“java的反射现在还重吗?试试反射调用10w次花多少时间?”,既然你问,我们就来看看。
这是MethodHandle版,如你所愿调用方法10w次:
这是普通的反射版,同样调用方法10w次:
代码几乎一模一样,没有作 弊成分。两种方式的测试分开来跑是为了避免前后代码相互干扰。
测试方式是先预热一段时间以确保被测试的test方法被JIT编译,然后再计时跑一次test测试,调用10w次空方法。
在JDK 7 Binary Snapshot build 70上,以Client VM连续测试多次,
MethodHandle版的其中5次测试结果:
普通反射版的其中5次测试结果:
看清楚了,两组结果的位数不同,时间单位是ns。
目前JDK 7里的JIT编译器还没有为MethodHandle做足优化,内联还没做彻底,况且我用来测试的并不是最新的build。即便如此,消除了反射固有的额外开销就已经有很明显的性能提升。
通过编译日志可以确认两组测试中test方法都确实被编译了:
MethodHandle版:
普通反射版:
阅读这个日志的方法是:
第一个数字:被JIT编译的方法的序号
%:说明触发了“栈上替换”(on-stack replacement,OSR),
这意味着被编译的方法是在自身执行过程中被编译的,编译好了之后通过OSR的方式从解释模式转到native模式;
当一个被OSR方式编译的方法再次被调用时,它就有机会再做一次正常的JIT编译;
!:说明被编译的方法有异常处理块;
n:说明方法是native method;
接下来就是被编译的方法名,
如果后面还有@符号,那就是OSR之后方法的入口在原字节码中的偏移量;
最后的括号是指被编译方法的字节码大小。
由于OSR会阻碍JIT编译器做某些优化,生成的代码效率较差,所以我们希望最后计时的测试是正常JIT编译的。上面的日志说明test方法在OSR方式编译后也做了正常编译,保证测试的稳定性。
OK,那两个版本的test方法被JIT编译后的native code是啥样的呢?下面就来看看
(AT&T语法的x86汇编,代码说明写在注释中。
只保留了正常执行时的代码路径,ret后面接着的异常处理代码省略了):
MethodHandle版:
MethodHandle版的test方法编译出来相当简洁,其中invoke跟正常的虚方法调用对应的代码一致,没有数组包装,没有原始类型装箱。
这段代码展现了Sun的HotSpot VM在调用函数时使用fastcall calling convention:头两个能被DWORD装下的非浮点参数放在ecx和edx传递,其余参数从右向左压栈(这里没有表现浮点参数的状况)。你可以会觉得“不对啊,上面的代码明明是从左向右处理参数的”,不是先处理了第一个显式参数,然后第二、第三个么?请看清楚,这一系列“压栈”操作并没有使用push指令,而是在不改变esp的前提下向栈顶存入数据。如果改为用push指令,就会先push 0x3再push 0x2,也就是从右向左压栈;这样3才会在2的“下面(地址更大的地方)”。HotSpot C1生成的代码在函数入口处理就申请好了该方法需要的所有栈空间,包括局部变量、临时变量与调用别的函数时压栈用的空间;“Java stack”与“native stack”是融合在一起的。
为了解释代码多废话了几句……OTL
(注意:register spill虽然被翻译为“寄存器溢出”,但跟算术溢出“arithmetic overflow”是完全不同的概念,请不要混淆了)
普通反射版:
这个就长了。我们只是想通过反射去调用doNothing()而已,但这里的代码帮我们创建了用于容纳可变长度参数的数组,并且将1、2、3这三个int类型的值装箱为Integer,存入数组,然后再调用Method.invoke;Method.invoke里经过一系列的反射操作,去到一个名为JVM_InvokeMethod的native方法,然后Reflection::invoke_method、Reflection::invoke,把自动装箱的原始类型参数拆箱,接着JavaCalls::call、JavaCalls::call_helper,终于到真正的方法调用点。呼……漫长。
不管MethodHandle.invoke与普通反射的Method.invoke内部的实现,光看test方法的字节码也可以帮助理解上面的包装/装箱状况:
MethodHandle版:
普通反射版:
写了那么长,结论是:如果要说“反射调用不重”,那要看跟什么东西比……
P.S. *有这么一个说法:if you want any framework to look dead slow, make it do nothing, and you'd have to pay all the overhead for no benefits. 这里其实就是如此 ^v^
===========================================================================
补充,新回复:
果然要问这个问题么……嘛,测一下也不是不行。
不过仍然需要强调的是,JDK 7里的MethodHandle的内部设计与API设计都还没定案,还在不断改进中。HotSpot目前对MethodHandle.invoke的内联支持也还不彻底,所以拿现在的MethodHandle跟直接调用来比较会有明显的差距。即便如此它已经比普通的反射调用要快很多。最终的目标是让MethodHandle.invoke跟接口方法调用的速度差不多。
那么废话少说,上代码:
直接调用静态方法:
直接调用接口方法:
前面的我给出的MethodHandle与普通反射的比较,用的例子是针对静态方法为目标的调用。实际上直接调用静态方法算是HotSpot里最容易优化的一种调用了,所以测试耗时很短:
相比之下,接口方法调用就慢一些,
MethodHandle.invoke最后就应该能达到接近这个水平。
为什么这两组测试比前面两组测试快那么多呢?因为我们要测试的“对象”——方法调用消失了。继续看代码,
静态方法调用版的test方法:
接口方法调用版的test方法:
这次就不写那么详细的注释了,相信参考之前的代码也可以理解个大概。
关键点就是:原本应该有call指令进行方法调用的地方,现在消失了。这就是方法内联的效果。因为被内联的是空方法,内联进来之后自然是什么也不留下了。
由于静态方法不参与继承/重写相关的多态,可以说是“编译时确定的目标”,所以静态方法是最容易内联的,不需要做额外的检查。
而虚方法/接口方法则实际调用的版本取决于receiver的类型,要内联的话就必须要做一定检查:
·如果只记录前一次调用遇到的receiver类型(或其它影响dispatch的信息),这种callsite cache就叫做monomorphic inline cache,简称MIC;
·如果记录之前多次调用遇到的receiver类型(或其它影响dispatch的信息),这种callsite cache就叫做polymorphic inline cache,简称PIC。
还有所谓megamorphic状态,一般是指receiver变化太多,不值得做inline caching,而总是采取较慢的传统方式搜索目标方法。
上面的接口方法调用测试中展现的就是MIC:先检查receiver类型是否为某个已知类型(Callable3Impl),如果是的话就直接执行内联版本的c.call();否则退回到搜索方法的逻辑,并视情况决定是否更新或取消MIC。
正是因为MethodHandle.invoke在目前的JDK 7中尚未彻底实现inline功能,所以其开销比接口方法调用还是大很多。不过有两个工程师已经在努力实现相关功能了,可以期待以后的性能改善。
star022 写道
定位到一个java方法,其实只需要类型(Class),方法名及参数即可。
对,说得一点也没错,所以MethodHandles的API就是这样的:
引用
findStatic( TestMethodHandle1.class, // 方法所属类型(Class) "hello", // 方法名 type // 由参数和返回值类型组成的“方法类型” );
如果只是要做Java的method overload resolution,当然只要参数类型不要返回值类型就够了,但了解class文件及JVM内部数据组织方式的话就会知道,方法的签名(signature)在class文件里是以方法描述符(method descriptor)的形式存在,而该描述符上是有返回值类型的。MethodHandles的API这么设计就是为了快,能更直接的访问VM里的信息,以最快的方式找到目标方法。
star022 写道
dennis_zane 写道
JeffreyZhao 写道
C#中的委托非常实用,但Java的这个做法有多少可用性啊
文章中说了,作为重量级的method reflection的替代品还是不错的。比之策略模式来说,这个method handler的调用方式还是比较恶心。
java的反射现在还重吗?
试试反射调用10w次花多少时间?
这个method handler的使用方式上的确没多少创意,显得还是不够简洁,跟自己写反射工具类使用上区别不大
API上看起来区别不大那就对了,这样API就能很快上手。关键的差异都在VM内部,把重点无视掉就不太好了。自己写反射工具类是深入不到VM内部的。
至于“java的反射现在还重吗?试试反射调用10w次花多少时间?”,既然你问,我们就来看看。
这是MethodHandle版,如你所愿调用方法10w次:
import java.dyn.*; public class SpeedTrap1 { private static void doNothing(int x, int y, int z) { } private static void test(MethodHandle method) { for (int i = 0; i < 100000; i++) { method.<void>invoke(1, 2, 3); } } public static void main(String[] args) { MethodHandle method = MethodHandles.lookup() .findStatic( SpeedTrap1.class, "doNothing", MethodType.make( void.class, int.class, int.class, int.class)); // warm up for (int i = 0; i < 10; i++) { test(method); } // time the test long start = System.nanoTime(); test(method); long end = System.nanoTime(); System.out.println("elapse time: " + (end - start)); } }
这是普通的反射版,同样调用方法10w次:
import java.lang.reflect.*; public class SpeedTrap2 { public static void doNothing(int x, int y, int z) { } private static void test(Method method) throws Throwable { for (int i = 0; i < 100000; i++) { method.invoke(null, 1, 2, 3); } } public static void main(String[] args) throws Throwable { Method method = SpeedTrap2.class .getMethod( "doNothing", int.class, int.class, int.class); // warm up for (int i = 0; i < 10; i++) { test(method); } // time the test long start = System.nanoTime(); test(method); long end = System.nanoTime(); System.out.println("elapse time: " + (end - start)); } }
代码几乎一模一样,没有作 弊成分。两种方式的测试分开来跑是为了避免前后代码相互干扰。
测试方式是先预热一段时间以确保被测试的test方法被JIT编译,然后再计时跑一次test测试,调用10w次空方法。
在JDK 7 Binary Snapshot build 70上,以Client VM连续测试多次,
MethodHandle版的其中5次测试结果:
引用
elapse time: 2220394
elapse time: 2220673
elapse time: 2226540
elapse time: 2175416
elapse time: 2196648
elapse time: 2220673
elapse time: 2226540
elapse time: 2175416
elapse time: 2196648
普通反射版的其中5次测试结果:
引用
elapse time: 22363177
elapse time: 22343343
elapse time: 22353399
elapse time: 22354797
elapse time: 22357311
elapse time: 22343343
elapse time: 22353399
elapse time: 22354797
elapse time: 22357311
看清楚了,两组结果的位数不同,时间单位是ns。
目前JDK 7里的JIT编译器还没有为MethodHandle做足优化,内联还没做彻底,况且我用来测试的并不是最新的build。即便如此,消除了反射固有的额外开销就已经有很明显的性能提升。
通过编译日志可以确认两组测试中test方法都确实被编译了:
MethodHandle版:
引用
1 java.lang.String::hashCode (60 bytes)
2 java.lang.String::charAt (33 bytes)
3 java.lang.String::indexOf (151 bytes)
1% SpeedTrap1::test @ 2 (22 bytes)
4 SpeedTrap1::test (22 bytes)
2 java.lang.String::charAt (33 bytes)
3 java.lang.String::indexOf (151 bytes)
1% SpeedTrap1::test @ 2 (22 bytes)
4 SpeedTrap1::test (22 bytes)
普通反射版:
引用
1 java.lang.String::hashCode (60 bytes)
2 java.lang.String::charAt (33 bytes)
3 java.lang.Object::<init> (1 bytes)
4 sun.reflect.ClassFileAssembler::emitByte (11 bytes)
5 sun.reflect.ByteVectorImpl::add (38 bytes)
6 java.lang.String::indexOf (151 bytes)
7 java.lang.Integer::valueOf (54 bytes)
8 java.lang.reflect.Modifier::isPublic (12 bytes)
9 sun.reflect.Reflection::quickCheckMemberAccess (10 bytes)
--- n sun.reflect.Reflection::getClassAccessFlags (static)
10 ! java.lang.reflect.Method::invoke (167 bytes)
11 sun.reflect.DelegatingMethodAccessorImpl::invoke (10 bytes)
12 ! sun.reflect.GeneratedMethodAccessor1::invoke (288 bytes)
1% SpeedTrap2::test @ 2 (46 bytes)
13 SpeedTrap2::test (46 bytes)
2 java.lang.String::charAt (33 bytes)
3 java.lang.Object::<init> (1 bytes)
4 sun.reflect.ClassFileAssembler::emitByte (11 bytes)
5 sun.reflect.ByteVectorImpl::add (38 bytes)
6 java.lang.String::indexOf (151 bytes)
7 java.lang.Integer::valueOf (54 bytes)
8 java.lang.reflect.Modifier::isPublic (12 bytes)
9 sun.reflect.Reflection::quickCheckMemberAccess (10 bytes)
--- n sun.reflect.Reflection::getClassAccessFlags (static)
10 ! java.lang.reflect.Method::invoke (167 bytes)
11 sun.reflect.DelegatingMethodAccessorImpl::invoke (10 bytes)
12 ! sun.reflect.GeneratedMethodAccessor1::invoke (288 bytes)
1% SpeedTrap2::test @ 2 (46 bytes)
13 SpeedTrap2::test (46 bytes)
阅读这个日志的方法是:
第一个数字:被JIT编译的方法的序号
%:说明触发了“栈上替换”(on-stack replacement,OSR),
这意味着被编译的方法是在自身执行过程中被编译的,编译好了之后通过OSR的方式从解释模式转到native模式;
当一个被OSR方式编译的方法再次被调用时,它就有机会再做一次正常的JIT编译;
!:说明被编译的方法有异常处理块;
n:说明方法是native method;
接下来就是被编译的方法名,
如果后面还有@符号,那就是OSR之后方法的入口在原字节码中的偏移量;
最后的括号是指被编译方法的字节码大小。
由于OSR会阻碍JIT编译器做某些优化,生成的代码效率较差,所以我们希望最后计时的测试是正常JIT编译的。上面的日志说明test方法在OSR方式编译后也做了正常编译,保证测试的稳定性。
OK,那两个版本的test方法被JIT编译后的native code是啥样的呢?下面就来看看
(AT&T语法的x86汇编,代码说明写在注释中。
只保留了正常执行时的代码路径,ret后面接着的异常处理代码省略了):
MethodHandle版:
;; 函数入口处理(prologue) 0x00be8df0: mov %eax,-0x4000(%esp) 0x00be8df7: push %ebp ; 0x00be8df8: mov %esp,%ebp ; 上条和这条指令用于建立帧指针 0x00be8dfa: sub $0x28,%esp ; “申请”了0x28字节栈空间,包括局部变量与求值栈 ;; 函数体开始 0x00be8dfd: mov %ecx,0x14(%esp) ; 保护%ecx寄存器,将method暂存到0x14(%esp)处 ;; 循环初始化 0x00be8e01: mov $0x0,%esi ; int i = 0 0x00be8e06: jmp 0x00be8e3e ; 跳转到位于0x00be8e3e的循环条件测试 0x00be8e0b: nop ; 填充一字节无用指令,保证跳转目标在4字节对齐的边界上 ;; 循环体开始 0x00be8e0c: mov %esi,0x10(%esp) ; %esi寄存器溢出(register spill),将i暂存到0x10(%esp)处 0x00be8e10: cmp (%ecx),%eax ; 隐式空指针检查(看method是否为空), ; 遇到空指针时会触发访问异常,跳转到0x00be8e50 0x00be8e12: mov $0x1,%edx ; invoke的第一个显式参数,整数1,存入%edx 0x00be8e17: movl $0x2,(%esp) ; invoke的第二个显式参数,整数2,“压入”栈顶 0x00be8e1e: movl $0x3,0x4(%esp) ; invoke的第三个显式参数,整数3,“压入”栈顶+4的位置 0x00be8e26: mov %ecx,%edi ; (这条指令废了……) 0x00be8e28: mov %edi,%ecx ; invoke的隐式参数(method),存入%ecx(原本就在%ecx) 0x00be8e2a: call 0x00b9af50 ; 调用MethodHandle.invoke方法 0x00be8e2f: mov 0x10(%esp),%esi ; 把变量i从0x10(%esp)恢复到%esi 0x00be8e33: inc %esi ; i++ 0x00be8e34: test %eax,0x990100 ; HotSpot的内部实现细节:{poll} 0x00be8e3a: mov 0x14(%esp),%ecx ; 恢复%ecx寄存器的值为method ;; 循环条件 0x00be8e3e: cmp $0x186a0,%esi ; i < 100000 0x00be8e44: jl 0x00be8e0c ; 如果满足循环条件,跳转回到循环开头(0x00be8e0c) ;; 函数出口处理(epilogue) 0x00be8e46: mov %ebp,%esp ; 0x00be8e48: pop %ebp ; 上条和这条指令恢复上一个栈帧 0x00be8e49: test %eax,0x990100 ; HotSpot的内部实现细节:{poll_return} 0x00be8e4f: ret ; 返回
MethodHandle版的test方法编译出来相当简洁,其中invoke跟正常的虚方法调用对应的代码一致,没有数组包装,没有原始类型装箱。
这段代码展现了Sun的HotSpot VM在调用函数时使用fastcall calling convention:头两个能被DWORD装下的非浮点参数放在ecx和edx传递,其余参数从右向左压栈(这里没有表现浮点参数的状况)。你可以会觉得“不对啊,上面的代码明明是从左向右处理参数的”,不是先处理了第一个显式参数,然后第二、第三个么?请看清楚,这一系列“压栈”操作并没有使用push指令,而是在不改变esp的前提下向栈顶存入数据。如果改为用push指令,就会先push 0x3再push 0x2,也就是从右向左压栈;这样3才会在2的“下面(地址更大的地方)”。HotSpot C1生成的代码在函数入口处理就申请好了该方法需要的所有栈空间,包括局部变量、临时变量与调用别的函数时压栈用的空间;“Java stack”与“native stack”是融合在一起的。
为了解释代码多废话了几句……OTL
(注意:register spill虽然被翻译为“寄存器溢出”,但跟算术溢出“arithmetic overflow”是完全不同的概念,请不要混淆了)
普通反射版:
;; 函数入口处理(prologue) 0x00bebab0: mov %eax,-0x4000(%esp) 0x00bebab7: push %ebp 0x00bebab8: mov %esp,%ebp 0x00bebaba: sub $0x28,%esp ; “申请”了0x28字节栈空间,包括局部变量与求值栈 ;; 函数体开始 0x00bebabd: mov %ecx,0x18(%esp) ;; 循环初始化 0x00bebac1: mov $0x0,%esi ; int i = 0 0x00bebac6: jmp 0x00bebcab ; 跳转到位于0x00bebcab的循环条件测试 0x00bebacb: nop ; 填充一字节无用指令,保证跳转目标在4字节对齐的边界上 ;; 到此为止都基本上跟前一个版本一样 ;; 循环体开始 ;; 下面很长一段指令就是创建新的可变长度参数数组,并将原始类型参数装箱存入数组中 0x00bebacc: mov %esi,0x1c(%esp) ; %esi寄存器溢出(register spill),将i暂存到0x1c(%esp)处 0x00bebad0: mov $0x3,%ebx ; 0x00bebad5: mov $0x140acdc0,%edx ; 将Object[]类型指针存入%edx 0x00bebada: mov %ebx,%edi ; 将整数3存入%edi 0x00bebadc: cmp $0xffffff,%ebx ; 0x00bebae2: ja 0x00bebcc1 ; anewarray的慢速路径(现有条件下不会跳转过去) 0x00bebae8: mov $0x13,%esi ; 0x00bebaed: lea (%esi,%ebx,4),%esi ; 将整数31存入%esi 0x00bebaf0: and $0xfffffff8,%esi 0x00bebaf3: mov %fs:0x0(,%eiz,1),%ecx 0x00bebafb: mov -0xc(%ecx),%ecx 0x00bebafe: mov 0x44(%ecx),%eax 0x00bebb01: lea (%eax,%esi,1),%esi 0x00bebb04: cmp 0x4c(%ecx),%esi 0x00bebb07: ja 0x00bebcc1 0x00bebb0d: mov %esi,0x44(%ecx) 0x00bebb10: sub %eax,%esi 0x00bebb12: movl $0x1,(%eax) 0x00bebb18: mov %edx,0x4(%eax) 0x00bebb1b: mov %ebx,0x8(%eax) 0x00bebb1e: sub $0xc,%esi 0x00bebb21: je 0x00bebb64 0x00bebb27: test $0x3,%esi 0x00bebb2d: je 0x00bebb44 0x00bebb33: push $0x83989dc ; {external_word} 0x00bebb38: call 0x00bebb3d 0x00bebb3d: pusha 0x00bebb3e: call 0x0801ba80 ; {runtime_call} 0x00bebb43: hlt 0x00bebb44: xor %ebx,%ebx 0x00bebb46: shr $0x3,%esi 0x00bebb49: jae 0x00bebb59 0x00bebb4f: mov %ebx,0xc(%eax,%esi,8) 0x00bebb53: je 0x00bebb64 0x00bebb59: mov %ebx,0x8(%eax,%esi,8) 0x00bebb5d: mov %ebx,0x4(%eax,%esi,8) 0x00bebb61: dec %esi 0x00bebb62: jne 0x00bebb59 ;*anewarray ; - SpeedTrap2::test@11 (line 8) 0x00bebb64: mov %eax,0x20(%esp) 0x00bebb68: mov $0x1,%ecx ; invoke的可变长度参数第一个,整数1 0x00bebb6d: call 0x00b9b3d0 ; 调用Integer.valueOf完成int的自动装箱 0x00bebb72: mov 0x20(%esp),%esi 0x00bebb76: lea 0xc(%esi),%ecx 0x00bebb79: cmp $0x0,%eax ; 检查装箱结果是否为空 0x00bebb7c: je 0x00bebbbd 0x00bebb82: mov 0x4(%esi),%edi ; 隐式空指针检查(看数组是否为空), ; 遇到空指针时会触发访问异常,跳转到0x00bebccb 0x00bebb85: mov 0x4(%eax),%ebx 0x00bebb88: mov 0x88(%edi),%edi 0x00bebb8e: cmp %edi,%ebx 0x00bebb90: je 0x00bebbbd 0x00bebb96: mov 0x10(%edi),%edx 0x00bebb99: cmp (%ebx,%edx,1),%edi 0x00bebb9c: je 0x00bebbbd 0x00bebba2: cmp $0x14,%edx 0x00bebba5: jne 0x00bebce1 0x00bebbab: push %ebx 0x00bebbac: push %edi 0x00bebbad: call 0x00ba9190 ; {runtime_call} 0x00bebbb2: pop %ebx 0x00bebbb3: pop %edi 0x00bebbb4: cmp $0x0,%edi 0x00bebbb7: je 0x00bebce1 0x00bebbbd: mov %eax,(%ecx) 0x00bebbbf: shr $0x9,%ecx 0x00bebbc2: movb $0x0,0x2aeff80(%ecx) ;*aastore ; - SpeedTrap2::test@20 (line 8) 0x00bebbc9: mov $0x2,%ecx ; invoke的可变长度参数第二个,整数2 0x00bebbce: call 0x00b9b3d0 ; 调用Integer.valueOf完成int的自动装箱 0x00bebbd3: mov 0x20(%esp),%esi 0x00bebbd7: lea 0x10(%esi),%ecx 0x00bebbda: cmp $0x0,%eax 0x00bebbdd: je 0x00bebc1e 0x00bebbe3: mov 0x4(%esi),%edi ; 隐式空指针检查(看数组是否为空), ; 遇到空指针时会触发访问异常,跳转到0x00bebcf7 0x00bebbe6: mov 0x4(%eax),%ebx 0x00bebbe9: mov 0x88(%edi),%edi 0x00bebbef: cmp %edi,%ebx 0x00bebbf1: je 0x00bebc1e 0x00bebbf7: mov 0x10(%edi),%edx 0x00bebbfa: cmp (%ebx,%edx,1),%edi 0x00bebbfd: je 0x00bebc1e 0x00bebc03: cmp $0x14,%edx 0x00bebc06: jne 0x00bebd0d 0x00bebc0c: push %ebx 0x00bebc0d: push %edi 0x00bebc0e: call 0x00ba9190 ; {runtime_call} 0x00bebc13: pop %ebx 0x00bebc14: pop %edi 0x00bebc15: cmp $0x0,%edi 0x00bebc18: je 0x00bebd0d 0x00bebc1e: mov %eax,(%ecx) 0x00bebc20: shr $0x9,%ecx 0x00bebc23: movb $0x0,0x2aeff80(%ecx) ;*aastore ; - SpeedTrap2::test@27 (line 8) 0x00bebc2a: mov $0x3,%ecx ; invoke的可变长度参数第三个,整数3 0x00bebc2f: call 0x00b9b3d0 ; 调用Integer.valueOf完成int的自动装箱 0x00bebc34: mov 0x20(%esp),%ecx 0x00bebc38: lea 0x14(%ecx),%edx 0x00bebc3b: cmp $0x0,%eax 0x00bebc3e: je 0x00bebc7f 0x00bebc44: mov 0x4(%ecx),%esi ; 隐式空指针检查(看数组是否为空), ; 遇到空指针时会触发访问异常,跳转到0x00bebd23 0x00bebc47: mov 0x4(%eax),%edi 0x00bebc4a: mov 0x88(%esi),%esi 0x00bebc50: cmp %esi,%edi 0x00bebc52: je 0x00bebc7f 0x00bebc58: mov 0x10(%esi),%ebx 0x00bebc5b: cmp (%edi,%ebx,1),%esi 0x00bebc5e: je 0x00bebc7f 0x00bebc64: cmp $0x14,%ebx 0x00bebc67: jne 0x00bebd39 0x00bebc6d: push %edi 0x00bebc6e: push %esi 0x00bebc6f: call 0x00ba9190 ; {runtime_call} 0x00bebc74: pop %edi 0x00bebc75: pop %esi 0x00bebc76: cmp $0x0,%esi 0x00bebc79: je 0x00bebd39 0x00bebc7f: mov %eax,(%edx) 0x00bebc81: shr $0x9,%edx 0x00bebc84: movb $0x0,0x2aeff80(%edx) ;*aastore ; - SpeedTrap2::test@34 (line 8) 0x00bebc8b: mov 0x18(%esp),%esi ; 将method存入%esi 0x00bebc8f: cmp (%esi),%eax ; 隐式空指针检查(看method是否为空), ; 遇到空指针时会触发访问异常,跳转到0x00bebd4f 0x00bebc91: mov $0x0,%edx ; invoke的第一个显式参数,null,存入%edx 0x00bebc96: mov %ecx,(%esp) ; invoke的第二个显式参数,可变长度参数数组,“压入”栈顶 0x00bebc99: mov %esi,%ecx ; invoke的隐式参数(method),存入%ecx 0x00bebc9b: call 0x00b9af50 ; 调用Method.invoke方法 0x00bebca0: mov 0x1c(%esp),%esi ; 把变量i从0x1c(%esp)恢复到%esi 0x00bebca4: inc %esi ; i++ 0x00bebca5: test %eax,0x990100 ; HotSpot的内部实现细节:{poll} ;; 循环条件 0x00bebcab: cmp $0x186a0,%esi ; i < 100000 0x00bebcb1: jl 0x00bebacc ; 如果满足循环条件,跳转回到循环开头(0x00bebacc) ;; 函数出口处理(epilogue) 0x00bebcb7: mov %ebp,%esp ; 0x00bebcb9: pop %ebp ; 上条和这条指令恢复上一个栈帧 0x00bebcba: test %eax,0x990100 ; HotSpot的内部实现细节:{poll_return} 0x00bebcc0: ret ; 返回
这个就长了。我们只是想通过反射去调用doNothing()而已,但这里的代码帮我们创建了用于容纳可变长度参数的数组,并且将1、2、3这三个int类型的值装箱为Integer,存入数组,然后再调用Method.invoke;Method.invoke里经过一系列的反射操作,去到一个名为JVM_InvokeMethod的native方法,然后Reflection::invoke_method、Reflection::invoke,把自动装箱的原始类型参数拆箱,接着JavaCalls::call、JavaCalls::call_helper,终于到真正的方法调用点。呼……漫长。
不管MethodHandle.invoke与普通反射的Method.invoke内部的实现,光看test方法的字节码也可以帮助理解上面的包装/装箱状况:
MethodHandle版:
引用
private static void test(java.dyn.MethodHandle);
Signature: (Ljava/dyn/MethodHandle;)V
flags: ACC_PRIVATE, ACC_STATIC LineNumberTable:
line 7: 0
line 8: 8
line 7: 15
line 10: 21
Code:
stack=4, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ldc #2 // int 100000
5: if_icmpge 21
8: aload_0
9: iconst_1
10: iconst_2
11: iconst_3
12: invokevirtual #3 // Method java/dyn/MethodHandle.invoke:(III)V
15: iinc 1, 1
18: goto 2
21: return
LineNumberTable:
line 7: 0
line 8: 8
line 7: 15
line 10: 21
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 18
Signature: (Ljava/dyn/MethodHandle;)V
flags: ACC_PRIVATE, ACC_STATIC LineNumberTable:
line 7: 0
line 8: 8
line 7: 15
line 10: 21
Code:
stack=4, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ldc #2 // int 100000
5: if_icmpge 21
8: aload_0
9: iconst_1
10: iconst_2
11: iconst_3
12: invokevirtual #3 // Method java/dyn/MethodHandle.invoke:(III)V
15: iinc 1, 1
18: goto 2
21: return
LineNumberTable:
line 7: 0
line 8: 8
line 7: 15
line 10: 21
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 18
普通反射版:
引用
private static void test(java.lang.reflect.Method) throws java.lang.Throwable;
Signature: (Ljava/lang/reflect/Method;)V
flags: ACC_PRIVATE, ACC_STATIC LineNumberTable:
line 7: 0
line 8: 8
line 7: 39
line 10: 45
Code:
stack=6, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ldc #2 // int 100000
5: if_icmpge 45
8: aload_0
9: aconst_null
10: iconst_3
11: anewarray #3 // class java/lang/Object
14: dup
15: iconst_0
16: iconst_1
17: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
20: aastore
21: dup
22: iconst_1
23: iconst_2
24: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
27: aastore
28: dup
29: iconst_2
30: iconst_3
31: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
34: aastore
35: invokevirtual #5 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
38: pop
39: iinc 1, 1
42: goto 2
45: return
LineNumberTable:
line 7: 0
line 8: 8
line 7: 39
line 10: 45
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 42
Exceptions:
throws java.lang.Throwable
Signature: (Ljava/lang/reflect/Method;)V
flags: ACC_PRIVATE, ACC_STATIC LineNumberTable:
line 7: 0
line 8: 8
line 7: 39
line 10: 45
Code:
stack=6, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ldc #2 // int 100000
5: if_icmpge 45
8: aload_0
9: aconst_null
10: iconst_3
11: anewarray #3 // class java/lang/Object
14: dup
15: iconst_0
16: iconst_1
17: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
20: aastore
21: dup
22: iconst_1
23: iconst_2
24: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
27: aastore
28: dup
29: iconst_2
30: iconst_3
31: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
34: aastore
35: invokevirtual #5 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
38: pop
39: iinc 1, 1
42: goto 2
45: return
LineNumberTable:
line 7: 0
line 8: 8
line 7: 39
line 10: 45
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 42
Exceptions:
throws java.lang.Throwable
写了那么长,结论是:如果要说“反射调用不重”,那要看跟什么东西比……
P.S. *有这么一个说法:if you want any framework to look dead slow, make it do nothing, and you'd have to pay all the overhead for no benefits. 这里其实就是如此 ^v^
===========================================================================
补充,新回复:
JohnnyJian 写道
反射和MethodHandle差10倍,那MethodHandle和直接的调用差多少?
果然要问这个问题么……嘛,测一下也不是不行。
不过仍然需要强调的是,JDK 7里的MethodHandle的内部设计与API设计都还没定案,还在不断改进中。HotSpot目前对MethodHandle.invoke的内联支持也还不彻底,所以拿现在的MethodHandle跟直接调用来比较会有明显的差距。即便如此它已经比普通的反射调用要快很多。最终的目标是让MethodHandle.invoke跟接口方法调用的速度差不多。
那么废话少说,上代码:
直接调用静态方法:
public class SpeedTrap { private static void doNothing(int x, int y, int z) { } private static void test() { for (int i = 0; i < 100000; i++) { doNothing(1, 2, 3); } } public static void main(String[] args) { // warm up for (int i = 0; i < 10; i++) { test(); } // time the test long start = System.nanoTime(); test(); long end = System.nanoTime(); System.out.println("elapse time: " + (end - start)); } }
直接调用接口方法:
interface Callable3 { void call(int x, int y, int z); } class Callable3Impl implements Callable3 { public void call(int x, int y, int z) { } } public class SpeedTrap3 { private static void test(Callable3 c) { for (int i = 0; i < 100000; i++) { c.call(1, 2, 3); } } public static void main(String[] args) { Callable3 c = new Callable3Impl(); // warm up for (int i = 0; i < 10; i++) { test(c); } // time the test long start = System.nanoTime(); test(c); long end = System.nanoTime(); System.out.println("elapse time: " + (end - start)); } }
前面的我给出的MethodHandle与普通反射的比较,用的例子是针对静态方法为目标的调用。实际上直接调用静态方法算是HotSpot里最容易优化的一种调用了,所以测试耗时很短:
引用
elapse time: 134933
elapse time: 134933
elapse time: 134934
elapse time: 134934
elapse time: 135213
elapse time: 134933
elapse time: 134934
elapse time: 134934
elapse time: 135213
相比之下,接口方法调用就慢一些,
引用
elapse time: 469054
elapse time: 468495
elapse time: 475759
elapse time: 468496
elapse time: 468775
elapse time: 468495
elapse time: 475759
elapse time: 468496
elapse time: 468775
MethodHandle.invoke最后就应该能达到接近这个水平。
为什么这两组测试比前面两组测试快那么多呢?因为我们要测试的“对象”——方法调用消失了。继续看代码,
静态方法调用版的test方法:
;; 函数入口处理(prologue) 0x00be6890: mov %eax,-0x4000(%esp) 0x00be6897: push %ebp 0x00be6898: mov %esp,%ebp 0x00be689a: sub $0x18,%esp ;*iconst_0 ; - SpeedTrap::test@0 (line 5) ;; 函数体开始 ;; 循环初始化 0x00be689d: mov $0x0,%esi 0x00be68a2: jmp 0x00be68af ;*istore_0 ; - SpeedTrap::test@1 (line 5) 0x00be68a7: nop ;; 循环体开始 ;; doNothing()方法的调用被内联进来而消失了 0x00be68a8: inc %esi ; OopMap{off=25} ;*goto ; - SpeedTrap::test@17 (line 5) 0x00be68a9: test %eax,0x990100 ;*goto ; - SpeedTrap::test@17 (line 5) ; {poll} ;; 循环条件 0x00be68af: cmp $0x186a0,%esi 0x00be68b5: jl 0x00be68a8 ;*if_icmpge ; - SpeedTrap::test@5 (line 5) ;; 函数出口处理(epilogue) 0x00be68b7: mov %ebp,%esp 0x00be68b9: pop %ebp 0x00be68ba: test %eax,0x990100 ; {poll_return} 0x00be68c0: ret
接口方法调用版的test方法:
;; 函数入口处理(prologue) 0x00be7230: mov %eax,-0x4000(%esp) 0x00be7237: push %ebp 0x00be7238: mov %esp,%ebp 0x00be723a: sub $0x18,%esp ;*iconst_0 ; - SpeedTrap3::test@0 (line 11) ;; 函数体开始 ;; 循环初始化 0x00be723d: mov $0x0,%esi 0x00be7242: jmp 0x00be726c ;*istore_1 ; - SpeedTrap3::test@1 (line 11) 0x00be7247: nop ;; 循环体开始 0x00be7248: cmp $0x0,%ecx ; 空指针检查(检查参数c是否为空) 0x00be724b: je 0x00be7261 ; 空指针时跳转到0x00be7261 0x00be7251: mov 0x4(%ecx),%ebx ; 这条与下条指令检查c的类型是否为Callable3Impl 0x00be7254: cmpl $0x14230e10,0x20(%ebx) ; {oop('Callable3Impl')} 0x00be725b: jne 0x00be727e ; c不是类型的实例则跳转到0x00be727e 0x00be7261: mov %ecx,%edi 0x00be7263: cmp (%ecx),%eax ;*invokeinterface call ; - SpeedTrap3::test@12 (line 12) ; implicit exception: dispatches to 0x00be7294 ;; 实际的c.call()的调用被内联进来而消失 0x00be7265: inc %esi ; OopMap{ecx=Oop off=54} ;*goto ; - SpeedTrap3::test@20 (line 11) 0x00be7266: test %eax,0x990100 ;*goto ; - SpeedTrap3::test@20 (line 11) ; {poll} ;; 循环条件 0x00be726c: cmp $0x186a0,%esi 0x00be7272: jl 0x00be7248 ;*if_icmpge ; - SpeedTrap3::test@5 (line 11) ;; 函数出口处理(epilogue) 0x00be7274: mov %ebp,%esp 0x00be7276: pop %ebp 0x00be7277: test %eax,0x990100 ; {poll_return} 0x00be727d: ret
这次就不写那么详细的注释了,相信参考之前的代码也可以理解个大概。
关键点就是:原本应该有call指令进行方法调用的地方,现在消失了。这就是方法内联的效果。因为被内联的是空方法,内联进来之后自然是什么也不留下了。
由于静态方法不参与继承/重写相关的多态,可以说是“编译时确定的目标”,所以静态方法是最容易内联的,不需要做额外的检查。
而虚方法/接口方法则实际调用的版本取决于receiver的类型,要内联的话就必须要做一定检查:
·如果只记录前一次调用遇到的receiver类型(或其它影响dispatch的信息),这种callsite cache就叫做monomorphic inline cache,简称MIC;
·如果记录之前多次调用遇到的receiver类型(或其它影响dispatch的信息),这种callsite cache就叫做polymorphic inline cache,简称PIC。
还有所谓megamorphic状态,一般是指receiver变化太多,不值得做inline caching,而总是采取较慢的传统方式搜索目标方法。
上面的接口方法调用测试中展现的就是MIC:先检查receiver类型是否为某个已知类型(Callable3Impl),如果是的话就直接执行内联版本的c.call();否则退回到搜索方法的逻辑,并视情况决定是否更新或取消MIC。
正是因为MethodHandle.invoke在目前的JDK 7中尚未彻底实现inline功能,所以其开销比接口方法调用还是大很多。不过有两个工程师已经在努力实现相关功能了,可以期待以后的性能改善。
发表评论
-
The Prehistory of Java, HotSpot and Train
2014-06-02 08:18 0http://cs.gmu.edu/cne/itcore/vi ... -
MSJVM and Sun 1.0.x/1.1.x
2014-05-20 18:50 0当年的survey paper: http://www.sym ... -
Sun JDK1.4.2_28有TieredCompilation
2014-05-12 08:48 0原来以前Sun的JDK 1.4.2 update 28就已经有 ... -
IBM JVM notes (2014 ver)
2014-05-11 07:16 0Sovereign JIT http://publib.bou ... -
class data sharing by Apple
2014-03-28 05:17 0class data sharing is implement ... -
Java 8与静态工具类
2014-03-19 08:43 16290以前要在Java里实现所谓“静态工具类”(static uti ... -
Java 8的default method与method resolution
2014-03-19 02:23 10465先看看下面这个代码例子, interface IFoo { ... -
HotSpot Server VM与Server Class Machine
2014-02-18 13:21 0HotSpot VM历来有Client VM与Server V ... -
Java 8的lambda表达式在OpenJDK8中的实现
2014-02-04 12:08 0三月份JDK8就要发布首发了,现在JDK8 release c ... -
GC stack map与deopt stack map的异同
2014-01-08 09:56 0两者之间不并存在包含关系。它们有交集,但也各自有特别的地方。 ... -
HotSpot Server Compiler与data-flow analysis
2014-01-07 17:41 0http://en.wikipedia.org/wiki/Da ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22408(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对C语义的for循环的基本代码生成模式
2013-10-19 23:12 21884之前有同学在做龙书(第二版)题目,做到8.4的练习,跟我对答案 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ... -
oop、klass、handle的关系
2013-07-30 17:34 0oopDesc及其子类的实例 oop : oopDesc* ... -
Nashorn各种笔记
2013-07-15 17:03 0http://bits.netbeans.org/netbea ... -
《深入理解Java虚拟机(第二版)》书评
2013-07-08 19:19 0值得推荐的中文Java虚拟机入门书 感谢作者赠与的样书,以下 ... -
豆列:从表到里学习JVM实现
2013-06-13 14:13 48396刚写了个学习JVM用的豆列跟大家分享。 豆列地址:http: ...
相关推荐
标题中的“答复: 通过代码简单介绍JDK 7的MethodHandle,并与.NET的委托对比(二)”表明本文将深入探讨Java中的MethodHandle概念,并将其与.NET平台上的委托进行对比。MethodHandle是JDK 7引入的一个强大特性,它...
《深入解析JDK7源代码》 JDK7(Java Development Kit 7)是Java编程语言的一个重要版本,它的源代码对于理解Java平台的工作原理、学习面向对象编程以及提升编程技巧具有极大的价值。在这个资源中,包含了 javax、...
Java 7.0 JDK 7 API CHM part02 本资源有五个压缩包,请全部下载后再解压 下载地址如下: Java 7.0 JDK 7 API CHM part05: http://download.csdn.net/source/3487478 Java 7.0 JDK 7 API CHM part04: ...
Java 7.0 JDK 7 API CHM part03 本资源有五个压缩包,请全部下载后再解压 下载地址如下: Java 7.0 JDK 7 API CHM part05: http://download.csdn.net/source/3487478 Java 7.0 JDK 7 API CHM part04: ...
Java 7.0 JDK 7 API CHM part05 本资源有五个压缩包,请全部下载后再解压 下载地址如下: Java 7.0 JDK 7 API CHM part05: http://download.csdn.net/source/3487478 Java 7.0 JDK 7 API CHM part04: ...
Java 7.0 JDK 7 API CHM part01 本资源有五个压缩包,请全部下载后再解压 下载地址如下: Java 7.0 JDK 7 API CHM part05: http://download.csdn.net/source/3487478 Java 7.0 JDK 7 API CHM part04: ...
Java 7.0 JDK 7 API CHM part04 本资源有五个压缩包,请全部下载后再解压 下载地址如下: Java 7.0 JDK 7 API CHM part05: http://download.csdn.net/source/3487478 Java 7.0 JDK 7 API CHM part04: ...
JDK是由Sun Microsystems(现已被Oracle公司收购)开发并维护的,自Java诞生以来,它一直是Java开发者的首选SDK。本文将深入探讨JDK的主要组件、功能及其不同版本的下载链接。 1. JDK的主要组件: - **javac**:...
Java Development Kit(JDK)是Java编程语言的核心组件,提供了编写、编译、调试和运行Java应用程序所需的...对比JDK 7与更高版本的源代码,可以帮助开发者追踪语言的发展,理解新特性的实现原理,从而提升编程能力。
标题提到的"jdk-7u191"指的是JDK的第7个主要版本的第191次更新。此版本在发布时是一个重要的里程碑,因为Java 7引入了许多新特性,如动态类型、字符串inswitch、try-with-resources语句等,极大地提高了开发效率。 ...
OpenJDK 8是基于Java SE 8规范的开源实现,它与Oracle JDK 8在功能上非常接近,但OpenJDK是免费且无版权限制的,而Oracle JDK则包含一些闭源的特性和服务。 在OpenJDK 7和8中,有一些重要的新特性和改进: 1. **...
**Java Development Kit (JDK) 1.7.0_79详解** JDK(Java Development Kit)是Oracle公司提供的用于开发和运行Java应用程序的重要工具集。本文将深入探讨JDK 1.7.0_79版本,该版本特别针对Windows x64操作系统进行...
**Java Development Kit (JDK) 7 for Windows 32位详解** JDK(Java Development Kit)是Oracle公司提供的用于开发和运行Java应用程序的核心工具集。JDK 7,也称为Java SE 7 (Java Standard Edition 7),是Java语言...
https://javaserverfaces.dev.java.net/ mojarra-1.2._11_b0_FCS JDK 1.5 绝对能用 good luck
虽然描述中没有明确指出Jdk7绿色版包含MySQL,但是提到了“使用”,这可能意味着该版本提供了一种简便的方法来与MySQL配合,可能是通过预配置的连接驱动或其他工具。 关于JDK7的重要知识点: 1. **多线程改进**:...
Java JDK 7是Java开发工具包的一个重要版本,它的全称是Java Development Kit,是用于构建和运行Java应用程序的关键组件。...通过这份PDF学习笔记,读者将能深入理解JDK 7的新特性,并能将其应用到实际项目中。
Java Development Kit (JDK) 1.7 64位官方正式版,即`jdk-7u76-linux-x64.tar.gz`,是Oracle公司为Linux 64位操作系统提供的Java开发工具集。这个版本的JDK对于开发和运行基于Java技术的应用程序至关重要,特别是当...
1. **多语言支持**:Java 7增加了对其他编程语言的支持,如Groovy、Scala等,通过JEP(JDK Enhancement Proposals)292,使得这些语言可以在Java虚拟机(JVM)上运行。 2. **try-with-resources**:这是一个语法糖...
赠送jar包:bcprov-jdk15on-1.68.jar; 赠送原API文档:bcprov-jdk15on-1.68-javadoc.jar; 赠送源代码:bcprov-jdk15on-1.68-sources.jar; 赠送Maven依赖信息文件:bcprov-jdk15on-1.68.pom; 包含翻译后的API文档...
JDK7,全称为Java SE 7 (Java Platform, Standard Edition 7),是Java平台的一个重大更新,于2011年发布,引入了许多新特性和改进,提升了开发者的工作效率和程序性能。"JDK7win安装版"表明这是专门为Windows操作...