深入理解JVM阅读笔记-内存溢出小结

JAVA系统除了程序计数器和虚拟机内存之外的其它几个内存区域都有发生OutOfMemory(OOM)的可能。堆,栈,方法区,静态常量池,直接内存,都是可能的。

1.Java堆溢出

Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在堆达到最大容量的限制后就会产生内存溢出异常。

-Xms -Xmx 参数可以设置Java堆的大小(实际使用中一般Xms和Xmx的大小一致,放置堆自动扩展),通过参数-XX:+HeapDumpOnOutOfMemoryError让虚拟机在出现内存溢出的异常时Dump出当前的内存堆转储快照以便事后进行分析。

当Java出现堆溢出的时候,异常的堆栈信息为“java.lang.OutOfMemberError” 后会跟着进一步提示“java heap space”

下面有一个堆溢出的OOM例子:

/**

 * -verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError

 *  -XX:+PrintGCDetails -XX:SurvivorRatio=8

 * @author scl

 *

 */

public class HeapOOM {

       static class OOMObject {

       }

       public static void main(String[] args) {

              List<OOMObject> list = new ArrayList<OOMObject>();

              while(true){

                     list.add(new OOMObject());

              }

       }

}     

下方为执行结果:

可以看到出现了一个堆溢出,并产生了一个dump

要解决这个区域的异常需要借助内存映像分析工具对转储快照进行分析,确认内存中的对象是否是必要的。也就是确认到底出现了内存泄露(Member Leak)还是内存溢出(Member Overflow)。

如果存在内存泄露,可以通过内存分析工具查看泄露的对象到GC Roots的引用.--暂时不理解如何查看引用

如下,可以推测出16M的空间被一个Object持有,对应的该对象内部都是OOMObject构成的,很自然的联想到List<OOMObject> list = new ArrayList<OOMObject>();对象.

如果是内存溢出那么就需要检查-Xms 和-Xmx大小,本机物理内存大小,或者其他调优。

2.虚拟机栈和本地方法栈溢出

栈容量由-Xss参数设定,以下是测试案例:

/**

 * -verbose:gc -Xms20M -Xmx20M -Xss128K

 *  -XX:+PrintGCDetails -XX:SurvivorRatio=8

 * @author scl

 *

 */

public class JavaVMStackSOF {

       private int stackInteger = 1;

       public void stackLeak(){

              stackInteger++;

              stackLeak();

       }

       public static void main(String[] args) throws  Throwable{

              JavaVMStackSOF oom = new JavaVMStackSOF();

              try{

                     oom.stackLeak();

              }catch (Throwable e) {

                     // TODO: handle exception

                     System.out.println("stack length :"+oom.stackInteger);

                     throw e;

              }

       }

}

下方是结果截图:

这里顺便提一下本来我在这里异常捕获的时候使用的是Exception ,结果不会打印出stack length 所以表示这个StackOverflowError不是Exception。而使用Throwable即可捕获并打印出栈深度。

这里测试使用的是单线程的情况,如果是多线程的情况下,每个栈分配的内存越大,反而更容易产生内存溢出异常。原因为栈的大小为:操作系统内存-Xmx(最大堆容量)-MaxPermSize(最大方法区容量)-程序计数器(可以忽略不计)。所以在操作系统内存恒定的情况下,每个线程分配的栈容量越大,可以创建的线程数就越少,对应的创建一个新的线程就越容易发生内存溢出。当系统内存溢出又无法减少线程数或者加大总内存的情况下可以尝试减少栈的-Xss值创建新的线程。

/**

 * -verbose:gc -Xms20M -Xmx20M -Xss2M

 * @author scl

 *

 */

public class JavaVMStackOOM {

       private void dontStop(){

              while(true){

              }

       }

       public void stackLeakByThread(){

              while(true){

                     Thread t = new Thread(new Runnable() {

                            @Override

                            public void run() {

                                   // TODO Auto-generated method stub

                                   dontStop();

                            }

                     });

                     t.start();

              }

       }

       public static void main(String[] args) {

              JavaVMStackOOM oom = new JavaVMStackOOM();

              oom.stackLeakByThread();

       }

}

以上代码执行具有风险,会造成操作系统假死,如果想要尝试请先保存当前所有文件。

3.方法区和内存常量池溢出

由于内存常量池包含在方法区内部,所以可以使用-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制内存常量池的大小。

/**

 * -XX:PermSize=10M -XX:MaxPermSize=10M

 * @author scl

 *

 */

public class RuntimeConstantPoolOOM {

       public static void main(String[] args) {

              List<String> list = new ArrayList<String>();

              int i = 0;

              while(true){

                     list.add(String.valueOf(i++).intern());

              }

       }

}

异常信息如下

Sting. Intern方法是一个Native方法,如果字符串常量池中已经包含一个等于该String对象的字符串,则返回代表池中这个字符串的String对象,否则将此String对象添加到常量池中,并返回此String对象的引用.此处OOM后跟着PermGen space也证明了HotSpot的常量池属于方法区的一部分

4.本机直接内存溢出--没有完成测试

DircetMemory容量可以通过-XX:MaxDirectMemorySize指定,如果不指定则与JAVA堆的最大值(-Xmx)一样。

DircetMemory造成的内存溢出,一个明显的特征是Heap Dump文件会看不到任何明显的异常如果发现OOM之后的dump文件很小,那么可以考虑是不是这方面的问题。

