明确GC roots
我们知道,java中决定一个对象是不是会被回收要看它是不是还被gc root引用,gc root分为以下几种:
- 局部变量
- 静态成员变量引用的对象
- 本地方法栈JNI本地方法引用的对象
- 方法区中的常量对象引用的对象
局部变量在Java内存中的存储模型
对于“局部变量是gc root”这个说法,准确说是当Java虚拟机栈、也就是线程栈,栈中的栈帧内的局部变量表引用了该局部变量,那么被这个局部变量引用的对象就是gc root。这很容易理解,这时候说明栈帧对应的方法正在调用,所以不能回收方法正在使用的局部变量。
一旦栈帧弹出,意味着方法调用完毕已返回,那么没有局部变量表引用该局部变量了,所以该局部变量对应的对象也就成为了垃圾对象,可以被回收。
需要注意的一个事实是,栈帧中的局部变量表登记着该栈帧对应方法内使用的局部变量,局部变量表是在编译阶段就确定的,此时也为变量表中的局部变量分配了内存空间。局部变量的内存是分配在栈空间的,同样类型的变量占用的栈空间都是一样大的。局部变量是基本类型则真值就是存储在该变量分配的栈空间。如果是引用类型、比如是个bean,那么局部变量栈空间存储的只是个引用,指向堆空间中的对象的真值。
我们来看个例子:
//看起来会无法垃圾回收,产生OOM的写法
while(true){
Car lancer = new Lancer();
System.gc();
}
- “不断循环生成新的对象,且局部变量lancer与新对象存在强引用关系,且由于循环的关系栈帧方法没返回、栈帧不弹出,所以这些大量对象无法被gc回收,最终上面代码会产生heap out of memory问题。” 果真如此吗?
假设上面代码是在一个方法内执行,以上看起来会出问题的代码理论上其实并不会产生OOM,原因在于循环体里边每次new Lancer()生成一个对象,然后局部变量Car lancer指向这个对象。但由于局部变量是在编译阶段分配的内存空间,方法执行过程中对应的栈帧中的局部变量表中,只有lancer这一个局部变量。上面程序相当于反复循环生成新的对象给这一个局部变量赋值。当新的循环生成新Lancer对象赋值给lancer变量时,之前生成的Lancer对象就会失去gc root引用,成为可回收对象,将在System.gc()时被垃圾回收。
也就是说下面这种看起来比较“性能优化”的写法,实际上跟上面的写法作用是一样的。没任何区别。而且从局部变量作用域来说,循环体内使用的局部变量就应该定义在循环体里边。
//"优化"写法
Car lancer = null;
while(true){
lancer = new Lancer();
System.gc();
}
网友评论