资讯专栏INFORMATION COLUMN

关于JVM内存溢出的原因分析及解决方案探讨

xuexiangjys / 2137人阅读

摘要:内存溢出分配的内存空间超过系统内存。内存泄漏的原因分析由大块组成堆,栈,本地方法栈,程序计数器,方法区。内存溢出的原因分析内存溢出是由于没被引用的对象垃圾过多造成没有及时回收,造成的内存溢出。小结栈内存溢出程序所要求的栈深度过大导致。

前言:JVM中除了程序计数器,其他的区域都有可能会发生内存溢出。
0.什么是内存溢出

当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出。

1.内存泄漏和内存溢出区别与联系

内存泄漏:系统分配的内存没有被回收。

内存溢出:分配的内存空间超过系统内存。

2.内存泄漏的原因分析

jvm由5大块组成:堆,栈,本地方法栈,程序计数器,方法区。栈它的主要记录方法的执行和对象的引用。堆则存在真正的引用的对象。

内存泄漏是由于使用不当,把一部分内存“丢掉了”,导致这部分内存不可用。

当在堆中创建了对象,后来没有使用这个对象了,又没有把整个对象的相关引用设为null。此时垃圾收集器会认为这个对象是需要的,就不会清理这部分内存。这就会导致这部分内存不可用。

所以内存泄漏会导致可用的内存减少,进而会导致内存溢出。

3. JVM垃圾回收机制思想

就是从栈出发(root),遍历对象的引用,在遍历堆里面的引用对象,因为栈中的对象的引用执行完毕就删除,所以我们就可以通过栈中的对象的引用,查找到堆中没有被指向的对象,这些对象即为不可到达对象,对其进行垃圾回收。

4.内存溢出的原因分析

内存溢出是由于没被引用的对象(垃圾)过多造成JVM没有及时回收,造成的内存溢出。如果出现这种现象可行代码排查:

是否App中的类中和引用变量过多使用了Static修饰 如public staitc Student s;在类中的属性中使用
static修饰的最好只用基本类型或字符串。如public static int i = 0; //public static
String str;

是否App中使用了大量的递归或无限递归(递归中用到了大量的建新的对象)

是否App中使用了大量循环或死循环(循环中用到了大量的新建的对象)

检查App中是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过10万多条了,就可能会造成内存溢出。所以在查询时应采用“分页查询”。

检查是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放。会大量存储在内存中。

检查是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行"+"就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。

如:

String s1 = "My name";
String s2 = "is";
String s3 = "xiaomanong";
String str = s1 + s2 + s3 +.........;

这是会容易造成内存溢出的

但是String str = "My name" + " is " + " xuwei" + " nice " + " to " + " meet you"; //但是这种就不会造成内存溢出。因为这是”字面量字符串“,在运行"+"时就会在编译期间运行好。不会按照JVM来执行的。

在使用String,StringBuffer,StringBuilder时,如果是字面量字符串进行"+"时,应选用String性能更好;如果是String类进行"+"时,在不考虑线程安全时,应选用StringBuilder性能更好。

5.常见的四种内存溢出情况

堆溢出(OutOfMemoryError:java heap space)

持久代溢出(OutOfMemoryError: PermGen space)

栈溢出(StackOverflowError)

OutOfMemoryError:unable to create native thread

1)堆溢出:JVM Heap :java.lang.OutOfMemoryError: Java heap space

JVM在启动的时候会自动设置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap的大小是Young Generation 和Tenured Generaion 之和。在JVM中如果98%的时间是用于GC,且可用的Heap size 不足2%的时候将抛出此异常信息。

解决方法 :手动设置JVM Heap(堆)的大小。

2)持久代溢出:PermGen space : java.lang.OutOfMemoryError: PermGen space

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出,这是由于这块内存主要是被JVM存放Class和Meta信息的,Class在被Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同,sun的 GC不会在主程序运行期对PermGen space进行清理,所以如果你的APP会载入很多CLASS的话,就很可能出现PermGen space溢出。一般发生在程序的启动阶段。

解决方法 : 通过-XX:PermSize和-XX:MaxPermSize设置永久代大小即可。

3)栈溢出: java.lang.StackOverflowError : Thread Stack space

