JE的水真的太深

纯粹记录一下,JE随便遇个30帖路人都很厉害,日后多思少言,验证问题不能想当然。

http://www.iteye.com/topic/894148?page=2

一楼:
[quote="mingjian01"][quote="kingkan"][quote="RednaxelaFX"][quote="kingkan"]请教下前辈们:

1.手动System.gc()与JVM自动gc有什么根本上的区别么?在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。

2.用户能自己设置JVM选择何种GC算法来进行GC么?[/quote]
1、根本的区别之一:System.gc()可能在自动GC原本不会进入GC的位置上进入GC。
正常情况下,Java代码要尝试在GC堆上分配空间的时候才会触发GC;换句话说,基本上是“new”的时候才会触发GC。但System.gc()、JVMTI的强制GC等动作都在正常情况之外提示系统要做一次GC。
[quote="kingkan"]在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。[/quote]
这个情况即便在HotSpot上也不一定,要看被测的方法有没有被JIT编译过。解释执行的时候基本上没啥优化,所以局部变量的存活范围跟源码里看起来是一致的。但因为HotSpot的JIT编译带有优化,一个局部变量在一个方法里被使用过的赋值才会有效,而未被使用过的赋值很可能被消除掉。这样,在方法最后把局部变量设为null就是徒劳的,赋值动作本身都会被消除。事实上不需要额外的赋值为null的动作,JIT编译器也会尽可能的缩小变量的有效范围,所以完全没必要在方法末尾将局部变量置null。
在.NET的CLR上,由于方法总是要被编译了才可以执行(AOT或者JIT),而且编译也带有优化,源码里变量的引用状况跟实际运行时的引用状况的差异可能更明显些,像[url=http://rednaxelafx.iteye.com/blog/248610]这个例子[/url]就比较极端。

话说回来,Runtime.totalMemory()和Runtime.freeMemory()都是很RP的方法,其实并不适合细粒度观察…不知道您是用什么方式来“通过内存使用数据查看,该对象的内存是还没释放的”呢?
HotSpot默认会对方法调用次数的计数器做“衰减”,每进一次GC就会检查是否已到半衰周期,到了就会把所有方法的调用次数的计数器减半。如果写microbenchmark的话,在被测的方法里插入System.gc()很可能会带来干扰,使被测方法的调用次数始终达不到编译的条件,导致其不被JIT编译。要禁用计数器衰减的话,启动VM的时候要给参数[b]-XX:-UseCounterDecay[/b]。要确认某个方法有没有被JIT编译请使用[b]-XX:+PrintCompilation[/b]。
靠microbenchmark来观察某个对象有没有被GC回收多半是不准确的。可以具体情况具体分析。

另外值得注意的是,System.gc()不一定是触发所谓的“full GC”或者叫“major GC”。
在Sun JDK6与OpenJDK 6的HotSpot里,"[url=http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/tip/src/share/vm/gc_interface/gcCause.hpp]GCCause[/url]是[b]_java_lang_system_gc[/b]"的时候,如果VM启动参数[url=http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/tip/src/share/vm/runtime/globals.hpp][b]DisableExplicitGC[/b][/url]为false,则会触发一次full GC,如果该参数为true则完全不触发任何GC。要将这个参数设置为true,启动的时候写上[b]-XX:+DisableExplicitGC[/b]就行。
HotSpot对System.gc()有特别处理,最主要的地方体现在一次System.gc()是否与普通GC一样会触发GC的统计/阈值数据的更新——HotSpot里的许多GC算法都带有自适应的功能,会根据先前收集的效率来决定接下来的GC中使用的参数,但System.gc()默认不更新这些统计数据,避免用户强行调用GC对这些自适应功能的干扰。除此之外,在HotSpot里,System.gc()所触发的full GC跟普通的full GC没啥大差别。

------------------

在Oracle JRockit里,[url=http://download.oracle.com/docs/cd/E13150_01/jrockit_jvm/jrockit/geninfo/devapps/codeprac.html#wp998554]System.gc()触发的是nursery GC[/url](如果选择了分代GC的话;如果选择的不是分代式GC算法则谈不上nursery还是old)。与HotSpot相同,可以通过一个参数禁用System.gc():[b]-XXnoSystemGC[/b]。也可以通过另一个参数来强制System.gc()做full GC:[b]-XXfullSystemGC[/b]。
JRockit R28里,禁用System.gc()的推荐参数是[b]-XX:AllowSystemGC=false[/b],而设定System.gc()触发full GC的参数是[b]-XX:FullSystemGC=true[/b]。

------------------

在IBM JDK的JVM里,System.gc()同样可以禁用——使用[b]-Xdisableexplicitgc[/b]参数。另外也有一些可以调节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
它们之间的关系请参考:[url=http://blogs.sun.com/jonthecollector/entry/our_collectors]Jon Masamitsu: Our Collectors[/url]
[quote="Jon Masamitsu"][img]http://blogs.sun.com/jonthecollector/resource/Collectors.jpg[/img][/quote]

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[/quote]


真心感谢RednaxelaFX大哥的解答,又学到了很多东西,对于JVM的迷雾又可以清晰点了。

对于JAVA内存的适合细粒度观察,有没有一些建议?[/quote]

真是大牛,研究细致到这个级别了,膜拜一下^_^ , 到这个级别都研究源码去了吧 [/quote]

二楼:
[quote="IcyFenix"][quote="kingkan"]
真心感谢RednaxelaFX大哥的解答,又学到了很多东西,对于JVM的迷雾又可以清晰点了。

对于JAVA内存的适合细粒度观察,有没有一些建议?
[/quote]

撒加应该是把你的问题想复杂了,你说的设置为null才会回收应该是指下面的情形:这3段代码加-verbose:gc运行。

public static void main(String[] args)() {
byte[] placeholder = new byte[64 * 1024 * 1024];
System.gc();
}

结果:placeholder没有被干掉,这是天经地义的,因为执行System.gc()的时候PC计数器还没越过placeholder的作用域。
[quote][GC 66846K->65824K(125632K), 0.0032678 secs]
[Full GC 65824K->[b]65746K[/b](125632K), 0.0064131 secs]
[/quote]

public static void main(String[] args)() {
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
System.gc();
}

结果:placeholder还是没有被干掉,这时候PC计数器越过了它的作用域,但是它在本地变量表中的Slot还没有被清除,也没有被其他变量复用。
[quote][GC 66846K->65888K(125632K), 0.0009397 secs]
[Full GC 65888K->[b]65746K[/b](125632K), 0.0051574 secs]
[/quote]

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。
[quote][GC 66401K->65778K(125632K), 0.0035471 secs]
[Full GC 65778K->[b]218K[/b](125632K), 0.0140596 secs]
[/quote]
这时候设null也不能说一点意义都没有,意义真的不大就是了,等jit编译了就会削除掉。

另外,要在程序中精确得知某个变量有没有挂掉,如撒加所说,靠Runtime.getRuntime().freeMemory()是不靠谱的,毕竟正常人不会和上面例子一样没事搞个64M的对象来耍,变量挂掉内存变化就会很明显看出来。要做到这点可以考虑用PhantomReference,回收后在ReferenceQueue中会收到消息。[/quote]

三楼:
[quote="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跑一下看看?[/quote]

四楼:
[quote="bugmenot"][quote="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跑一下看看?[/quote]
请问楼上的大大,测试的环境和启动参数是什么?
小的也看了下,发现fillHeap在两种条件下都编译了
小的测试的环境是XP SP2,JDK 1.6.0_14 fastdebug
[code]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[/code]

client编译的fillHeap:
[code]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}[/code]

server编译的fillHeap:
[code]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 [/code]
两个版本的placeholder = null都被干掉了。编译的代码结构差不多,快速路径中,
·先检查是否将有栈溢出(mov %eax,-0x4000(%esp)),
·然后保存上一栈帧(push %ebp)并建立新栈帧(sub $0x8,%esp),
·然后尝试从TLAB申请空间,
·然后初始化数组的对象头,并对数组内容清零,
[color=red]·(注意这里没有了placeholder = null的动作),[/color]
·然后判断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。[/quote]

五楼:
[quote="IcyFenix"][quote="bugmenot"]两个版本的placeholder = null都被干掉了。编译的代码结构差不多,快速路径中,
·先检查是否将有栈溢出(mov %eax,-0x4000(%esp)),
·然后保存上一栈帧(push %ebp)并建立新栈帧(sub $0x8,%esp),
·然后尝试从TLAB申请空间,
·然后初始化数组的对象头,并对数组内容清零,
[color=red]·(注意这里没有了placeholder = null的动作),[/color]
·然后判断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。[/quote]

那把前面的话换一个说法:“placeholder = null并不是无意义的,是否存在placeholder = null会直接影响jit后的编译结果”。兄弟认同否?[/quote]

六楼:
[quote="bugmenot"][quote="IcyFenix"]那把前面的话换一个说法:“placeholder = null并不是无意义的,是否存在placeholder = null会直接影响jit后的编译结果”。兄弟认同否?[/quote]
小的不敢认同,对不对还等大大们自己定夺

小的把placeholder = null注释掉再跑了一次,
[code]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);
}
}[/code]

client编译出来的fillHeap:
[code]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}[/code]
代码与带有placeholder = null时编译出来的一模一样。唯有的差异是原始字节码短了2字节,所以PrintAssembly出来的注释的基本块大小有点差异。

