在解决java内存溢出问题之前,需要对jvm(java虚拟机)的内存管理有一定的认识。
jvm管理的内存大致包括三种不同类型的内存区域:permanent generation space(永久保存区域)、heap space(堆区域)、java stacks(java栈)。
其中永久保存区域主要存放class(类)和meta的信息,class第一次被load的时候被放入permgen space区域,class需要存储的内容主要包括方法和静态属性。
堆区域用来存放class的实例(即对象),对象需要存储的内容主要是非静态属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理。
而java栈跟大多数编程语言包括汇编语言的栈功能相似,主要基本类型变量以及方法的输入输出参数。java程序的每个线程中都有一个独立的堆栈。
容易发生内存溢出问题的内存空间包括:permanent generation space和heap space。
第一种outofmemoryerror: permgen space (1.8后更新为metaspace,不存在)
permspace主要是存放静态的类信息和方法信息,静态的方法和变量,final标注的常量信息等。
发生这种问题的原意是程序中使用了大量的jar或class,使java虚拟机装载类的空间不够,与permanent generation space有关。解决这类问题有以下两种办法:
1. 增加java虚拟机中的xx:permsize和xx:maxpermsize参数的大小,其中xx:permsize是初始永久保存区域大小,xx:maxpermsize是最大永久保存区域大小。
如针对tomcat6.0,在catalina.sh 或catalina.bat文件中一系列环境变量名说明结束处(大约在70行左右) 增加一行:
java_opts=" -xx:permsize=64m -xx:maxpermsize=128m"
2. 清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到tomcat共同的lib下,减少类的重复加载。
第二种outofmemoryerror: java heap space
发生这种问题的原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与heap space有关。解决这类问题有两种思路:
1. 检查代码中是否有死循环或递归调用。
检查是否有大循环重复产生新对象实体。
检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
检查list、map等集合对象是否有使用完后,未清除的问题。list、map等集合对象会始终存有对对象的引用,使得这些对象不能被gc回收。
2. 增加java虚拟机中xms(初始堆大小)和xmx(最大堆大小)参数的大小。如:set java_opts= -xms256m -xmx1024m
第三种outofmemoryerror:unable to create new native thread
在java应用中,有时候会出现这样的错误:outofmemoryerror: unable to create new native thread.
这种怪事是因为jvm已经被系统分配了大量的内存(比如1.5g),并且它至少要占用可用内存的一半。有人发现,在线程个数很多的情况下,你分配给jvm的内存越多,那么,上述错误发生的可能性就越大。
那么是什么原因造成这种问题呢? 每一个32位的进程最多可以使用2g的可用内存,因为另外2g被操作系统保留。这里假设使用1.5g给jvm,那么还余下500m可用内存。 这500m内存中的一部分必须用于系统dll的加载,那么真正剩下的也许只有400m,现在关键的地方出现了:当你使用java创建一个线程,在jvm的内存里也会创建一个thread对象,但是同时也会在操作系统里创建一个真正的物理线程(参考jvm规范),操作系统会在余下的400兆内存里创建这个物理线程,而不是在jvm的1500m的内存堆里创建。在jdk1.4里头,默认的栈大小是256kb,但是在jdk1.5里头,默认的栈大小为1m每线程,因此,在余下400m的可用内存里边我们最多也只能创建400个可用线程。
这样结论就出来了,要想创建更多的线程,你必须减少分配给jvm的最大内存。还有一种做法是让jvm宿主在你的jni代码里边。 给出一个有关能够创建线程的最大个数的估算公式:
(maxprocessmemory - jvmmemory - reservedosmemory) / (threadstacksize) = number of threads 对于jdk1.5而言,假设操作系统保留120m内存:
1.5gb jvm: (2gb-1.5gb-120mb)/(1mb) = ~
380 threads1.0gb jvm: (2gb-1.0gb-120mb)/(1mb) = ~880 threads 对于栈大小为256kb的jdk1.4而言, 1.5gb allocated to jvm: ~1520 threads
1.0gb allocated to jvm: ~3520 threads 对于这个异常我们首先需要判断下,发生内存溢出时进程中到底都有什么样的线程,这些线程是否是应该存在的,是否可以通过优化来降低线程数; 另外一方面默认情况下java为每个线程分配的栈内存大小是1m,通常情况下,这1m的栈内存空间是足足够用了,因为在通常在栈上存放的只是基础类型的数据或者对象的引用,这些东西都不会占据太大的内存, 我们可以通过调整jvm参数,降低为每个线程分配的栈内存大小来解决问题,例如在jvm参数中添加-xss128k将线程栈内存大小设置为128k。