java中方法的局部变量是放在虚拟机栈的局部变量表里面:
- public static void main(String[] args) {
- byte[] waste = new byte[6 * 1024 * 1024];
- int new_var = 0;
- System.gc();
- }
public static void main(String[] args) {
byte[] waste = new byte[6 * 1024 * 1024];
int new_var = 0;
System.gc();
}
上面的代码反编译后得到:
- public static void main(java.lang.String[]);
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=1, locals=3, args_size=1
- 0: ldc #2 // int 6291456
- 2: newarray byte
- 4: astore_1
- 5: iconst_0
- 6: istore_2
- 7: invokestatic #3 // Method java/lang/System.gc:()V
- 10: return
- LineNumberTable:
- line 3: 0
- line 4: 5
- line 5: 7
- line 6: 10
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: ldc #2 // int 6291456
2: newarray byte
4: astore_1
5: iconst_0
6: istore_2
7: invokestatic #3 // Method java/lang/System.gc:()V
10: return
LineNumberTable:
line 3: 0
line 4: 5
line 5: 7
line 6: 10
可以看到locals=3,也就是说局部变量表长度是3,有man函数的参数,waste变量和new_var三个变量。
在JVM参数:-verbose:gc -Xmx12m -Xms12m 下GC日志:
[GC 6514K->6448K(11776K), 0.0011241 secs]
[Full GC 6448K->6356K(11776K), 0.0070312 secs]
这个看起来很正常,局部变量表引用了waste和new_var,所以他们的内存没有被释放。
我的疑问是,在执行System.gc()之前,waste变量的作用范围已经过期了,从int new_var = 0开始,后面就再也没有用到waste,那gc为什么不能回收waste的空间(虽然局部变量表保存了waste的引用)。
其实这个在《深入理解java虚拟机》上面有详细的讲解p202页。
如果你把方法体写成这样就会回收了:
{
byte b = new byte[6 * 1024 * 1024];
}
int a = 0;
这样垃圾回收器就会马上回收。
原因:局部变量表中的slot是可重用的,方法体中定义的变量,其作用域不一定会覆盖整个方法体,如果当前字节码的PC计数器的值已经超出了整个变量的作用域,那么这个变量对应的slot既可以交给其它的变量使用。
对照上面的代码int a = 0;变量a的定义已经超过了b的作用范围,所以a会重新使用b的slot(这个slot指的是局部变量表的基本单位),这时执行垃圾回收就会回收b所占用的空间。
byte[] waste = new byte[6 * 1024 * 1024];
int new_var = 0;
System.gc();
当你声明了wate数组后,再执行System.gc,即如下代码:
byte[] waste = new byte[6 * 1024 * 1024];
System.gc();
虚拟机是不会回收掉waste数组的,按理说都虚拟机都退出了,为什么没有清除waste呢?
那是因为在执行gc前,waste还在当前main方法的作用域中,虚拟机是不敢贸然回收waste的
如果在waste后加上 int new_var = 0; 这句话,还是没有执行回收内存呢?这里提出一个概念:
局部变量表中的solt槽在局部方法中不同的作用域范围是可以重用的。
注意要在不同的作用域内是可重用的,也就是说如下代码是不可重用局部变量solt的
byte[] waste = new byte[6 * 1024 * 1024];
int new_var = 0;
而这个片段是可以重用局部变量solt的
{
byte[] waste = new byte[6 * 1024 * 1024];
}
int new_var = 0;
因为waste和new_var不在同一个作用域,对于上述代码,我们知道对于静态的main方法,局部变量表有3个solt
solt0 对应String[]参数
solt1 对应waste参数
而new_var 对应solt1,即solt重用了,当然waste在局部变量表中编程了GCRoots不可达的状态,这个时候执行gc
肯定就回收了waste占用的堆内存,相反,对于代码
byte[] waste = new byte[6 * 1024 * 1024];
int new_var = 0; new_var并没有重用waste的solt,这个时候局部变量表保持着对waste的引用,gc当然不会回收