一个很有趣的Java Memory Puzzle


这篇文章描述一段很有趣的内存溢出现象。



下面的代码会抛出OutOfMemoryError:


public class JavaMemoryPuzzle {

	private final int dataSize = (int)(Runtime.getRuntime().maxMemory() * 0.6);
	
	public void foo(){
		{
			byte[] data = new byte[dataSize];
		}
		
		byte[] data1 = new byte[dataSize];
	}
	
	public static void main(String[] args){
		JavaMemoryPuzzle puzzle = new JavaMemoryPuzzle();
		
		puzzle.foo();
	}
}

  

而下面的代码就不会:


public class JavaMemoryPuzzle {

	private final int dataSize = (int)(Runtime.getRuntime().maxMemory() * 0.6);
	
	public void foo(){
		{
			byte[] data = new byte[dataSize];
		}
		
		String msg = "Hello";
		
		byte[] data1 = new byte[dataSize];
	}
	
	public static void main(String[] args){
		JavaMemoryPuzzle puzzle = new JavaMemoryPuzzle();
		
		puzzle.foo();
	}
}

  

区别在哪里?


我们只在第一个版本的foo方法的内部block块后下加了


String msg = "Hello";


很明显,这段代码的加入促使JVM在实例化data1时发生了一次垃圾收集,为何?


利用javap -verbose JavaMemoryPuzzle,我们来看第一版本的foo方法的汇编代码:


0:   aload_0
1:   getfield        #24; //Field dataSize:I
4:   newarray byte
6:   astore_1
7:   aload_0
8:   getfield        #24; //Field dataSize:I
11:  newarray byte
13:  astore_1
14:  return

 

第0行将this放置到操作数栈顶部,第1行弹出this并从this的实例堆内存中加载dataSize数据到操作数栈顶部,第4行生成数组,将数组的指针放到操作数栈顶部,第6行从操作数栈顶部弹出刚刚生成的数组指针并将之放到第2个栈变量。接下来在11行再次生成一个数组,这个数组很大,以至于JVM无法分配足够的内存来实例化,抛出OutOfMemoryError。为何在第二次生成很大的数组时,JVM没有回收data的空间,毕竟data在方法块内。原来,按照这段汇编代码,第二次生成数组时,data的应用还在栈的第二个变量位置处保存,data还是一个强引用变量,JVM当然不去回收他。


从生成的汇编可以看到,版本1的代码和下面的代码等价:


byte[] data = new byte[dataSize];
		
data = new byte[dataSize];

  

我们再来看版本2的汇编:


 0:   aload_0
 1:   getfield        #24; //Field dataSize:I
 4:   newarray byte
 6:   astore_1
 7:   ldc     #31; //String Hello
 9:   astore_1
 10:  aload_0
 11:  getfield        #24; //Field dataSize:I
 14:  newarray byte
 16:  astore_2
 17:  return

 

秘密就爱第7行和第9行。第7行从常量池加载"Hello"到操作数栈,第9行清空操作数栈,将栈顶部的"Hello"赋值给第二个栈变量,就是这次赋值,使data应用的一片内存区域变成了孤魂野鬼,第14行再次生成一个大数组时,JVM回收了原来data引用的那一大片数组内存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值