server编译出来的fillHeap:
[code]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 [/code]
与client的情况一样,去掉placeholder = null之后编译出来的代码没有变化。直接diff能看到的都是地址上的差异,因为多次运行编译出来的代码不一定在同一地址上,这是正常的;除地址之外,操作指令都一模一样。

IcyFenix大大,小的也请教了问题,大大还没回答呢
[quote="bugmenot"]请问楼上的大大,测试的环境和启动参数是什么? [/quote][/quote]

七楼:
[quote="IcyFenix"][quote="bugmenot"]
小的把placeholder = null注释掉再跑了一次,

与client的情况一样,去掉placeholder = null之后编译出来的代码没有变化。直接diff能看到的都是地址上的差异,因为多次运行编译出来的代码不一定在同一地址上,这是正常的;除地址之外,操作指令都一模一样。

IcyFenix大大,小的也请教了问题,大大还没回答呢

请问楼上的大大,测试的环境和启动参数是什么? [/quote]

公司里测试的是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 [b](12 bytes)[/b]
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->[b]65803K(81476K)[/b], 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 [b](17 bytes)[/b]
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->[b]267K(81476K)[/b], 0.0235428 secs]
[/quote]

八楼:
[quote="bugmenot"][quote="IcyFenix"]由于没有梯子,下载不到墙外的hsdis-i386.dll,所以没加PrintAssembly[/quote]
小的也没用梯子呀,这里就有:[url]http://hllvm.group.iteye.com/group/share[/url]

