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

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

	// 测试内存溢出错误:程序须要的内存超过了最大的堆内存配置
	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串所需的存储空间以指数级别增加,因而没多久便撑爆了程序所能用到的内存了。java

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

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

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

执行测试代码中的testUnlimitedRecursion方法,结果仍是很快就报错退出了,错误信息为“java.lang.StackOverflowError”,意思是栈溢出。但是第二个例子在递归调用中并未拼接字符串,为啥仍旧出现溢出错误了呢?这是由于程序在运行时会申请两块内存空间,一块叫堆内存,另外一块叫栈内存;其中堆内存承包了程序运行所需的大部分存储需求,包括变量、数组、对象实例等等;而栈内存仅仅负责保管每次方法调用的现场数据,包括方法自身、方法的输入参数、方法内部的基本变量等等,并在方法调用结束时释放该方法占用的内存空间。前述的第一个例子,它的内存溢出发生于堆内存;至于后面的第二个例子,它的内存溢出发生于栈内存。数组

那么为何方法调用的有关数据放在栈内存而不是堆内存呢?举个现实生活中的例子,假设一对小夫妻带着宝宝回家过年,随身携带的物品都放在行李箱里,则行李箱就是属于他们的堆内存。而后一家三口准备坐动车回来,在路上还得处理一些事情,每件事情都至关于一次方法调用。例如在车站买车票,用手掏出钱包,抽出人民币付款买完车票,再把钱包塞回去。在这个买车票的方法中,输入参数是钱包,输出参数是车票,而手充当了栈内存的角色。买票以前,两手空空;买票的过程当中,一只手抓着钱包;买完票后,钱包塞回去,两手又变空了。打电话也可看做是方法调用,打电话前,两手空空;打电话的时候,一只手握住手机通话;打完电话,收好手机,两手依然空空。此时双手属于分配给他们的栈内存,因为有两只手,所以栈内存的大小为二,即最多同时办理两件事情。
一家三口检票上车,女人有事走开了一下子,这时宝宝饿得大哭,男人赶忙泡奶给宝宝喂。只见这个奶爸先用左手抱着宝宝,再用右手扶着奶瓶,至关于喂奶事件拥有方法嵌套,外层的喂奶方法占用了左手这块栈内存,内层的扶奶瓶方法又占用了右手这块栈内存。宝宝还在喝奶的时候,苦逼的奶爸突然内急,因而抱着宝宝一边喂奶一边飞奔至厕所,站在马桶面前准备小便,猛然发现两只手都在忙,没法解手。只有等宝宝喝完奶,右手把奶瓶放旁边,这样空出来的右手才能帮忙方便。可是宝宝喝奶的方法还没结束调用,上厕所的方法已经等不及了,怎么办怎么办?可怜的奶爸情急之下只好尿裤子了,对程序来讲便发生了栈内存溢出。要么有个路人伸出援手(栈内存大小加一),一把扯下奶爸的裤子,方能避免尿裤子的尴尬(栈溢出的错误)。
总结一下,凡是编码问题形成的程序崩溃,都归类为异常Exception;凡是系统不堪重负形成的程序崩溃,都归类为错误Error。不过异常与错误仅是分类上的区别,实际开发中,两者的扔出和捕捉操做无甚差异,因此若没有特殊状况,从此将使用“异常”一词统称异常与错误。app



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

相关文章
相关标签/搜索