栈溢出了,JVM依然是采用栈式的虚拟机,这个和C和Pascal都是一样的。函数的调用过程都体现在堆栈和退栈上了。调用构造函数的 “层”太多了,以致于把栈区溢出了。 通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K的空间(这个大约相当于在一个C函数内声明了256个int类型的变量),那么栈区也不过是需要1MB的空间。通常栈的大小是1-2MB的。通俗一点讲就是单线程的程序需要的内存太大了。 通常递归也不要递归的层次过多,很容易溢出。

解决方法 :1:修改程序。2:通过 -Xss: 来设置每个线程的Stack大小即可。

4)OutOfMemoryError:unable to create native thread

OutOfMemoryError:unable to create native thread:字面意思是内存溢出:无法创建新的线程。字面意思已经很明显了,出现这种情况的原因基本下面2点:

程序创建的线程数超过操作系统的限制。

JVM占用的内存太多,导致创建线程的内存空间太小。

我们都知道操作系统对每个进程的内存是有限制的,我们启动Jvm,相当于启动了一个进程,假如我们一个进程占用了4G的内存,那么通过下面的公式计算出来的剩余内存就是建立线程栈的时候可以用的内存。 线程栈总可用内存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序计数器占用的内存 通过上面的公式我们可以看出,-Xmx 和 MaxPermSize的值越大,那么留给线程栈可用的空间就越小,在-Xss参数配置的栈容量不变的情况下,可以创建的线程数也就越小。因此如果是因为这种情况导致的unable to create native thread,

解决方法:1:增大进程所占用的总内存。2:减少-Xmx或者-Xss来达到创建更多线程的目的。

5)小结

栈内存溢出:程序所要求的栈深度过大导致。
堆内存溢出: 分清 内存泄露还是 内存容量不足。泄露则看对象如何被 GC Root 引用。不足则通过 调大 -Xms,-Xmx参数。
持久带内存溢出:Class对象未被释放,Class对象占用信息过多,有过多的Class对象。
无法创建本地线程:总容量不变,堆内存,非堆内存设置过大,会导致能给线程的内存不足。

补充:阿里巴巴内存溢出面试题

下面哪种情况会导致持久区jvm堆内存溢出():

A. 循环上万次的字符串处理

B. 在一段代码内申请上百M甚至上G的内存

C. 使用CGLib技术直接操作字节码运行,生成大量的动态类

D. 不断创建对象

解答:AC

解析:AC是持久带,B直接内存也就是堆外内存,D堆内存。

参考书籍:

《深入理解Java虚拟机》 (第二版) 周志明 著;

温馨提示: 如果你喜欢本文,并且想要学习更多干货内容,可以关注一下我的公众号《Java技术zhai》; 
不定期的技术干货内容分享,带你重新理解架构的魅力!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76057.html

相关文章

  • Java深入-框架技巧

    摘要:从使用到原理学习线程池关于线程池的使用,及原理分析分析角度新颖面向切面编程的基本用法基于注解的实现在软件开发中,分散于应用中多出的功能被称为横切关注点如事务安全缓存等。 Java 程序媛手把手教你设计模式中的撩妹神技 -- 上篇 遇一人白首,择一城终老,是多么美好的人生境界,她和他历经风雨慢慢变老,回首走过的点点滴滴,依然清楚的记得当初爱情萌芽的模样…… Java 进阶面试问题列表 -...

    chengtao1633 评论0 收藏0
  • 学习JVM是如何从入门到放弃

    摘要:而字节码运行在之上,所以不用关心字节码是在哪个操作系统编译的,只要符合规范,那么,这个字节码文件就是可运行的。好处防止内存中出现多份同样的字节码安全性角度特别说明类加载器在成功加载某个类之后,会把得到的类的实例缓存起来。 前言 只有光头才能变强 JVM在准备面试的时候就有看了,一直没时间写笔记。现在到了一家公司实习,闲的时候就写写,刷刷JVM博客,刷刷电子书。 学习JVM的目的也很简单...

    Joyven 评论0 收藏0
  • JVM爆炸几种姿势自救方法

    摘要:方法区溢出在的方法区中,它主要存放了类的信息,常量,静态变量等。运行结果简单解决思路一般来说此类问题多出现在存在递归的地方,要从代码里重新审视递归未结束的原因,若递归的方法没问题可以根据实际情况调整参数的大小。 前言 如今不管是在面试还是在我们的工作中,OOM总是不断的出现在我们的视野中,所以我们有必要去了解一下导致OOM的原因以及一些基本的调整方法,大家可以通过下面的事例来了解一下什...

    hyuan 评论0 收藏0

发表评论

0条评论

xuexiangjys

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<