时间: 2024-10-28 22:40:58

深入理解JVM阅读笔记-内存溢出小结的相关文章

Linux 2.6 内核阅读笔记 内存管理

2014年7月29日 buddy分配算法 内核需要为分配一组连续的页框提供一种健壮.高效的分配策略.分配连续的页框必须解决内存管理中的外碎片(external fragmentation).频繁的请求和释放不同大小的一组连续页框,必然导致分配页框的块分算来许多小块的空闲页框无法被一次性大量分配使用. linux内核采用著名的伙伴系统算法来解决外碎片问题.该算法的核心思想是把所有的空闲页框分成11个链块表.每个链块表的大小分别为1,2,4,8,16,32,64,128,256,512和1024个连

JVM学习笔记-内存管理

第一章 内存分配 1. 内存区域. 方法区和堆(线程共享),程序计数器 , VM栈 和 本地方法栈(线程隔离). 1) java虚拟机栈:线程私有.描述的是java方法执行的内存模型:栈帧,用户存储 局部变量表,操作数栈,动态链接,方法出口等信息. 局部变量表在编译时即可完全确定!如果线程请求的栈深度大于 规定的深度,StackOverflowError. 2) 本地方法栈,类似. 3)堆:垃圾收集器管理的主要区域.线程共享. 4)方法区: 各个线程共享.存储:加载的类信息,常量,静态变量,即时

深入理解JVM读书笔记一: Java内存区域与内存溢出异常

Java虚拟机管理的内存包括几个运行时数据内存:方法区.虚拟机栈.本地方法栈.堆.程序计数器,其中方法区和堆是由线程共享的数据区,其他几个是线程隔离的数据区. 2.2 运行时数据区域 2.2.1程序计数器 程序计数器是一块较小的内存,他可以看做是当前线程所执行的行号指示器.字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节码的指令,分支.循环.跳转.异常处理.线程恢复等基础功能都需要依赖这个计数器来完成.如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚

深入理解JVM读书笔记五: Java内存模型与Volatile关键字

12.2硬件的效率与一致性 由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了. 基于高速缓存的存储交互很好地理解了处理器与内存的速度矛盾,但是也为计算机系统带来了更高的复杂度,因为它引入了一个新的问题: 缓存一致性(Cache Coherenc

深入理解jvm之一【内存区域】

文章开始之前,首先需要申明,本系列文章讨论的是HotSpot VM,文章中多数观点基于<深入理解Java虚拟机:JVM高级特性与最佳时间   周志明>,笔者如有理解错误,欢迎指正. 在开始探索jvm虚拟机之前,不得不对jvm的内存区域进行讨论,依旧先附上图表: 程序计数器 程序计数器,也能叫做PC寄存器,从名字上来理解可能会把它想成一个计数的内存区域,但是,了解汇编的人会知道,程序技术器实际上是CPU上的一个寄存器,它保存当前指令执行的地址(也可以说下一条指令所在的存储单元).当cpu执行该线

JVM模型及内存溢出

一.JVM截图及概念 图1:JVM虚拟机运行时数据区域概念模型 1.程序计数器:内存空间中的一块小区域,作为当前线程所执行的字节码的行号指示器,注:如果是native方法,计数器为空 2.虚拟机栈:线程私有,生命周期与线程相同,虚拟机栈描述的是Java方法执行的内存模型:创建栈帧,用于存储局部变量表.操作数栈.动态链接.方法出口等信息 3.本地方法栈:和虚拟机栈功能类似,虚拟机使用本地Native方法服务 4.Java堆:线程共享,用于存放对象,是GC的主要管理区域 5.方法区:线程共享,用于存

java内存区域——深入理解JVM读书笔记

本内容由<深入理解java虚拟机>的部分读书笔记整理而成,本读者计划连载. 通过如下图和文字介绍来了解几个运行时数据区的概念. 方法区:它是各个线程共享的区域,用于内存已被VM加载的类信息.常量.静态变量.即时编译器编译的代码等数据.JVM规范对这个区域的限制很宽松,如同堆一样不需要连续的内存.可选择固定大小.可扩展的大小外,还可以选择不实现垃圾收集.因为在些区域的垃圾收集必要性不高且效果较差.如果回收也是常量池的回收和类型的卸载,但此操作异常困难.当方法区无法满足内存的分配时,抛OutOfM

深入理解JVM读书笔记二: 垃圾收集器与内存分配策略

3.2对象已死吗? 3.2.1 引用计数法 给对象添加一个引用计数器,每当有一个地方引用它的地方,计数器值+1:当引用失效,计数器值就减1;任何时候计数器为0,对象就不可能再被引用了. 它很难解决对象之间相互循环引用的问题. 3.2.2 可达性分析算法 这个算法的基本思路就是通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC

深入理解JVM读书笔记: Class类文件结构

Class文件是一组以8位字节为基础单位的二进制流.采用一种类似于C语言结构体的微结构来存储数据,只有两种数据类型:无符号数和表.其中无符号数数据基本的数据类型,以u1.u2.u4.u8表示1.2.4.8字节的无符号数,用于描述数字.索引引用.数量值或者UTF-8编码字符串:表则是由无符号树和其他表的复合数据类型,以_info后缀.整个Class文件本质上就是一张表: 解析Class文件各个数据项含义: 魔数 头4个字节为魔数Magic Number,唯一作用是识别文件是否能被虚拟机接受. 版本