Java虚拟机OOM之虚拟机栈和本地方法栈溢出(4)
一、在 Java 虚拟机规范中,对虚拟机栈这个区域规定了两种异常状况:
(1)如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;
(2)如果虚拟机栈可以动态扩展(当前大部分的 Java 虚拟机都可动态扩展,只不过 Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。
(3)与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和OutOfMemoryError 异常。
二、前一篇文章中的第一张图也看出来了对于虚拟机栈和本地方法栈 如果在多线程的情况下是可能出现OOM的,下边就是一个单线程的案例。演示一下:
要设置VM Args: -Xss128k(上一篇已经说到:设置栈为128k),结果如下:
stack length:40550Exception in thread "main" java.lang.StackOverflowError at com.lc.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:8) 后续很多异常省略。。。
结果表明:在单个线程下,无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是 StackOverflowError 异常,而不是OOM。
三、如果测试时不限于单线程,通过不断地建立线程的方式倒是可以产生内存溢出异常。自己可以创建多个线程,进行测试,但是:由于在Windows 平台的虚拟机中, Java 的线程是映射到操作系统的内核线程上的,所以多线程代码执行时有较大的风险,可能会导致操作系统假死。所以。。。
不过结果肯定是,多线程的情况下,是会出现OOM的!
原理如下:
为什吗多线程的情况下就会产生OOM,这样产生的内存溢出异常与栈空间是否足够大并不存在任何联系,或者准确地说,在这种情况下,给每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。
原因其实不难理解,操作系统分配给每个进程的内存是有限制的,譬如 32 位的 Windows 限制为 2GB。虚拟机提供了参数来控制 Java 堆和方法区的这两部分内存的最大值。剩余的内存为 2GB(操作系统限制)减去 Xmx(最大堆容量),再减去 MaxPermSize(最大方法区容量),程序计数器消耗内存很小,可以忽略掉。如果虚拟机进程本身耗费的内存不计算在内,剩下的内存就由虚拟机栈和本地方法栈“瓜分”了。每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。
更简单理解的就是:有一个操作系统为2G,我们分配给两个线程,每个800M,也就还剩400M,这样的话,有一个线程不够用的话,就会在400里边申请,所以如果剩下的越多,出现OOM的可能性越小,如果每个分配950M,这样就剩100M,这样的话出现OOM的可能性就更大。如果在增加线程,系统对每一个线程非配的内存是一定的,所以剩下的内存更少,这样的话,出现OOM的可能更大,但这都是相对而说。
遇到OOM这时候我们应该怎么做:
如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换 64 位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。如果没有这方面的经验,这种通过“减少内存”的手段来解决内存溢出的方式会比较难以想到。这种拆东墙补西墙的方法,还是自己意会吧。
<End>
版权声明:“Java后端技术”所推送文章,为本人原创、网上收集或其他作者投稿,对于网上收集部分除非确实无法确认,我们都会注明作者和来源。部分文章推送时未能与原作者取得联系。若涉及版权问题,烦请原作者联系我们,我们会在24小时内删除处理,谢谢!^_^ QQ:1573876303
微信公众号,长按关注或微信搜索公众号:Java后端技术: