Java内存溢出与栈溢出

一、背景知识


1、JVM体系结构




2、JVM运行时数据区




3、JVM内存模型


JVM运行时内存 = 共享内存区 + 线程内存区




3-1、共享内存区


共享内存区 = 持久带 + 堆

持久带 = 方法区 + 其他

堆 = Old Space + Young Space

Young Space = Eden + S0 + S1




3-1-1、持久代


JVM用持久带(Permanent Space)实现方法区,主要存放所有已加载的类信息,方法信息,常量池等等。

可通过-XX:PermSize和-XX:MaxPermSize来指定持久带初始化值和最大值

Permanent Space并不等同于方法区,只不过是Hotspot JVM用Permanent Space来实现方法区而已,有些虚拟机没

有Permanent Space而用其他机制来实现方法区。


3-1-2、堆


堆(heap),主要用来存放类的对象实例信息(包括new操作实例化的对象和定义的数组)。

堆分为Old Space(又名,Tenured Generation)和Young Space。

Old Space主要存放应用程序中生命周期长的存活对象;

Eden(伊甸园)主要存放新生的对象;

S0和S1是两个大小相同的内存区域,主要存放每次垃圾回收后Eden存活的对象,作为对象从Eden过渡到Old Space

的缓冲地带(S是指英文单词Survivor Space)。

堆之所以要划分区间,是为了方便对象创建和垃圾回收,后面垃圾回收部分会解释。


3-2、线程内存区


线程内存区=单个线程内存+单个线程内存+.......

单个线程内存=PC Regster+JVM栈+本地方法栈

JVM栈=栈帧+栈帧+.....

栈帧=局域变量区+操作数区+帧数据区




在Java中,一个线程会对应一个JVM栈(JVM Stack),JVM栈里记录了线程的运行状态。

JVM栈以栈帧为单位组成,一个栈帧代表一个方法调用。栈帧由三部分组成:局部变量区、操作数栈、帧数据区。


二、堆溢出


堆(Heap)是Java存放对象实例的地方。

堆溢出可以分为以下两种情况,这两种情况都会抛出OutOfMemoryError:java heap space异常:


1、内存泄漏


内存泄漏是指对象实例在新建和使用完毕后,仍然被引用,没能被垃圾回收释放,一直积累,直到没有剩余

内存可用。

如果内存泄露,我们要找出泄露的对象是怎么被GC ROOT引用起来,然后通过引用链来具体分析泄露的原因。

分析内存泄漏的工具有:Jprofiler,visualvm等。


示例代码:

[java]  view plain  copy
  1. package com.jvm;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5. import java.util.UUID;  
  6.   
  7. /** 
  8.  * 内存泄漏 
  9.  * @author feizi 
  10.  * @time 2015-1-23上午8:42:53 
  11.  */  
  12. public class OOMTest {  
  13.   
  14.     public static void main(String[] args) {  
  15.           
  16.         List<UUID> list = new ArrayList<UUID>();  
  17.         while(true){  
  18.             list.add(UUID.randomUUID());  
  19.         }  
  20.     }  
  21.   
  22. }  



看看控制台的输出结果,因为我这边的JVM设置的参数内存足够大,所以需要等待一定的时间,才能看到效果:






如果是用CMD命令行,就可以自己指定参数编译运行了,这样效果就更快一些:

通过下列命令运行程序,注意先要用javac命令将.java源文件编译成.class类字节码文件。


[java]  view plain  copy
  1. java -Xms10M -Xmx10M -XX:-UseGCOverheadLimit OOMTest  


2、内存溢出


内存溢出是指当我们新建一个实力对象时,实例对象所需占用的内存空间大于堆的可用空间。

如果出现了内存溢出问题,这往往是程序本生需要的内存大于了我们给虚拟机配置的内存,这种情况下,我们可以采用调大-Xmx来解决这种问题。


示例代码:

[java]  view plain  copy
  1. package com.jvm;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  * 内存溢出 
  8.  * @author feizi 
  9.  * @time 2015-1-23上午8:56:22 
  10.  */  
  11. public class OOMTest_1 {  
  12.     public static void main(String args[]){  
  13.         List<byte[]> byteList = new ArrayList<byte[]>();  
  14.         byteList.add(new byte[1000 * 1024 * 1024]);  
  15.     }  
  16. }  



看看控制台的运行效果:




使用CMD命令行指定参数运行:


[java]  view plain  copy
  1. java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest_1  




三、线程栈


栈(JVM Stack)存放主要是栈帧( 局部变量表, 操作数栈 , 动态链接 , 方法出口信息 )的地方。注意区分栈和栈帧:栈里包含栈帧。

与线程栈相关的内存异常有两个:

a)、StackOverflowError(方法调用层次太深,内存不够新建栈帧)

b)、OutOfMemoryError(线程太多,内存不够新建线程)


1、java.lang.StackOverflowError


栈溢出抛出java.lang.StackOverflowError错误,出现此种情况是因为方法运行的时候,请求新建栈帧时,

栈所剩空间小于战帧所需空间。

例如,通过递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常 :


示例代码:

[java]  view plain  copy
  1. package com.jvm;  
  2. /** 
  3.  * 栈溢出 
  4.  * @author feizi 
  5.  * @time 2015-1-23上午9:13:11 
  6.  */  
  7. public class SOFTest {  
  8.   
  9.     public void stackOverFlowMethod(){  
  10.         stackOverFlowMethod();  
  11.     }  
  12.       
  13.     /** 
  14.      * 通过递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常 : 
  15.      * @param args 
  16.      */  
  17.     public static void main(String[] args) {  
  18.         SOFTest sof = new SOFTest();  
  19.         sof.stackOverFlowMethod();  
  20.     }  
  21.   
  22. }  


看看控制台运行的效果:





猜你喜欢

转载自blog.csdn.net/qq_34368762/article/details/79542609