精华帖 (1) :: 良好帖 (6) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-01-28
RednaxelaFX 写道 kingkan 写道 请教下前辈们:
1.手动System.gc()与JVM自动gc有什么根本上的区别么?在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。 2.用户能自己设置JVM选择何种GC算法来进行GC么? 1、根本的区别之一:System.gc()可能在自动GC原本不会进入GC的位置上进入GC。 正常情况下,Java代码要尝试在GC堆上分配空间的时候才会触发GC;换句话说,基本上是“new”的时候才会触发GC。但System.gc()、JVMTI的强制GC等动作都在正常情况之外提示系统要做一次GC。 kingkan 写道 在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。
这个情况即便在HotSpot上也不一定,要看被测的方法有没有被JIT编译过。解释执行的时候基本上没啥优化,所以局部变量的存活范围跟源码里看起来是一致的。但因为HotSpot的JIT编译带有优化,一个局部变量在一个方法里被使用过的赋值才会有效,而未被使用过的赋值很可能被消除掉。这样,在方法最后把局部变量设为null就是徒劳的,赋值动作本身都会被消除。事实上不需要额外的赋值为null的动作,JIT编译器也会尽可能的缩小变量的有效范围,所以完全没必要在方法末尾将局部变量置null。 在.NET的CLR上,由于方法总是要被编译了才可以执行(AOT或者JIT),而且编译也带有优化,源码里变量的引用状况跟实际运行时的引用状况的差异可能更明显些,像这个例子就比较极端。 话说回来,Runtime.totalMemory()和Runtime.freeMemory()都是很RP的方法,其实并不适合细粒度观察…不知道您是用什么方式来“通过内存使用数据查看,该对象的内存是还没释放的”呢? HotSpot默认会对方法调用次数的计数器做“衰减”,每进一次GC就会检查是否已到半衰周期,到了就会把所有方法的调用次数的计数器减半。如果写microbenchmark的话,在被测的方法里插入System.gc()很可能会带来干扰,使被测方法的调用次数始终达不到编译的条件,导致其不被JIT编译。要禁用计数器衰减的话,启动VM的时候要给参数-XX:-UseCounterDecay。要确认某个方法有没有被JIT编译请使用-XX:+PrintCompilation。 靠microbenchmark来观察某个对象有没有被GC回收多半是不准确的。可以具体情况具体分析。 另外值得注意的是,System.gc()不一定是触发所谓的“full GC”或者叫“major GC”。 在Sun JDK6与OpenJDK 6的HotSpot里,"GCCause是_java_lang_system_gc"的时候,如果VM启动参数DisableExplicitGC为false,则会触发一次full GC,如果该参数为true则完全不触发任何GC。要将这个参数设置为true,启动的时候写上-XX:+DisableExplicitGC就行。 HotSpot对System.gc()有特别处理,最主要的地方体现在一次System.gc()是否与普通GC一样会触发GC的统计/阈值数据的更新——HotSpot里的许多GC算法都带有自适应的功能,会根据先前收集的效率来决定接下来的GC中使用的参数,但System.gc()默认不更新这些统计数据,避免用户强行调用GC对这些自适应功能的干扰。除此之外,在HotSpot里,System.gc()所触发的full GC跟普通的full GC没啥大差别。 ------------------ 在Oracle JRockit里,System.gc()触发的是nursery GC(如果选择了分代GC的话;如果选择的不是分代式GC算法则谈不上nursery还是old)。与HotSpot相同,可以通过一个参数禁用System.gc():-XXnoSystemGC。也可以通过另一个参数来强制System.gc()做full GC:-XXfullSystemGC。 JRockit R28里,禁用System.gc()的推荐参数是-XX:AllowSystemGC=false,而设定System.gc()触发full GC的参数是-XX:FullSystemGC=true。 ------------------ 在IBM JDK的JVM里,System.gc()同样可以禁用——使用-Xdisableexplicitgc参数。另外也有一些可以调节System.gc()触发的GC内容的参数,如-Xcompactexplicitgc、-Xnocompactexplicitgc之类。 ============================================================ 2、对OpenJDK 6里的HotSpot VM,请看这个文件,grep出/Use.*GC,/就知道了: curl 'http://hg.openjdk.java.net/jdk6/jdk6/hotspot/raw-file/tip/src/share/vm/runtime/globals.hpp' | grep -A 2 -E 'Use.*GC,' product(bool, UseSerialGC, false, \ "Use the serial garbage collector") \ \ product(bool, UseG1GC, false, \ "Use the Garbage-First garbage collector") \ \ product(bool, UseParallelGC, false, \ "Use the Parallel Scavenge garbage collector") \ \ product(bool, UseParallelOldGC, false, \ "Use the Parallel Old garbage collector") \ \ -- product(bool, UseMaximumCompactionOnSystemGC, true, \ "In the Parallel Old garbage collector maximum compaction for " \ "a system GC") \ -- product(bool, UseConcMarkSweepGC, false, \ "Use Concurrent Mark-Sweep GC in the old generation") \ \ -- develop(bool, UseAsyncConcMarkSweepGC, true, \ "Use Asynchronous Concurrent Mark-Sweep GC in the old generation")\ \ -- product(bool, UseParNewGC, false, \ "Use parallel threads in the new generation.") \ \ -- product(bool, UseAdaptiveSizePolicyWithSystemGC, false, \ "Use statistics from System.GC for adaptive size policy") \ \ 这样grep出来的启动参数中,UseMaximumCompactionOnSystemGC和UseAdaptiveSizePolicyWithSystemGC不是选择GC算法类型的参数,另外几个都是。它们分别是 ·UseSerialGC ·UseG1GC ·UseParallelGC ·UseParallelOldGC ·UseAsyncConcMarkSweepGC(产品模式不可调) ·UseConcMarkSweepGC ·UseParNewGC 它们之间的关系请参考:Jon Masamitsu: Our Collectors Jon Masamitsu 写道
Sun(=> Oracle)的产品版JDK 6里的HotSpot同上。 ------------------ JRockit R28的话,GC算法的基本设定可以用下面几个参数: -Xgc:singlecon -Xgc:gencon -Xgc:singlepar -Xgc:genpar 不过更推荐并且也更简单的是设定优化的目标,例如这几个参数: -XgcPrio:throughput -XgcPrio:pausetime -XgcPrio:deterministic ------------------ IBM J9有诸如下面几种设定GC算法的VM参数: -Xgcpolicy:optthruput -Xgcpolicy:optavgpause -Xgcpolicy:gencon -Xgcpolicy:subpool 真心感谢RednaxelaFX大哥的解答,又学到了很多东西,对于JVM的迷雾又可以清晰点了。 对于JAVA内存的适合细粒度观察,有没有一些建议? |
|
返回顶楼 | |
发表时间:2011-01-28
kingkan 写道 RednaxelaFX 写道 kingkan 写道 请教下前辈们:
1.手动System.gc()与JVM自动gc有什么根本上的区别么?在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。 2.用户能自己设置JVM选择何种GC算法来进行GC么? 1、根本的区别之一:System.gc()可能在自动GC原本不会进入GC的位置上进入GC。 正常情况下,Java代码要尝试在GC堆上分配空间的时候才会触发GC;换句话说,基本上是“new”的时候才会触发GC。但System.gc()、JVMTI的强制GC等动作都在正常情况之外提示系统要做一次GC。 kingkan 写道 在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。
这个情况即便在HotSpot上也不一定,要看被测的方法有没有被JIT编译过。解释执行的时候基本上没啥优化,所以局部变量的存活范围跟源码里看起来是一致的。但因为HotSpot的JIT编译带有优化,一个局部变量在一个方法里被使用过的赋值才会有效,而未被使用过的赋值很可能被消除掉。这样,在方法最后把局部变量设为null就是徒劳的,赋值动作本身都会被消除。事实上不需要额外的赋值为null的动作,JIT编译器也会尽可能的缩小变量的有效范围,所以完全没必要在方法末尾将局部变量置null。 在.NET的CLR上,由于方法总是要被编译了才可以执行(AOT或者JIT),而且编译也带有优化,源码里变量的引用状况跟实际运行时的引用状况的差异可能更明显些,像这个例子就比较极端。 话说回来,Runtime.totalMemory()和Runtime.freeMemory()都是很RP的方法,其实并不适合细粒度观察…不知道您是用什么方式来“通过内存使用数据查看,该对象的内存是还没释放的”呢? HotSpot默认会对方法调用次数的计数器做“衰减”,每进一次GC就会检查是否已到半衰周期,到了就会把所有方法的调用次数的计数器减半。如果写microbenchmark的话,在被测的方法里插入System.gc()很可能会带来干扰,使被测方法的调用次数始终达不到编译的条件,导致其不被JIT编译。要禁用计数器衰减的话,启动VM的时候要给参数-XX:-UseCounterDecay。要确认某个方法有没有被JIT编译请使用-XX:+PrintCompilation。 靠microbenchmark来观察某个对象有没有被GC回收多半是不准确的。可以具体情况具体分析。 另外值得注意的是,System.gc()不一定是触发所谓的“full GC”或者叫“major GC”。 在Sun JDK6与OpenJDK 6的HotSpot里,"GCCause是_java_lang_system_gc"的时候,如果VM启动参数DisableExplicitGC为false,则会触发一次full GC,如果该参数为true则完全不触发任何GC。要将这个参数设置为true,启动的时候写上-XX:+DisableExplicitGC就行。 HotSpot对System.gc()有特别处理,最主要的地方体现在一次System.gc()是否与普通GC一样会触发GC的统计/阈值数据的更新——HotSpot里的许多GC算法都带有自适应的功能,会根据先前收集的效率来决定接下来的GC中使用的参数,但System.gc()默认不更新这些统计数据,避免用户强行调用GC对这些自适应功能的干扰。除此之外,在HotSpot里,System.gc()所触发的full GC跟普通的full GC没啥大差别。 ------------------ 在Oracle JRockit里,System.gc()触发的是nursery GC(如果选择了分代GC的话;如果选择的不是分代式GC算法则谈不上nursery还是old)。与HotSpot相同,可以通过一个参数禁用System.gc():-XXnoSystemGC。也可以通过另一个参数来强制System.gc()做full GC:-XXfullSystemGC。 JRockit R28里,禁用System.gc()的推荐参数是-XX:AllowSystemGC=false,而设定System.gc()触发full GC的参数是-XX:FullSystemGC=true。 ------------------ 在IBM JDK的JVM里,System.gc()同样可以禁用——使用-Xdisableexplicitgc参数。另外也有一些可以调节System.gc()触发的GC内容的参数,如-Xcompactexplicitgc、-Xnocompactexplicitgc之类。 ============================================================ 2、对OpenJDK 6里的HotSpot VM,请看这个文件,grep出/Use.*GC,/就知道了: curl 'http://hg.openjdk.java.net/jdk6/jdk6/hotspot/raw-file/tip/src/share/vm/runtime/globals.hpp' | grep -A 2 -E 'Use.*GC,' product(bool, UseSerialGC, false, \ "Use the serial garbage collector") \ \ product(bool, UseG1GC, false, \ "Use the Garbage-First garbage collector") \ \ product(bool, UseParallelGC, false, \ "Use the Parallel Scavenge garbage collector") \ \ product(bool, UseParallelOldGC, false, \ "Use the Parallel Old garbage collector") \ \ -- product(bool, UseMaximumCompactionOnSystemGC, true, \ "In the Parallel Old garbage collector maximum compaction for " \ "a system GC") \ -- product(bool, UseConcMarkSweepGC, false, \ "Use Concurrent Mark-Sweep GC in the old generation") \ \ -- develop(bool, UseAsyncConcMarkSweepGC, true, \ "Use Asynchronous Concurrent Mark-Sweep GC in the old generation")\ \ -- product(bool, UseParNewGC, false, \ "Use parallel threads in the new generation.") \ \ -- product(bool, UseAdaptiveSizePolicyWithSystemGC, false, \ "Use statistics from System.GC for adaptive size policy") \ \ 这样grep出来的启动参数中,UseMaximumCompactionOnSystemGC和UseAdaptiveSizePolicyWithSystemGC不是选择GC算法类型的参数,另外几个都是。它们分别是 ·UseSerialGC ·UseG1GC ·UseParallelGC ·UseParallelOldGC ·UseAsyncConcMarkSweepGC(产品模式不可调) ·UseConcMarkSweepGC ·UseParNewGC 它们之间的关系请参考:Jon Masamitsu: Our Collectors Jon Masamitsu 写道
Sun(=> Oracle)的产品版JDK 6里的HotSpot同上。 ------------------ JRockit R28的话,GC算法的基本设定可以用下面几个参数: -Xgc:singlecon -Xgc:gencon -Xgc:singlepar -Xgc:genpar 不过更推荐并且也更简单的是设定优化的目标,例如这几个参数: -XgcPrio:throughput -XgcPrio:pausetime -XgcPrio:deterministic ------------------ IBM J9有诸如下面几种设定GC算法的VM参数: -Xgcpolicy:optthruput -Xgcpolicy:optavgpause -Xgcpolicy:gencon -Xgcpolicy:subpool 真心感谢RednaxelaFX大哥的解答,又学到了很多东西,对于JVM的迷雾又可以清晰点了。 对于JAVA内存的适合细粒度观察,有没有一些建议? 真是大牛,研究细致到这个级别了,膜拜一下^_^ , 到这个级别都研究源码去了吧 |
|
返回顶楼 | |
发表时间:2011-01-29
最后修改:2011-01-29
kingkan 写道 真心感谢RednaxelaFX大哥的解答,又学到了很多东西,对于JVM的迷雾又可以清晰点了。 对于JAVA内存的适合细粒度观察,有没有一些建议? 撒加应该是把你的问题想复杂了,你说的设置为null才会回收应该是指下面的情形:这3段代码加-verbose:gc运行。 public static void main(String[] args)() { byte[] placeholder = new byte[64 * 1024 * 1024]; System.gc(); } 结果:placeholder没有被干掉,这是天经地义的,因为执行System.gc()的时候PC计数器还没越过placeholder的作用域。 引用 [GC 66846K->65824K(125632K), 0.0032678 secs]
[Full GC 65824K->65746K(125632K), 0.0064131 secs] public static void main(String[] args)() { { byte[] placeholder = new byte[64 * 1024 * 1024]; } System.gc(); } 结果:placeholder还是没有被干掉,这时候PC计数器越过了它的作用域,但是它在本地变量表中的Slot还没有被清除,也没有被其他变量复用。 引用 [GC 66846K->65888K(125632K), 0.0009397 secs]
[Full GC 65888K->65746K(125632K), 0.0051574 secs] public static void main(String[] args)() { { byte[] placeholder = new byte[64 * 1024 * 1024]; placeholder = null; } // 或者上面那句=null的不写,在这里写个int a = 0;也可以 System.gc(); } 结果:这次placeholder完蛋了。虽然执行System.gc()的时候这个方法的栈帧没有被回收,但本地变量表那个slot被填null了,使用int a= 0的话就被变量a复用了,这时候GC Roots才没有placeholder。 引用 [GC 66401K->65778K(125632K), 0.0035471 secs]
[Full GC 65778K->218K(125632K), 0.0140596 secs] 这时候设null也不能说一点意义都没有,意义真的不大就是了,等jit编译了就会削除掉。 另外,要在程序中精确得知某个变量有没有挂掉,如撒加所说,靠Runtime.getRuntime().freeMemory()是不靠谱的,毕竟正常人不会和上面例子一样没事搞个64M的对象来耍,变量挂掉内存变化就会很明显看出来。要做到这点可以考虑用PhantomReference,回收后在ReferenceQueue中会收到消息。 |
|
返回顶楼 | |
发表时间:2011-01-29
最后修改:2011-01-29
试了一下下面代码,无论是c1还是c2,jit后placeholder = null都没有被干掉。
public static void fillHeap(boolean go, int n) throws Exception { { byte[] placeholder = new byte[n]; placeholder = null; } if (go) System.gc(); } public static void main(String[] args) throws Exception { for (int i = 0; i < 100000; i++) { fillHeap(false, 1); } fillHeap(true, 64 * 1024 * 1024); } 撒加用-XX:+PrintCompilation -XX:+PrintAssembly跑一下看看? |
|
返回顶楼 | |
发表时间:2011-01-30
最后修改:2011-01-30
IcyFenix 写道 试了一下下面代码,无论是c1还是c2,jit后placeholder = null都没有被干掉。
public static void fillHeap(boolean go, int n) throws Exception { { byte[] placeholder = new byte[n]; placeholder = null; } if (go) System.gc(); } public static void main(String[] args) throws Exception { for (int i = 0; i < 100000; i++) { fillHeap(false, 1); } fillHeap(true, 64 * 1024 * 1024); } 撒加用-XX:+PrintCompilation -XX:+PrintAssembly跑一下看看? 请问楼上的大大,测试的环境和启动参数是什么? 小的也看了下,发现fillHeap在两种条件下都编译了 小的测试的环境是XP SP2,JDK 1.6.0_14 fastdebug D:\test>java -version java version "1.6.0_14-ea-fastdebug" Java(TM) SE Runtime Environment (build 1.6.0_14-ea-fastdebug-b04) Java HotSpot(TM) Client VM (build 14.0-b13-fastdebug, mixed mode) D:\test>java -Xms128m -Xmx128m -XX:+PrintCompilation -XX:+PrintInlining Test VM option '+PrintCompilation' VM option '+PrintInlining' 1 java.lang.String::hashCode (60 bytes) 2 java.lang.String::charAt (33 bytes) 3 Test::fillHeap (14 bytes) 1% Test::main @ 2 (26 bytes) @ 10 Test::fillHeap (14 bytes) @ 22 Test::fillHeap (14 bytes) D:\test>java -server -Xms128m -Xmx128m -XX:+PrintCompilation -XX:+PrintInlining Test VM option '+PrintCompilation' VM option '+PrintInlining' 1 java.lang.String::charAt (33 bytes) 2 Test::fillHeap (14 bytes) 1% Test::main @ 2 (26 bytes) @ 10 Test::fillHeap inline (hot) @ 22 Test::fillHeap inline (hot) @ 10 java.lang.System::gc executed < MinInliningThreshold times client编译的fillHeap: Decoding compiled method 0x00bb1cc8: Code: [Disassembling for mach='i386'] [Entry Point] [Verified Entry Point] ;; block B3 [0, 0] 0x00bb1db0: mov %eax,-0x4000(%esp) 0x00bb1db7: push %ebp 0x00bb1db8: mov %esp,%ebp 0x00bb1dba: sub $0x18,%esp ;*iload_1 ; - Test::fillHeap@0 (line 4) 0x00bb1dbd: mov %ecx,0x10(%esp) ;; block B0 [0, 7] 0x00bb1dc1: mov %edx,%ebx 0x00bb1dc3: mov $0x18010850,%edx ; {oop({type array byte})} 0x00bb1dc8: mov %ebx,%edi 0x00bb1dca: cmp $0xffffff,%ebx 0x00bb1dd0: ja 0x00bb1e6e 0x00bb1dd6: mov $0x13,%esi 0x00bb1ddb: lea (%esi,%ebx,1),%esi 0x00bb1dde: and $0xfffffff8,%esi 0x00bb1de1: mov %fs:0x0(,%eiz,1),%ecx 0x00bb1de9: mov -0xc(%ecx),%ecx 0x00bb1dec: mov 0x44(%ecx),%eax 0x00bb1def: lea (%eax,%esi,1),%esi 0x00bb1df2: cmp 0x4c(%ecx),%esi 0x00bb1df5: ja 0x00bb1e6e 0x00bb1dfb: mov %esi,0x44(%ecx) 0x00bb1dfe: sub %eax,%esi 0x00bb1e00: movl $0x1,(%eax) 0x00bb1e06: mov %edx,0x4(%eax) 0x00bb1e09: mov %ebx,0x8(%eax) 0x00bb1e0c: sub $0xc,%esi 0x00bb1e0f: je 0x00bb1e52 0x00bb1e15: test $0x3,%esi 0x00bb1e1b: je 0x00bb1e32 0x00bb1e21: push $0x838451c ; {external_word} 0x00bb1e26: call 0x00bb1e2b 0x00bb1e2b: pusha 0x00bb1e2c: call 0x0801b6a0 ; {runtime_call} 0x00bb1e31: hlt 0x00bb1e32: xor %ebx,%ebx 0x00bb1e34: shr $0x3,%esi 0x00bb1e37: jae 0x00bb1e47 0x00bb1e3d: mov %ebx,0xc(%eax,%esi,8) 0x00bb1e41: je 0x00bb1e52 0x00bb1e47: mov %ebx,0x8(%eax,%esi,8) 0x00bb1e4b: mov %ebx,0x4(%eax,%esi,8) 0x00bb1e4f: dec %esi 0x00bb1e50: jne 0x00bb1e47 ;*newarray ; - Test::fillHeap@1 (line 4) 0x00bb1e52: mov 0x10(%esp),%ecx 0x00bb1e56: cmp $0x0,%ecx ;; 22 branch [EQ] [B2] 0x00bb1e59: je 0x00bb1e64 ;*ifeq ; - Test::fillHeap@7 (line 7) ;; block B1 [10, 10] 0x00bb1e5f: call 0x00b6b3d0 ; OopMap{off=180} ;*invokestatic gc ; - Test::fillHeap@10 (line 8) ; {static_call} ;; block B2 [13, 13] 0x00bb1e64: mov %ebp,%esp 0x00bb1e66: pop %ebp 0x00bb1e67: test %eax,0x960100 ; {poll_return} 0x00bb1e6d: ret ;; NewTypeArrayStub slow case 0x00bb1e6e: call 0x00baef50 ; OopMap{off=195} ;*newarray ; - Test::fillHeap@1 (line 4) ; {runtime_call} 0x00bb1e73: jmp 0x00bb1e52 0x00bb1e75: nop 0x00bb1e76: nop 0x00bb1e77: hlt 0x00bb1e78: hlt 0x00bb1e79: hlt 0x00bb1e7a: hlt 0x00bb1e7b: hlt 0x00bb1e7c: hlt 0x00bb1e7d: hlt 0x00bb1e7e: hlt 0x00bb1e7f: hlt [Stub Code] 0x00bb1e80: nop ; {no_reloc} 0x00bb1e81: nop 0x00bb1e82: mov $0x0,%ebx ; {static_stub} 0x00bb1e87: jmp 0x00bb1e87 ; {runtime_call} [Exception Handler] 0x00bb1e8c: mov $0xdead,%ebx 0x00bb1e91: mov $0xdead,%ecx 0x00bb1e96: mov $0xdead,%edx 0x00bb1e9b: mov $0xdead,%esi 0x00bb1ea0: mov $0xdead,%edi 0x00bb1ea5: jmp 0x00bad920 ; {runtime_call} 0x00bb1eaa: push $0xbb1eaa ; {section_word} 0x00bb1eaf: jmp 0x00b6ba40 ; {runtime_call} server编译的fillHeap: Decoding compiled method 0x00bdad48: Code: [Disassembling for mach='i386'] [Entry Point] [Verified Entry Point] 0x00bdae20: mov %eax,-0x4000(%esp) 0x00bdae27: push %ebp 0x00bdae28: sub $0x8,%esp ;*synchronization entry ; - Test::fillHeap@-1 (line 4) 0x00bdae2e: mov %ecx,%ebp 0x00bdae30: cmp $0x100000,%edx 0x00bdae36: ja 0x00bdaea8 0x00bdae38: mov %edx,%ecx 0x00bdae3a: add $0x13,%ecx 0x00bdae3d: mov %ecx,%esi 0x00bdae3f: and $0xfffffff8,%esi 0x00bdae42: mov %fs:0x0,%edi 0x00bdae49: mov -0xc(%edi),%ebx 0x00bdae4f: mov 0x44(%ebx),%eax 0x00bdae52: mov %eax,%edi 0x00bdae54: add %esi,%edi 0x00bdae56: cmp 0x4c(%ebx),%edi 0x00bdae59: jae 0x00bdaea8 0x00bdae5b: mov %edi,0x44(%ebx) 0x00bdae5e: prefetchnta 0x100(%edi) 0x00bdae65: movl $0x1,(%eax) 0x00bdae6b: prefetchnta 0x140(%edi) 0x00bdae72: movl $0x18010850,0x4(%eax) ; {oop({type array byte})} 0x00bdae79: mov %edx,0x8(%eax) 0x00bdae7c: prefetchnta 0x180(%edi) 0x00bdae83: movl $0x0,0xc(%eax) 0x00bdae8a: lea 0x10(%eax),%edi 0x00bdae8d: shr $0x3,%ecx 0x00bdae90: add $0xfffffffe,%ecx 0x00bdae93: shl %ecx 0x00bdae95: xor %eax,%eax 0x00bdae97: rep stos %eax,%es:(%edi) ;*newarray ; - Test::fillHeap@1 (line 4) 0x00bdae99: test %ebp,%ebp 0x00bdae9b: jne 0x00bdaeb6 ;*ifeq ; - Test::fillHeap@7 (line 7) 0x00bdae9d: add $0x8,%esp 0x00bdaea0: pop %ebp 0x00bdaea1: test %eax,0x9b0000 ; {poll_return} 0x00bdaea7: ret 0x00bdaea8: mov $0x18010850,%ecx ; {oop({type array byte})} 0x00bdaead: nop 0x00bdaeae: nop 0x00bdaeaf: call 0x00bda920 ; OopMap{off=148} ;*newarray ; - Test::fillHeap@1 (line 4) ; {runtime_call} 0x00bdaeb4: jmp 0x00bdae99 ;*synchronization entry ; - Test::fillHeap@-1 (line 4) 0x00bdaeb6: mov $0x16,%ecx 0x00bdaebb: call 0x00bba680 ; OopMap{off=160} ;*invokestatic gc ; - Test::fillHeap@10 (line 8) ; {runtime_call} 0x00bdaec0: int3 ;*newarray ; - Test::fillHeap@1 (line 4) 0x00bdaec1: mov %eax,%ecx 0x00bdaec3: add $0x8,%esp 0x00bdaec6: pop %ebp 0x00bdaec7: jmp 0x00bdbee0 ; {runtime_call} 0x00bdaecc: hlt 0x00bdaecd: hlt 0x00bdaece: hlt 0x00bdaecf: hlt 0x00bdaed0: hlt 0x00bdaed1: hlt 0x00bdaed2: hlt 0x00bdaed3: hlt 0x00bdaed4: hlt 0x00bdaed5: hlt 0x00bdaed6: hlt 0x00bdaed7: hlt 0x00bdaed8: hlt 0x00bdaed9: hlt 0x00bdaeda: hlt 0x00bdaedb: hlt 0x00bdaedc: hlt 0x00bdaedd: hlt 0x00bdaede: hlt 0x00bdaedf: hlt [Exception Handler] [Stub Code] 0x00bdaee0: jmp 0x00bdab00 ; {no_reloc} 0x00bdaee5: push $0xbdaee5 ; {section_word} 0x00bdaeea: jmp 0x00bbbb40 ; {runtime_call} [Constants] 0x00bdaeef: int3 两个版本的placeholder = null都被干掉了。编译的代码结构差不多,快速路径中, ·先检查是否将有栈溢出(mov %eax,-0x4000(%esp)), ·然后保存上一栈帧(push %ebp)并建立新栈帧(sub $0x8,%esp), ·然后尝试从TLAB申请空间, ·然后初始化数组的对象头,并对数组内容清零, ·(注意这里没有了placeholder = null的动作), ·然后判断go的真假,真的时候调用System.gc(), ·然后撤销栈帧(mov %ebp,%esp)并恢复上一栈帧(pop %ebp), ·然后临返回前检查safepoint(test %eax,0x9b0000), ·最后返回(ret)。 client代码中,指向新建数组实例的指针存在%eax里,也就是代码中placeholder变量被分配到了%eax上。但给数组分配了空间并将数组内容清零后没有再出现对%eax赋值的指令,可准确判断placeholder = null的代码被消除了。 server代码中,0x00bdae95: xor %eax,%eax用于数组内容清零。在这之前,指向新建数组实例的指针就存在%eax,也就是placeholder变量分配在前半部分代码里分配到%eax上。但执行了异或指令后%eax的值就被破坏掉了,虽然实际效果跟placeholder = null一样,但因为后面%eax转为用作数组清零所以这个赋值并不能看作是placeholder = null。 |
|
返回顶楼 | |
发表时间:2011-01-31
bugmenot 写道 两个版本的placeholder = null都被干掉了。编译的代码结构差不多,快速路径中,
·先检查是否将有栈溢出(mov %eax,-0x4000(%esp)), ·然后保存上一栈帧(push %ebp)并建立新栈帧(sub $0x8,%esp), ·然后尝试从TLAB申请空间, ·然后初始化数组的对象头,并对数组内容清零, ·(注意这里没有了placeholder = null的动作), ·然后判断go的真假,真的时候调用System.gc(), ·然后撤销栈帧(mov %ebp,%esp)并恢复上一栈帧(pop %ebp), ·然后临返回前检查safepoint(test %eax,0x9b0000), ·最后返回(ret)。 client代码中,指向新建数组实例的指针存在%eax里,也就是代码中placeholder变量被分配到了%eax上。但给数组分配了空间并将数组内容清零后没有再出现对%eax赋值的指令,可准确判断placeholder = null的代码被消除了。 server代码中,0x00bdae95: xor %eax,%eax用于数组内容清零。在这之前,指向新建数组实例的指针就存在%eax,也就是placeholder变量分配在前半部分代码里分配到%eax上。但执行了异或指令后%eax的值就被破坏掉了,虽然实际效果跟placeholder = null一样,但因为后面%eax转为用作数组清零所以这个赋值并不能看作是placeholder = null。 那把前面的话换一个说法:“placeholder = null并不是无意义的,是否存在placeholder = null会直接影响jit后的编译结果”。兄弟认同否? |
|
返回顶楼 | |
发表时间:2011-01-31
IcyFenix 写道 bugmenot 写道 两个版本的placeholder = null都被干掉了。编译的代码结构差不多,快速路径中,
·先检查是否将有栈溢出(mov %eax,-0x4000(%esp)), ·然后保存上一栈帧(push %ebp)并建立新栈帧(sub $0x8,%esp), ·然后尝试从TLAB申请空间, ·然后初始化数组的对象头,并对数组内容清零, ·(注意这里没有了placeholder = null的动作), ·然后判断go的真假,真的时候调用System.gc(), ·然后撤销栈帧(mov %ebp,%esp)并恢复上一栈帧(pop %ebp), ·然后临返回前检查safepoint(test %eax,0x9b0000), ·最后返回(ret)。 client代码中,指向新建数组实例的指针存在%eax里,也就是代码中placeholder变量被分配到了%eax上。但给数组分配了空间并将数组内容清零后没有再出现对%eax赋值的指令,可准确判断placeholder = null的代码被消除了。 server代码中,0x00bdae95: xor %eax,%eax用于数组内容清零。在这之前,指向新建数组实例的指针就存在%eax,也就是placeholder变量分配在前半部分代码里分配到%eax上。但执行了异或指令后%eax的值就被破坏掉了,虽然实际效果跟placeholder = null一样,但因为后面%eax转为用作数组清零所以这个赋值并不能看作是placeholder = null。 那把前面的话换一个说法:“placeholder = null并不是无意义的,是否存在placeholder = null会直接影响jit后的编译结果”。兄弟认同否? 从上面的编译结果看,placeholer = null就是无意义的,对jit没有任何影响。 |
|
返回顶楼 | |
发表时间:2011-01-31
IcyFenix 写道 那把前面的话换一个说法:“placeholder = null并不是无意义的,是否存在placeholder = null会直接影响jit后的编译结果”。兄弟认同否?
小的不敢认同,对不对还等大大们自己定夺 小的把placeholder = null注释掉再跑了一次, public class Test { public static void fillHeap(boolean go, int n) throws Exception { { byte[] placeholder = new byte[n]; // placeholder = null; } if (go) System.gc(); } public static void main(String[] args) throws Exception { for (int i = 0; i < 100000; i++) { fillHeap(false, 1); } fillHeap(true, 64 * 1024 * 1024); } } client编译出来的fillHeap: Decoding compiled method 0x00bb1cc8: Code: [Disassembling for mach='i386'] [Entry Point] [Verified Entry Point] ;; block B3 [0, 0] 0x00bb1db0: mov %eax,-0x4000(%esp) 0x00bb1db7: push %ebp 0x00bb1db8: mov %esp,%ebp 0x00bb1dba: sub $0x18,%esp ;*iload_1 ; - Test::fillHeap@0 (line 4) 0x00bb1dbd: mov %ecx,0x10(%esp) ;; block B0 [0, 5] 0x00bb1dc1: mov %edx,%ebx 0x00bb1dc3: mov $0x18010850,%edx ; {oop({type array byte})} 0x00bb1dc8: mov %ebx,%edi 0x00bb1dca: cmp $0xffffff,%ebx 0x00bb1dd0: ja 0x00bb1e6e 0x00bb1dd6: mov $0x13,%esi 0x00bb1ddb: lea (%esi,%ebx,1),%esi 0x00bb1dde: and $0xfffffff8,%esi 0x00bb1de1: mov %fs:0x0(,%eiz,1),%ecx 0x00bb1de9: mov -0xc(%ecx),%ecx 0x00bb1dec: mov 0x44(%ecx),%eax 0x00bb1def: lea (%eax,%esi,1),%esi 0x00bb1df2: cmp 0x4c(%ecx),%esi 0x00bb1df5: ja 0x00bb1e6e 0x00bb1dfb: mov %esi,0x44(%ecx) 0x00bb1dfe: sub %eax,%esi 0x00bb1e00: movl $0x1,(%eax) 0x00bb1e06: mov %edx,0x4(%eax) 0x00bb1e09: mov %ebx,0x8(%eax) 0x00bb1e0c: sub $0xc,%esi 0x00bb1e0f: je 0x00bb1e52 0x00bb1e15: test $0x3,%esi 0x00bb1e1b: je 0x00bb1e32 0x00bb1e21: push $0x838451c ; {external_word} 0x00bb1e26: call 0x00bb1e2b 0x00bb1e2b: pusha 0x00bb1e2c: call 0x0801b6a0 ; {runtime_call} 0x00bb1e31: hlt 0x00bb1e32: xor %ebx,%ebx 0x00bb1e34: shr $0x3,%esi 0x00bb1e37: jae 0x00bb1e47 0x00bb1e3d: mov %ebx,0xc(%eax,%esi,8) 0x00bb1e41: je 0x00bb1e52 0x00bb1e47: mov %ebx,0x8(%eax,%esi,8) 0x00bb1e4b: mov %ebx,0x4(%eax,%esi,8) 0x00bb1e4f: dec %esi 0x00bb1e50: jne 0x00bb1e47 ;*newarray ; - Test::fillHeap@1 (line 4) 0x00bb1e52: mov 0x10(%esp),%ecx 0x00bb1e56: cmp $0x0,%ecx ;; 22 branch [EQ] [B2] 0x00bb1e59: je 0x00bb1e64 ;*ifeq ; - Test::fillHeap@5 (line 7) ;; block B1 [8, 8] 0x00bb1e5f: call 0x00b6b3d0 ; OopMap{off=180} ;*invokestatic gc ; - Test::fillHeap@8 (line 8) ; {static_call} ;; block B2 [11, 11] 0x00bb1e64: mov %ebp,%esp 0x00bb1e66: pop %ebp 0x00bb1e67: test %eax,0x960100 ; {poll_return} 0x00bb1e6d: ret ;; NewTypeArrayStub slow case 0x00bb1e6e: call 0x00baef50 ; OopMap{off=195} ;*newarray ; - Test::fillHeap@1 (line 4) ; {runtime_call} 0x00bb1e73: jmp 0x00bb1e52 0x00bb1e75: nop 0x00bb1e76: nop 0x00bb1e77: hlt 0x00bb1e78: hlt 0x00bb1e79: hlt 0x00bb1e7a: hlt 0x00bb1e7b: hlt 0x00bb1e7c: hlt 0x00bb1e7d: hlt 0x00bb1e7e: hlt 0x00bb1e7f: hlt [Stub Code] 0x00bb1e80: nop ; {no_reloc} 0x00bb1e81: nop 0x00bb1e82: mov $0x0,%ebx ; {static_stub} 0x00bb1e87: jmp 0x00bb1e87 ; {runtime_call} [Exception Handler] 0x00bb1e8c: mov $0xdead,%ebx 0x00bb1e91: mov $0xdead,%ecx 0x00bb1e96: mov $0xdead,%edx 0x00bb1e9b: mov $0xdead,%esi 0x00bb1ea0: mov $0xdead,%edi 0x00bb1ea5: jmp 0x00bad920 ; {runtime_call} 0x00bb1eaa: push $0xbb1eaa ; {section_word} 0x00bb1eaf: jmp 0x00b6ba40 ; {runtime_call} 代码与带有placeholder = null时编译出来的一模一样。唯有的差异是原始字节码短了2字节,所以PrintAssembly出来的注释的基本块大小有点差异。 server编译出来的fillHeap: Decoding compiled method 0x00bdb608: Code: [Disassembling for mach='i386'] [Entry Point] [Verified Entry Point] 0x00bdb6e0: mov %eax,-0x4000(%esp) 0x00bdb6e7: push %ebp 0x00bdb6e8: sub $0x8,%esp ;*synchronization entry ; - Test::fillHeap@-1 (line 4) 0x00bdb6ee: mov %ecx,%ebp 0x00bdb6f0: cmp $0x100000,%edx 0x00bdb6f6: ja 0x00bdb768 0x00bdb6f8: mov %edx,%ecx 0x00bdb6fa: add $0x13,%ecx 0x00bdb6fd: mov %ecx,%esi 0x00bdb6ff: and $0xfffffff8,%esi 0x00bdb702: mov %fs:0x0,%edi 0x00bdb709: mov -0xc(%edi),%ebx 0x00bdb70f: mov 0x44(%ebx),%eax 0x00bdb712: mov %eax,%edi 0x00bdb714: add %esi,%edi 0x00bdb716: cmp 0x4c(%ebx),%edi 0x00bdb719: jae 0x00bdb768 0x00bdb71b: mov %edi,0x44(%ebx) 0x00bdb71e: prefetchnta 0x100(%edi) 0x00bdb725: movl $0x1,(%eax) 0x00bdb72b: prefetchnta 0x140(%edi) 0x00bdb732: movl $0x18010850,0x4(%eax) ; {oop({type array byte})} 0x00bdb739: mov %edx,0x8(%eax) 0x00bdb73c: prefetchnta 0x180(%edi) 0x00bdb743: movl $0x0,0xc(%eax) 0x00bdb74a: lea 0x10(%eax),%edi 0x00bdb74d: shr $0x3,%ecx 0x00bdb750: add $0xfffffffe,%ecx 0x00bdb753: shl %ecx 0x00bdb755: xor %eax,%eax 0x00bdb757: rep stos %eax,%es:(%edi) ;*newarray ; - Test::fillHeap@1 (line 4) 0x00bdb759: test %ebp,%ebp 0x00bdb75b: jne 0x00bdb776 ;*ifeq ; - Test::fillHeap@5 (line 7) 0x00bdb75d: add $0x8,%esp 0x00bdb760: pop %ebp 0x00bdb761: test %eax,0x9b0000 ; {poll_return} 0x00bdb767: ret 0x00bdb768: mov $0x18010850,%ecx ; {oop({type array byte})} 0x00bdb76d: nop 0x00bdb76e: nop 0x00bdb76f: call 0x00bda7e0 ; OopMap{off=148} ;*newarray ; - Test::fillHeap@1 (line 4) ; {runtime_call} 0x00bdb774: jmp 0x00bdb759 ;*synchronization entry ; - Test::fillHeap@-1 (line 4) 0x00bdb776: mov $0x16,%ecx 0x00bdb77b: call 0x00bba680 ; OopMap{off=160} ;*invokestatic gc ; - Test::fillHeap@8 (line 8) ; {runtime_call} 0x00bdb780: int3 ;*newarray ; - Test::fillHeap@1 (line 4) 0x00bdb781: mov %eax,%ecx 0x00bdb783: add $0x8,%esp 0x00bdb786: pop %ebp 0x00bdb787: jmp 0x00bdbfe0 ; {runtime_call} 0x00bdb78c: hlt 0x00bdb78d: hlt 0x00bdb78e: hlt 0x00bdb78f: hlt 0x00bdb790: hlt 0x00bdb791: hlt 0x00bdb792: hlt 0x00bdb793: hlt 0x00bdb794: hlt 0x00bdb795: hlt 0x00bdb796: hlt 0x00bdb797: hlt 0x00bdb798: hlt 0x00bdb799: hlt 0x00bdb79a: hlt 0x00bdb79b: hlt 0x00bdb79c: hlt 0x00bdb79d: hlt 0x00bdb79e: hlt 0x00bdb79f: hlt [Exception Handler] [Stub Code] 0x00bdb7a0: jmp 0x00bdab00 ; {no_reloc} 0x00bdb7a5: push $0xbdb7a5 ; {section_word} 0x00bdb7aa: jmp 0x00bbbb40 ; {runtime_call} [Constants] 0x00bdb7af: int3 与client的情况一样,去掉placeholder = null之后编译出来的代码没有变化。直接diff能看到的都是地址上的差异,因为多次运行编译出来的代码不一定在同一地址上,这是正常的;除地址之外,操作指令都一模一样。 IcyFenix大大,小的也请教了问题,大大还没回答呢 bugmenot 写道 请问楼上的大大,测试的环境和启动参数是什么?
|
|
返回顶楼 | |
发表时间:2011-01-31
我想说,最好避免太长的引用,看着眼睛好痛。
|
|
返回顶楼 | |
发表时间:2011-01-31
bugmenot 写道 小的把placeholder = null注释掉再跑了一次, 与client的情况一样,去掉placeholder = null之后编译出来的代码没有变化。直接diff能看到的都是地址上的差异,因为多次运行编译出来的代码不一定在同一地址上,这是正常的;除地址之外,操作指令都一模一样。 IcyFenix大大,小的也请教了问题,大大还没回答呢 请问楼上的大大,测试的环境和启动参数是什么? 公司里测试的是6u21,现在在家里用笔记本,下面测试用的是刚刚下的openjdk7-b127-fastdebug D:\>java -version java version "1.7.0-ea-fastdebug" Java(TM) SE Runtime Environment (build 1.7.0-ea-fastdebug-b127) Java HotSpot(TM) Client VM (build 20.0-b06-fastdebug, mixed mode) 参数是就2个:-verbose:gc -XX:+PrintCompilation,由于没有梯子,下载不到墙外的hsdis-i386.dll,所以没加PrintAssembly,如果打印出来的asm都一样的话,对于下面这两次测试的结果,黑体字部分我觉得很疑惑,请兄弟不吝指教。 不带placeholder = null的版本 225 1 java.lang.String::hashCode (67 bytes) VM option '+PrintCompilation' 228 2 java.lang.String::charAt (33 bytes) 229 3 java.lang.String::indexOf (87 bytes) 230 4 java.lang.Object::<init> (1 bytes) 230 5 java.lang.String::indexOf (166 bytes) 242 6 org.fenixsoft.test.Test::fillHeap (12 bytes) 243 1% org.fenixsoft.test.Test::main @ 5 (26 bytes) [GC 2080K->267K(15872K), 0.0042400 secs] [Full GC 267K->267K(15872K), 0.0117231 secs] [Full GC 65893K->65803K(81476K), 0.0140037 secs] 带placeholder = null的版本 253 1 java.lang.String::hashCode (67 bytes) VM option '+PrintCompilation' 258 2 java.lang.String::charAt (33 bytes) 259 3 java.lang.String::indexOf (87 bytes) 261 4 java.lang.Object::<init> (1 bytes) 261 5 java.lang.String::indexOf (166 bytes) 274 6 org.fenixsoft.test.Test::fillHeap (17 bytes) 275 1% org.fenixsoft.test.Test::main @ 5 (26 bytes) [GC 2080K->267K(15872K), 0.0043529 secs] [Full GC 267K->267K(15872K), 0.0124955 secs] [Full GC 65893K->267K(81476K), 0.0235428 secs] |
|
返回顶楼 | |