JVM系列之六:内存溢出、内存泄漏 和 栈溢出

1. OOM && SOF

OutOfMemoryError异常: 除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能,

内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。

内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

从定义上可以看出内存泄露是内存溢出的一种诱因,不是唯一因素。

栈溢出:当应用程序递归太深而发生堆栈溢出时,抛出该错误。

2. 发生了内存泄露或溢出怎么办?

一般的异常信息:java.lang.OutOfMemoryError:Java heap spacess
  java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。

(1)通过参数 -XX:+HeapDumpOnOutOfMemoryError 让虚拟机在出现OOM异常的时候Dump出内存映像以便于分析。

(2)一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。(到底是出现了内存泄漏还是内存溢出)

哪些对象被怀疑为内存泄漏,哪些对象占的空间最大及对象的调用关系,还可以分析线程状态,可以观察到线程被阻塞在哪个对象上,从而判断系统的瓶颈。

(3)如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象时通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。 找到引用信息,可以准确的定位出内存泄漏的代码位置。(HashMap中的元素的某些属性改变了,影响了hashcode的值会发生内存泄漏)

(4)如果不存在内存泄漏,就应当检查虚拟机的参数(-Xmx与-Xms)的设置是否适当,是否可以调大;修改代码逻辑,把某些对象生命周期过长,持有状态时间过长等情况的代码修改。

3. 内存泄漏的场景

(1)使用静态的集合类

  静态的集合类的生命周期和应用程序的生命周期一样长,所以在程序结束前容器中的对象不能被释放,会造成内存泄露。

  解决办法是最好不使用静态的集合类,如果使用的话,在不需要容器时要将其赋值为null。

  修改hashset中对象的参数值,且参数是计算哈希值的字段

(2)单例模式可能会造成内存泄露(长生命周期的对象持有短生命周期对象的引用)

  单例模式只允许应用程序存在一个实例对象,并且这个实例对象的生命周期和应用程序的生命周期一样长,如果单例对象中拥有另一个对象的引用的话,这个被引用的对象就不能被及时回收。

  解决办法是单例对象中持有的其他对象使用弱引用,弱引用对象在GC线程工作时,其占用的内存会被回收掉。

(3)数据库、网络、输入输出流,这些资源没有显示的关闭

垃圾回收只负责内存回收,如果对象正在使用资源的话,Java虚拟机不能判断这些对象是不是正在进行操作,比如输入输出,也就不能回收这些对象占用的内存,所以在资源使用完后要调用close()方法关闭。

4. 内存溢出的场景

4.1 Java Heap 溢出

在jvm规范中,堆中的内存是用来生成对象实例和数组的。 
  如果细分,堆内存还可以分为年轻代和年老代,年轻代包括一个eden区和两个survivor区。 
  当生成新对象时,内存的申请过程如下:

  1. jvm先尝试在eden区分配新建对象所需的内存;
  2. 如果内存大小足够,申请结束,否则下一步;
  3. jvm启动youngGC,试图将eden区中不活跃的对象释放掉,释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区;
  4. Survivor区被用来作为Eden及old的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区;
  5. 当OLD区空间不够时,JVM会在OLD区进行full GC;
  6. full GC后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”: outOfMemoryError:java heap space

4.2 虚拟机栈和本地方法栈溢出   

  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。   
  2. 不断创建线程,如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常  
  3. 这里需要注意当栈的大小越大可分配的线程数就越少。
  4. 用Xss设置

4.3 运行时常量池溢出

  1. 异常信息:java.lang.OutOfMemoryError:PermGen space
  2. 如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。
  3. 该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
  4. 由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。

4.4 方法区溢出

  异常信息:java.lang.OutOfMemoryError: PermGen space
    方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

  所以如果程序加载的类过多,或者使用反射、gclib等这种动态代理生成类的技术,就可能导致该区发生内存溢出
  方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。 
  我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小

4.5 java.lang.OutOfMemoryError: GC overhead limit exceeded

  原因:执行垃圾收集的时间比例太大, 有效的运算量太小. 默认情况下, 如果GC花费的时间超过 98%, 并且GC回收的内存少于 2%, JVM就会抛出这个错误。
  目的是为了让应用终止,给开发者机会去诊断问题。一般是应用程序在有限的内存上创建了大量的临时对象或者弱引用对象,从而导致该异常。
  解决方法:
    1. 大对象在使用之后指向null。
    2. 增加参数,-XX:-UseGCOverheadLimit,关闭这个特性;
    3. 增加heap大小,-Xmx1024m

5. SOF (堆栈溢出 StackOverflow)

  StackOverflowError 的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。 因为栈一般默认为1-2M,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1M而导致溢出。 
  栈溢出的原因: 
    递归调用 
    大量循环或死循环 
    全局变量是否过多 
    数组、List、map数据过大

6. 如何避免发生内存泄露和溢出

1、尽早释放无用对象的引用

2、使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域

3、尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收

4、避免在循环中创建对象

5、开启大型文件或从数据库一次拿了太多的数据很容易造成内存溢出,所以在这些地方要大概计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。

