Java开发笔记(七十四)内存溢出的两种错误

前面介绍的几种异常,其实都存在这样那样的逻辑问题,属于程序员的编码手误。还有一大类系统错误,表面上看不出什么问题,但是程序仍然运行不下去,兹举二例说明。
第一个例子且看下列的测试代码:

// 测试内存溢出错误:程序需要的内存超过了最大的堆内存配置
private static void testUnlimitedString() {
	String str = "Hello world";
	String result = getUnlimitedString(str); // 获取无限大小的字符串
	System.out.println("result="+result.toString());
}

// 获取无限大小的字符串
private static String getUnlimitedString(String str) {
	System.out.println("getUnlimitedString");
	String append = String.format("%s+%s", str, str);
	return getUnlimitedString(append);
}

执行测试代码中的testUnlimitedString方法,一开始程序正常打印日志,然而不一会儿就报错退出了,错误信息为“java.lang.OutOfMemoryError: Java heap space”,意思是内存溢出。仔细阅读测试代码,发现其中的getUnlimitedString方法会调用自身,从而形成了递归调用。要命的是,方法递归的同时不断拼接更长的字符串append;而这意味着,每次递归调用之后,新的append串长度都要翻番;经过多次调用,append串所需的存储空间以指数级别增长,于是没多久便撑爆了程序所能用到的内存了。

第二个例子依旧先看下面的下面的测试代码:

// 测试栈溢出错误:程序占用的栈空间超过了配置的栈内存大小
private static void testUnlimitedRecursion() {
	recursionAction(); // 用于递归动作的方法
}

// 用于递归动作的方法
public static void recursionAction() {
	System.out.println("recursionAction");
	recursionAction();
}

执行测试代码中的testUnlimitedRecursion方法,结果还是很快就报错退出了,错误信息为“java.lang.StackOverflowError”,意思是栈溢出。可是第二个例子在递归调用中并未拼接字符串,为啥仍旧出现溢出错误了呢?这是因为程序在运行时会申请两块内存空间,一块叫堆内存,另一块叫栈内存;其中堆内存承包了程序运行所需的大部分存储需求,包括变量、数组、对象实例等等;而栈内存仅仅负责保管每次方法调用的现场数据,包括方法自身、方法的输入参数、方法内部的基本变量等等,并在方法调用结束时释放该方法占用的内存空间。前述的第一个例子,它的内存溢出发生于堆内存;至于后面的第二个例子,它的内存溢出发生于栈内存。
那么为什么方法调用的有关数据放在栈内存而不是堆内存呢?举个现实生活中的例子,假设一对小夫妻带着宝宝回家过年,随身携带的物品都放在行李箱里,则行李箱就是属于他们的堆内存。然后一家三口准备坐动车回来,在路上还得处理一些事情,每件事情都相当于一次方法调用。例如在车站买车票,用手掏出钱包,抽出人民币付款买完车票,再把钱包塞回去。在这个买车票的方法中,输入参数是钱包,输出参数是车票,而手充当了栈内存的角色。买票之前,两手空空;买票的过程中,一只手抓着钱包;买完票后,钱包塞回去,两手又变空了。打电话也可看作是方法调用,打电话前,两手空空;打电话的时候,一只手握住手机通话;打完电话,收好手机,两手依然空空。此时双手属于分配给他们的栈内存,由于有两只手,因此栈内存的大小为二,即最多同时办理两件事情。
一家三口检票上车,女人有事走开了一会儿,这时宝宝饿得大哭,男人赶紧泡奶给宝宝喂。只见这个奶爸先用左手抱着宝宝,再用右手扶着奶瓶,相当于喂奶事件拥有方法嵌套,外层的喂奶方法占用了左手这块栈内存,内层的扶奶瓶方法又占用了右手这块栈内存。宝宝还在喝奶的时候,苦逼的奶爸忽然内急,于是抱着宝宝一边喂奶一边飞奔至厕所,站在马桶面前准备小便,猛然发现两只手都在忙,无法解手。只有等宝宝喝完奶,右手把奶瓶放旁边,这样空出来的右手才能帮忙方便。但是宝宝喝奶的方法还没结束调用,上厕所的方法已经等不及了,怎么办怎么办?可怜的奶爸情急之下只好尿裤子了,对程序来说便发生了栈内存溢出。要么有个路人伸出援手(栈内存大小加一),一把扯下奶爸的裤子,方能避免尿裤子的尴尬(栈溢出的错误)。
总结一下,凡是编码问题造成的程序崩溃,都归类为异常Exception;凡是系统不堪重负造成的程序崩溃,都归类为错误Error。不过异常与错误仅是分类上的区别,实际开发中,二者的扔出和捕捉操作无甚差别,所以若没有特殊情况,今后将使用“异常”一词统称异常与错误。

更多Java技术文章参见《Java开发笔记(序)章节目录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值