[quote="IcyFenix"]242 6 org.fenixsoft.test.Test::fillHeap [b](12 bytes)[/b][/quote]
这数字是被编译的方法原本字节码的大小。用javap能看到。
小的跑不带placeholder = null测试用的完整代码是这样:
[code]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);
}
}[/code]
用javap看fillHeap
[code]public static void fillHeap(boolean, int) throws java.lang.Exception;
Code:
Stack=1, Locals=3, Args_size=2
0: iload_1
1: newarray byte
3: astore_2
4: iload_0
5: ifeq 11
8: invokestatic #2; //Method java/lang/System.gc:()V
11: return
LineNumberTable:
line 4: 0
line 7: 4
line 8: 8
line 9: 11

LocalVariableTable:
Start Length Slot Name Signature
4 0 2 placeholder [B
0 12 0 go Z
0 12 1 n I

StackMapTable: number_of_entries = 1
frame_type = 11 /* same */

Exceptions:
throws java.lang.Exception[/code]
字节码就12字节,能对上。

把注释去掉,带上placeholder = null的fillHeap,用javap看
[code]public static void fillHeap(boolean, int) throws java.lang.Exception;
Code:
Stack=1, Locals=3, Args_size=2
0: iload_1
1: newarray byte
3: astore_2
4: aconst_null
5: astore_2
6: iload_0
7: ifeq 13
10: invokestatic #2; //Method java/lang/System.gc:()V
13: return
LineNumberTable:
line 4: 0
line 5: 4
line 7: 6
line 8: 10
line 9: 13

LocalVariableTable:
Start Length Slot Name Signature
4 2 2 placeholder [B
0 14 0 go Z
0 14 1 n I

StackMapTable: number_of_entries = 1
frame_type = 13 /* same */

Exceptions:
throws java.lang.Exception[/code]
就比不带placeholder = null多两条指令,字节码大小增加到14字节。IcyFenix大大的测试里带上placeholder = null是17字节是因为大大用的测试代码与小的的不一样。所以才有必要把完整的测试代码,运行环境,启动参数都拉出来晒晒,不然讨论的就不是同一个东西。

[quote="IcyFenix"][Full GC [b]65893K->65803K[/b](81476K), 0.0140037 secs][/quote]
不知大大是如何跑测试的。小的继续在XP SP2,JDK 1.6.0_14 fastdebug上跑,不带placeholder = null跑出来几组结果

D:\test>java -Xmx128m -Xms128m -XX:+PrintCompilation -XX:+PrintGC -XX:+PrintGCDetails Test
VM option '+PrintCompilation'
VM option '+PrintGC'
VM option '+PrintGCDetails'
1 java.lang.String::hashCode (60 bytes)
2 java.lang.String::charAt (33 bytes)
3 Test::fillHeap [b](12 bytes)[/b]
1% Test::main @ 2 (26 bytes)
[Full GC (System) [Tenured: [b]65536K->137K(121024K)[/b], 0.0829719 secs] 67324K->137K(130112K), [Perm : 2284K->2284K(12288K)], 0.0835611 secs] [Times: user=0.08 sys=0.00, real=0.08 secs]
Heap
def new generation total 9088K, used 162K [0x10010000, 0x109e0000, 0x109e0000)
eden space 8128K, 2% used [0x10010000, 0x10038aa8, 0x10800000)
from space 960K, 0% used [0x10800000, 0x10800000, 0x108f0000)
to space 960K, 0% used [0x108f0000, 0x108f0000, 0x109e0000)
tenured generation total 121024K, used 137K [0x109e0000, 0x18010000, 0x18010000)
the space 121024K, 0% used [0x109e0000, 0x10a02530, 0x10a02600, 0x18010000)

compacting perm gen total 12288K, used 2313K [0x18010000, 0x18c10000, 0x1c010000)
the space 12288K, 18% used [0x18010000, 0x18252710, 0x18252800, 0x18c10000)
No shared spaces configured.

D:\test>java -server -Xmx128m -Xms128m -XX:+PrintCompilation -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseParallelGC Test
VM option '+PrintCompilation'
VM option '+PrintGC'
VM option '+PrintGCDetails'
VM option '+UseParallelGC'
1 java.lang.String::charAt (33 bytes)
2 Test::fillHeap [b](12 bytes)[/b]
1% Test::main @ 2 (26 bytes)
2 made not entrant (2) Test::fillHeap (12 bytes)
[GC [PSYoungGen: 1970K->168K(12736K)] 67506K->65704K(129280K), 0.3141070 secs] [Times: user=0.63 sys=0.00, real=0.31 secs]
[Full GC (System) [PSYoungGen: 168K->0K(12736K)] [PSOldGen: [b]65536K->137K[/b](116544K)] 65704K->137K(129280K) [PSPermGen: 2286K->2286K(16384K)], 0.0629148 secs] [Times: user=0.06 sys=0.00, real=0.06 secs]
Heap
PSYoungGen total 12736K, used 218K [0x1b1e0000, 0x1c010000, 0x1c010000)
eden space 10944K, 2% used [0x1b1e0000,0x1b216bf0,0x1bc90000)
from space 1792K, 0% used [0x1bc90000,0x1bc90000,0x1be50000)
to space 1792K, 0% used [0x1be50000,0x1be50000,0x1c010000)
PSOldGen total 116544K, used 137K [0x14010000, 0x1b1e0000, 0x1b1e0000)
object space 116544K, 0% used [0x14010000,0x140325e8,0x1b1e0000)
PSPermGen total 16384K, used 2315K [0x10010000, 0x11010000, 0x14010000)
object space 16384K, 14% used [0x10010000,0x10252f90,0x11010000)

D:\test>java -server -Xmx128m -Xms128m -XX:+PrintCompilation -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseSerialGC Test
VM option '+PrintCompilation'
VM option '+PrintGC'
VM option '+PrintGCDetails'
VM option '+UseSerialGC'
1 java.lang.String::charAt (33 bytes)
2 Test::fillHeap [b](12 bytes)[/b]
1% Test::main @ 2 (26 bytes)
2 made not entrant (2) Test::fillHeap (12 bytes)
[Full GC (System) [Tenured: [b]65536K->137K[/b](116544K), 0.0755938 secs] 67410K->137K(129664K), [Perm : 2286K->2286K(16384K)], 0.0761244 secs] [Times: user=0.08 sys=0.00, real=0.08 secs]
Heap
def new generation total 13120K, used 234K [0x10010000, 0x10e40000, 0x10e40000)
eden space 11712K, 2% used [0x10010000, 0x1004a960, 0x10b80000)
from space 1408K, 0% used [0x10b80000, 0x10b80000, 0x10ce0000)
to space 1408K, 0% used [0x10ce0000, 0x10ce0000, 0x10e40000)
tenured generation total 116544K, used 137K [0x10e40000, 0x18010000, 0x18010000)
the space 116544K, 0% used [0x10e40000, 0x10e625e8, 0x10e62600, 0x18010000)

compacting perm gen total 16384K, used 2315K [0x18010000, 0x19010000, 0x1c010000)
the space 16384K, 14% used [0x18010000, 0x18252f90, 0x18253000, 0x19010000)
No shared spaces configured.

看到的结果跟大大的不一样。那个64M的byte数组能看到被收集了。打上-XX:+PrintGCDetails看到Full GC (System)的是System.gc()的日志。[/quote]

九楼:
[quote="IcyFenix"]hsdis-i386.dll我找了一圈,没想到JE就有 @_@

加上-XX:+PrintAssembly可以看到jit之后赋null的操作确实消除了,那64m内存在jit之后没有被收集掉,是因为挂着jdwp agent的关系,不挂这个agentlib就可以正常回收掉。用eclipse debug环境跑确实是个低级错误。

在命令行重新测试了一次,对于没有进入jit的代码,那64m如果没有赋null的话不会被回收,进入了jit之后,赋不赋null都会被那句System.gc()回收。

感谢楼上的bugmenot同学。[/quote]

悲剧完毕,over。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值