前言
之前看书的时候,看到了方法执行的内容,忽然就想到了这么一个有趣的东西.然后就特意开一个贴,把一些前人,大大的知识做一个汇总,做一下记录吧.
正文
相信,网上很多java性能优化的帖子里都会有这么一条
可以明确的说,这个观点是基本错误的.sun jdk远比我们想象中的机智.完全能判断出对象是否已经no ref..但是,我上面用的词是"基本".也就是说,有例外的情况.这里先把这个例外情况给提出来,后续我会一点点解释.这个例外的情况是, 方法前面中有定义大的对象,然后又跟着非常耗时的操作,且没有触发JIT编译..总结这句话,就是
上面这句话有点绕,但是,上面说的每一个条件都是有意义的.这些条件分别是
2 定义了一个大对象(小对象没有意义)
3 之后跟着一个非常耗时的操作.
4 没有满足JIT编译条件
上面4个条件缺一不可,把obj显式设置成null才是有意义的. 下面我会一一解释上面的这些条件
在解释上面的条件之前,简略的说一下一些基础知识.
(1)sun jdk的内存垃圾判定,是基于根搜索算法的.也就是说,在GC root为跟,能被搜索到的,就认为是存活对象,搜索不到的,则认为是"垃圾".
(2)GC root 里和我们这篇文章有关的gc root是这一条
Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.
这句话直接翻译就是说是"本地变量,例如方法的参数或者方法中创建的局部变量".如果换一种说法是,
下面开始说四大条件. 我们测试是否被垃圾回收的方法是,申请一个64M的byte数组(作为大对象),然后调用System.gc();.运行的时候用 -verbose:gc 观察回收情况来判定是否会回收.
同一个方法中
这个条件是最容易理解的,如果大对象定义在其他方法中,那么是不需要设置成Null的,
public class Test { public static void main(String[] args){ foo(); System.gc(); } public static void foo(){ byte[] placeholder = new byte[64*1024*1024]; } }
对应的输出如下,可以看到64M的内存已经被回收
[GC 66798K->66120K(120960K), 0.0012225 secs]
[Full GC 66120K->481K(120960K), 0.0059647 secs]
其实很好理解,placeholder是foo方法的局部变量,在main方法中调用的时候,其实foo方法对应的栈帧已经结束.那么placeholder指向的大对象自然被gc的时候回收了.
定义了一个大对象
这句话的意思也很好理解.只有定义的是大的对象,我们才需要关心他尽快被回收.如果你只是定义了一个 String str = "abc"; 后续手动设置成null让gc回收是没有任何意义的.
后面跟着一个非常耗时的操作
这里理解是:后面的这个耗时的可能超过了一个GC的周期.例如
public static void main(String[] args) throws Exception{ byte[] placeholder = new byte[64*1024*1024]; Thread.sleep(3000l); // dosomething }
在线程sleep的三秒内,可能jvm已经进行了好几次ygc.但是由于placeholder一直持有这个大对象,所以造成这个64M的大对象一直无法被回收,甚至有可能造成了满足进入old 区的条件.这个时候,在sleep之前,显式得把placeholder设置成Null是有意义的. 但是,
没有满足JIT编译条件
jit编译的触发条件,这里就不多阐述了.对应的测试代码和前面一样
public class Test { public static void main(String[] args) throws Exception{ byte[] placeholder = new byte[64*1024*1024]; placeholder = null; //do some time-consuming operation System.gc(); } }
在解释执行中,我们认为
是有助于对这个大对象的回收的.在JIT编译下,我们可以通过强制执行编译执行,然后打印出对应的 ASM码的方式查看. 安装fast_debug版本的jdk请查看
使用-XX:+PrintAssembly打印asm代码遇到的问题
命令是
Code:
[Disassembling for mach='i386']
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'main' '([Ljava/lang/String;)V' in 'Test'
# parm0: ecx = '[Ljava/lang/String;'
# [sp+0x20] (sp of caller)
;; block B1 [0, 0]
0x0267f2d0: mov %eax,-0x8000(%esp)
0x0267f2d7: push %ebp
0x0267f2d8: sub $0x18,%esp ;*ldc ; - Test::main@0 (line 7)
;; block B0 [0, 10]
0x0267f2db: mov $0x4000000,%ebx
0x0267f2e0: mov $0x20010850,%edx ; {oop({type array byte})}
0x0267f2e5: mov %ebx,%edi
0x0267f2e7: cmp $0xffffff,%ebx
0x0267f2ed: ja 0x0267f37f
0x0267f2f3: mov $0x13,%esi
0x0267f2f8: lea (%esi,%ebx,1),%esi
0x0267f2fb: and $0xfffffff8,%esi
0x0267f2fe: mov %fs:0x0(,%eiz,1),%ecx
0x0267f306: mov -0xc(%ecx),%ecx
0x0267f309: mov 0x44(%ecx),%eax
0x0267f30c: lea (%eax,%esi,1),%esi
0x0267f30f: cmp 0x4c(%ecx),%esi
0x0267f312: ja 0x0267f37f
0x0267f318: mov %esi,0x44(%ecx)
0x0267f31b: sub %eax,%esi
0x0267f31d: movl $0x1,(%eax)
0x0267f323: mov %edx,0x4(%eax)
0x0267f326: mov %ebx,0x8(%eax)
0x0267f329: sub $0xc,%esi
0x0267f32c: je 0x0267f36f
0x0267f332: test $0x3,%esi
0x0267f338: je 0x0267f34f
0x0267f33e: push $0x844ef48 ; {external_word}
0x0267f343: call 0x0267f348
0x0267f348: pusha
0x0267f349: call 0x0822c2e0 ; {runtime_call}
0x0267f34e: hlt
0x0267f34f: xor %ebx,%ebx
0x0267f351: shr $0x3,%esi
0x0267f354: jae 0x0267f364
0x0267f35a: mov %ebx,0xc(%eax,%esi,8)
0x0267f35e: je 0x0267f36f
0x0267f364: mov %ebx,0x8(%eax,%esi,8)
0x0267f368: mov %ebx,0x4(%eax,%esi,8)
0x0267f36c: dec %esi
0x0267f36d: jne 0x0267f364 ;*newarray
; - Test::main@2 (line 7)
0x0267f36f: call 0x025bb450 ; OopMap{off=164}
;*invokestatic gc
; - Test::main@7 (line 10)
; {static_call}
0x0267f374: add $0x18,%esp
0x0267f377: pop %ebp
0x0267f378: test %eax,0x370100 ; {poll_return}
0x0267f37e: ret
;; NewTypeArrayStub slow case
0x0267f37f: call 0x025f91d0 ; OopMap{off=180}
;*newarray
; - Test::main@2 (line 7)
; {runtime_call}
0x0267f384: jmp 0x0267f36f
0x0267f386: nop
0x0267f387: nop
;; Unwind handler
0x0267f388: mov %fs:0x0(,%eiz,1),%esi
0x0267f390: mov -0xc(%esi),%esi
0x0267f393: mov 0x198(%esi),%eax
0x0267f399: movl $0x0,0x198(%esi)
0x0267f3a3: movl $0x0,0x19c(%esi)
0x0267f3ad: add $0x18,%esp
0x0267f3b0: pop %ebp
0x0267f3b1: jmp 0x025f7be0 ; {runtime_call}
0x0267f3b6: hlt
0x0267f3b7: hlt
0x0267f3b8: hlt
0x0267f3b9: hlt
0x0267f3ba: hlt
0x0267f3bb: hlt
0x0267f3bc: hlt
0x0267f3bd: hlt
0x0267f3be: hlt
0x0267f3bf: hlt
[Stub Code]
0x0267f3c0: nop ; {no_reloc}
0x0267f3c1: nop
0x0267f3c2: mov $0x0,%ebx ; {static_stub}
0x0267f3c7: jmp 0x0267f3c7 ; {runtime_call}
[Exception Handler]
0x0267f3cc: mov $0xdead,%ebx
0x0267f3d1: mov $0xdead,%ecx
0x0267f3d6: mov $0xdead,%esi
0x0267f3db: mov $0xdead,%edi
0x0267f3e0: call 0x025f9c40 ; {runtime_call}
0x0267f3e5: push $0x83c8bc0 ; {external_word}
0x0267f3ea: call 0x0267f3ef
0x0267f3ef: pusha
0x0267f3f0: call 0x0822c2e0 ; {runtime_call}
0x0267f3f5: hlt
[Deopt Handler Code]
0x0267f3f6: push $0x267f3f6 ; {section_word}
0x0267f3fb: jmp 0x025bbac0 ; {runtime_call}
可以看到, placeholder = null; 这个语句被消除了! 也就是说,对于JIT编译以后的来说,压根不需要这个语句!
所以说,如果是解释执行的情况下,显式设置成Null是没有任何必要的!
到这里,基本已经把文章开头说的那个论断给说明清楚了.但是,在文章的结尾,补充一下局部变量表会对内存回收有什么影响.这个例子参照<深入理解Java虚拟机:JVM高级特性与最佳实践> 一书
我们认为
public class Test { public static void main(String[] args) throws Exception{ byte[] placeholder = new byte[64*1024*1024]; //do some time-consuming operation System.gc(); } }
这样的情况下,placeholder的对象是不会被回收的.可以理解..然后我们继续修改方法体
public class Test { public static void main(String[] args) throws Exception{ { byte[] placeholder = new byte[64*1024*1024]; } System.gc(); } }
我们运行发现
[GC 66798K->66072K(120960K), 0.0021019 secs]
[Full GC 66072K->66017K(120960K), 0.0069085 secs]
垃圾收集器并不会把对象给回收..明明已经出了作用域,竟然还是不回收!. 好吧,继续修改例子
public class Test { public static void main(String[] args) throws Exception{ { byte[] placeholder = new byte[64*1024*1024]; } int a = 0; System.gc(); } }
唯一的变化就是新增了一个 int a = 0; 继续看效果
[GC 66798K->66144K(120960K), 0.0011617 secs]
[Full GC 66144K->481K(120960K), 0.0060882 secs]
可以看到,大对象被回收了..这是一个神奇的例子..能想到这个,我对书的作者万分佩服! 但是这个例子的解释,在书中的解释有点泛(至少我刚开始没看懂),所以这里就仔细说明一下.
要解释这个,先大概看一下 Java执行机制 里面局部变量表的部分.
上面的这段话有点抽象,后面一个个解释.其实方法的局部变量表大小在javac的时候就已经确定了.
在class文件中,方法体对应的Code属性中就有对应的Locals属性,就是来记录局部变量表的大小的.例子如下:
public class Test { public void foo(int a,int b){ int c = 0; return; } }
通过 javac -g:vars Test 编译,然后,通过javap -verbose 查看
Code:
Stack=1, Locals=4, Args_size=3
0: iconst_0
1: istore_3
2: return
LocalVariableTable:
Start Length Slot Name Signature
0 3 0 this LTest;
0 3 1 a I
0 3 2 b I
2 1 3 c I
可以看到,局部变量表的Slot数量是4个.分别是 this,a,b,c ..这个非常好理解.那么,什么叫做Slot的复用呢,继续看例子
public class Test { public void foo(int a,int b){ { int d = 0; } int c = 0; return; } }
在 int c = 0;之前新增一个作用域,里面定义了一个局部变量.如果没有slot复用机制,那么,理论上说,这个方法中局部变量表的slot个数应该是5个,但是,看具体的javap 输出
Code:
Stack=1, Locals=4, Args_size=3
0: iconst_0
1: istore_3
2: iconst_0
3: istore_3
4: return
LocalVariableTable:
Start Length Slot Name Signature
2 0 3 d I
0 5 0 this LTest;
0 5 1 a I
0 5 2 b I
4 1 3 c I
可以看到,对应的locals=4 ,也就是对应的slot个数还是4个. 通过查看对应的LocalVariableTable属性,可以看到,局部变量d和c都是在Slot[3]中. 这就是上面说的,在某个作用域结束以后,里面的对应的slot并没有马上消除,而是继续留着给下面的局部变量使用..按照这样理解,
public class Test { public static void main(String[] args) throws Exception{ { byte[] placeholder = new byte[64*1024*1024]; } System.gc(); } }
这个例子中,在执行System.gc()的时候,虽然placeholder 的作用域已经结束,但是placeholder 对应的slot还存在,继续持有64M数组这个大对象,那么自然的,在GC的时候不会把对应的大对象给清理掉.而在
public class Test { public static void main(String[] args) throws Exception{ { byte[] placeholder = new byte[64*1024*1024]; } int a = 0; System.gc(); } }
这个例子中,在System.gc的时候,placeholder对应的slot已经被a给占用了,那么对应的大对象就变成了无根的"垃圾",当然会被清楚.这一点,可以通过javap明显的看到
Code:
Stack=1, Locals=2, Args_size=1
0: ldc #2; //int 67108864
2: newarray byte
4: astore_1
5: iconst_0
6: istore_1
7: invokestatic #3; //Method java/lang/System.gc:()V
10: return
LocalVariableTable:
Start Length Slot Name Signature
5 0 1 placeholder [B
0 11 0 args [Ljava/lang/String;
7 4 1 a I
Exceptions:
throws java.lang.Exception
}
可以看到,placeholder 和 a 都对应于Slot[1].
这个例子说明的差不多了,在上面的基础上,再多一个例子
public class Test { public static void main(String[] args) throws Exception{ { int b = 0; byte[] placeholder = new byte[64*1024*1024]; } int a = 0; System.gc(); } }
这个代码中,这个64M的大对象会被GC回收吗..
参考文章:
http://icyfenix.iteye.com/blog/900737
http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html
<深入理解Java虚拟机:JVM高级特性与最佳实践> 周志明的书
相关推荐
### 高性能JAVA开发之内存管理 在Java开发过程中,内存管理是...在实际开发中,开发者应该尽量减少不必要的对象创建,合理利用现有的对象资源,同时关注垃圾回收机制,以便更好地优化内存使用,提高程序的整体性能。
* GC是指垃圾回收机制,用于释放对象占用的资源 * Java中提供了多种GC机制,例如:`System.gc()`方法等 JDBC-ODBC * JDBC是指Java Database Connectivity,用于连接数据库 * ODBC是指Open Database Connectivity,...
JAVA GC (Garbage Collection) 是Java编程语言中的一个重要特性,它自动管理程序的内存,确保不再使用的对象能够被及时回收,以防止内存泄漏。GC主要针对的是Java堆内存中的对象,这里的对象是由Java栈中的引用指向...
- **MarkBits**: 在垃圾回收过程中用于标记可回收对象的位图。这两个位图对于内存管理至关重要,它们的大小通常是堆内存大小的六十四分之一。例如,在MIUI中,这两个位图各占1MB的内存。 ##### 4. 内存管理实现 ...
- **定义**:Java 虚拟机,是运行 Java 字节码的虚拟机,它提供了运行时环境的核心服务,如垃圾回收、异常处理等。 - **用途**:用于执行 Java 字节码。 #### 四、`==` 与 `equals()` 方法的区别 1. **`==` 操作...
在上面的代码中,obj 是一个强引用,它不会被垃圾回收器回收,直到手动将其设置为 null。 软引用(Soft Reference) 软引用是一种比较特殊的引用类型,它会在 JVM 报告内存不足的时候被垃圾回收器回收,否则不会被...
### Java岗位常规面试题知识点详解 ...以上是对给定文件中提出的部分问题进行了详细的解答,涵盖了Java面试中常见的知识点。这些知识点对于深入理解Java语言特性及其在实际开发中的应用至关重要。
- **含义**: 代码创建了一个异常对象,但没有实际抛出这个异常,而是让其被垃圾回收器回收。 - **示例**: ```java if (x ) { new IllegalArgumentException("x must be non-negative"); } ``` 这段代码创建...
- **答案与解析:** 在这段代码中,使由`obj1`引用的对象成为垃圾回收的候选的关键行是`obj1 = null;`。当执行到这一行时,`obj1`引用的`Integer`对象就不再有任何强引用指向它,因此它成为了垃圾回收的候选。值得...
Java 数组类型 Java 数组类型是 Java 语言中的一种基本数据类型,用于存储多个相同类型的值。...Java 中有一个垃圾回收机制,用于处理内存回收的事宜。垃圾回收机制可以自动释放不再使用的对象所占据的内存空间。
import java.awt.Graphics; import java.awt.image.BufferedImage; public abstract class FlyingObject { protected double x;//物体的x坐标 protected double y;//物体的y坐标 protected double width;//物体...
Java提供了一种垃圾回收机制(Garbage Collection),用于自动回收不再使用的内存,以避免内存泄漏。 创建对象时,所有的对象都是在堆中创建的。例如,`Object obj1 = new Object();`这条语句会首先在堆中分配空间...
1. **减少创建与销毁开销**:频繁地创建和销毁对象会导致大量的CPU时间消耗在内存分配和垃圾回收上,而对象池能够有效减少这种开销。 2. **节省内存资源**:对象池限制了对象的最大数量,避免了大量临时对象占用内存...
使用强指针时,开发者无需手动释放对象内存,这一点与Java的垃圾回收机制类似。强指针定义和使用方式类似普通指针,但需要注意的是,不能将普通指针定义为智能指针的指针(例如错误的定义sp*),应该直接定义为智能...
对于 JVM,面试者需要了解内存模型、垃圾回收机制、性能调优、类加载过程等。例如,如何理解和分析内存泄漏,以及如何通过 JMX、VisualVM 等工具监控和诊断 JVM 性能问题。 总的来说,这份200+的面试题汇总涵盖了 ...
* 匿名对象的生命周期很短,一旦创建后就会被垃圾回收。 NullPointerException * NullPointerException是当null引用变量尝试访问对象的成员时抛出的异常。 * 例如,`Object obj = null; obj.toString();` 将抛出...
- **finalize方法**: 在对象即将被垃圾回收器回收之前调用。此方法主要用于执行清理工作,例如关闭文件等资源。然而,现代Java编程中通常推荐使用其他机制如try-with-resources来管理资源。 - **toString方法**: ...
- **垃圾回收机制**:Java的垃圾回收机制自动管理内存,回收不再使用的对象所占用的内存空间。当一个对象没有引用指向它时,这个对象就会成为垃圾回收的目标。 示例: ```java // 垃圾回收示例 public class ...