内存溢出
内存溢出
内存溢出是指应用新建一个对象实例时,所需的内存空间大于堆的可用空间。内存溢出的种类较多,一般会在报错日志里看到 OutOfMemoryError 关键字。
系统内存分配
首先看下操作系统如何划分内存给应用系统,其实在 Win 32、Linux 32 的系统中,地址总线为 32 位的理论上应该可以支持 4G 内存空间,但是当你在 Win 32 上设置初始化内存如果达到 2G,就会报错,说这个块空间没法做。
首先默认的 Win32 系统,会按照 50% 比例给予给 Kernel 使用,而另一部分给应用内存,也就是说操作系统内核部分不论是否使用,这一半是不会给你的,而还有 2G 呢,它在系统扩展的部分,也就是并非 Kernel 的部分,有很多静态区域 和字典表的内容,所以要划分一个连续的 2G 内存给 JVM 在 Win 32 上是不可能的,Win 32 提出了一种 Win 32 3G 模式,貌似可以划分 3G 空间,其实它只是将内核部分缩小也就是管理部分缩小,也就是将一部分划分到外部来使用,而且 Win 32 习惯在内存 2G 的位置做一些手脚,让你分配连续 2G 没有可能性,一般来说在 Win 32 平台上,在物理内存足够的情况下给 JVM 划分的空间一般是 1.4 ~ 1.5G 左右,具体数据没有测试过;而 Linux 32 类似于 Win 32 3G 模式,但是它还是一般情况下分布不凌乱的情况下,一般可以给 JVM 划分到 2G 的大小。Linux 32 Hugemem 是一个扩展版本,可以划分更大的空间,但是需要付出一些其他的代价,理论上可以支持到 4G 给应用,也就是 Kenel 是独立 的;Solaris x86-32 和 AIX 32 等系统,也类似于 Linux 32 平台一样。
当你申请一个线程的时候,它的除了线程内部对象的开销外,线程本身的开销,是需要 OS 来调度完成,一般来说,会在 OS 的线程与虚拟机内部有都有一个一一对 应的,但是会根据操作系统不同有所变化,有些可能只有一个,总之 heapSize 外的那部分空间是跑不掉的,它放在哪里呢?就是放在 Stack 中的,所以 上文中的-Xss 就是设置这个的,在 jdk 1.5 以后,每个线程的大小被默认设置为 1M 的 stack 开销,我们习惯将这个开销降低。
常见内存溢出
常见内存溢出种类及分析思路如下:
-
java.lang.OutOfMemoryError: Java heap space。原因:堆中(新生代和老年代)无法继续分配对象了、某些对象的引用长期被持有没有被释放,垃圾回收器无法回收、使用了大量的 Finalizer 对象,这些对象并不在 GC 的回收周期内等。一般堆溢出都是由于内存泄漏引起的,如果确认没有内存泄漏,可以适当通过增大堆内存。
-
java.lang.OutOfMemoryError: GC overhead limit exceeded。原因:垃圾回收器超过 98%的时间用来垃圾回收,但回收不到 2%的堆内存,一般是因为存在内存泄漏或堆空间过小。
-
java.lang.OutOfMemoryError: Metaspace 或 java.lang.OutOfMemoryError: PermGen space。排查思路:检查是否有动态的类加载但没有及时卸载,是否有大量的字符串常量池化,永久代/元空间是否设置过小等。
-
java.lang.OutOfMemoryError: unable to create new native Thread。原因:虚拟机在拓展栈空间时,无法申请到足够的内存空间。可适当降低每个线程栈的大小以及应用整体的线程个数。此外,系统里总体的进程/线程创建总数也受到系统空闲内存和操作系统的限制,请仔细检查。注:这种栈溢出,和 StackOverflowError 不同,后者是由于方法调用层次太深,分配的栈内存不够新建栈帧导致。
此外,还有 Swap 分区溢出、本地方法栈溢出、数组分配溢出等 OutOfMemoryError 类型。
内存溢出工具
使用非常多的工具区检测 Java 的内存如:jstat(只能看 HeapSize 和 PermSize)、jmap(很细的东西)、jps(java 的 ps -ef 呵呵)、jdb(这个不是监控工具哈,这个是 debug 工具)、jprofile(图形支持,但是可以远程连接)等等;jconsole(可以看到 heapsize、permsize+native mem size(这这里叫做:non-heapsize)等等的使用的趋势图)、visualvm(极为推荐的东西,图形化查看,你可以查看到内存单元分配、交 换、回收、移动等等整个过程,非常清晰展现 jvm 的全局资源)、另外 pmap 可以展现非常清晰的资料,可以精确到某一个 java 进程内部的每一个细节,而 且可以看到 heapsize 只是其中很小一部分(在 solaris 操作系统上看得最齐全,LINUX 下有些进程可能看不太懂);也可以在/proc/进程 号/maps 中查看(这里可以看到内存地址单元的起始地址,包含了 reserved 的地址范围和 commited 的地址范围),全局资源使用操作系统 top 命令和 free 命令看;IBM 有一个 GCMV 免费下载工具也很好;Win32 有一个 WMMap 工具都是很好的工具