原文地址:https://www.cnblogs.com/haimishasha/p/11329510.html

时间: 2024-11-08 19:24:59

JVM系列之六:内存溢出、内存泄漏 和 栈溢出的相关文章

JVM 系列(二)内存模型

02 JVM 系列(二)内存模型 一.JVM 内存组成 (1) PC 寄存器(线程私有) Java 虚拟机会为每个线程创建 PC 寄存器,在任意时刻,一个 java 线程总是在执行一个方法,这个方法被称为当前方法. 如果当前方法不是本地方法,PC 寄存器就会执行当前正在被执行的指令,如果是本地方法,则 PC 寄存器值为 undefined,寄存器存放如当前执行环境指针.程序计数器.操作栈指针.计算的变量指针等信息. 这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError

Android 基础学习--内存溢出和泄漏

过几天又要去面试了,所以整理一下知识点,以前知道,但是都没仔细想,可能在面试的时候答不全,如果各位觉得除了我总结的之外还有其它情况可以留言,谢谢! 一  什么是内存泄漏?怎么解决? 内存泄漏也称作“存储渗漏”,就是在内存中创建对象开辟的空间,在使用完毕后没有释放,或者无法释放,结果导致该空间一直没有释放.直到程序结束.即所谓内存泄漏. 我总结了以下,内存泄漏的原因:不再有用的对象被其他依然有用的对象所引用是导致内存泄漏的主要原因. 1 查询SQlite数据库后,游标没有关闭. 2 ListVie

JVM系列文章(一):Java内存区域分析

作为一个程序员,仅仅知道怎么用是远远不够的.起码,你需要知道为什么可以这么用,即我们所谓底层的东西. 那到底什么是底层呢?我觉得这不能一概而论.以我现在的知识水平而言:对于Web开发者,TCP/IP.HTTP等等协议可能就是底层:对于C.C++程序员,内存.指针等等可能就是底层的东西.那对于Java开发者,你的Java代码运行所在的JVM可能就是你所需要去了解.理解的东西. 我会在接下来的一段时间,和读者您一起去学习JVM,所有内容均参考自<深入理解Java虚拟机:JVM高级特性与最佳实践>(

[转]JVM系列二:GC策略&amp;内存申请、对象衰老

原文地址:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037056.html JVM里的GC(Garbage Collection)的算法有很多种,如标记清除收集器,压缩收集器,分代收集器等等,详见HotSpot VM GC 的种类 现在比较常用的是分代收集(generational collection,也是SUN VM使用的,J2SE1.2之后引入),即将内存分为几个区域,将不同生命周期的对象放在不同区域里:young genera

JVM系列二:GC策略&amp;内存申请、对象衰老

JVM里的GC(Garbage Collection)的算法有很多种,如标记清除收集器,压缩收集器,分代收集器等等,详见HotSpot VM GC 的种类 现在比较常用的是分代收集(generational collection,也是SUN VM使用的,J2SE1.2之后引入),即将内存分为几个区域,将不同生命周期的对象放在不同区域里:young generation,tenured generation和permanet generation.绝大部分的objec被分配在young gener

JVM系列一:虚拟机内存区域

虚拟机栈 1.虚拟机栈维护一个线程中所有方法的栈帧,每个栈帧中保存着这个方法中用到的局部变量表,操作数栈,常量引用 2.可以用-Xss来设置每个线程中虚拟机栈的大小,在jdk1.4之前默认虚拟机栈大小是256K,在jdk1.5+默认虚拟机栈大小是1M java -Xss2M HackTheJava 3.该区域可能抛出的异常 当线程请求的栈深度超过最大限制后,或抛出StackOverflowError 当栈进行动态扩展无法申请到内存后,会抛出OutOfMemoryError 本地方法栈 本地方法栈

rxjava封装,RxBus封装(上线项目集成,声明周期管理,无内存溢出内存,支持同时多个请求。)

Github地址 RxLibrary工程:1.rxjava2 + retrofit2的封装,常用的请求(Get,Post,文件上传,文件下载),简单便捷,支持自定义loading等属性.2.RxBus的使用,用法完全与EvenBus一样. 效果图 集成 compile 'com.bhm.sdk.rxlibrary:RxLibrary:2.3.1' 或者 <dependency> <groupId>com.bhm.sdk.rxlibrary</groupId> <a

内存溢出和内存泄漏的区别,产生原因以及解决方案

1.1内存溢出:(Out Of Memory---OOM) 系统已经不能再分配出你所需要的空间,比如你需要100M的空间,系统只剩90M了,这就叫内存溢出 例子:一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了.这就是溢出.比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢.就是分配的内存不足以放下数据项序列,称为内存溢出.说白了就是我承受不了那么多,那我就报错, 1.2内存泄漏:  (Memory Leak)---->强引用所指向的对象

有关内存溢出和内存泄漏的知识点

在jvm中,有关内存可能会出现的两种错误内存溢出和内存泄漏,怎么理解这两种错误: 1.内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出. 2.内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出. 3.二者的关系 内存泄漏的堆