最近在工作中遇到了内存溢出,虽然这个问题没用多久就用在catalina.bat和catalina.sh设置栈堆内存初始大小立时解决,但对个中缘由不甚理解,故深入了解了一番在此分享。
首先,Java内存管理原理:
在java中,有java程序、虚拟机、操作系统三个层次,其中java程序与虚拟机交互,而虚拟机与操作系统交互。这也就保证了java的与平台无关性,三者的运行原理是:
1、程序运行前:JVM向操作系统请求一定的内存空间,成为初始内存空间!程序执行过程中所需的内存都是由java虚拟机从这片内存空间中划分的。
2、程序运行中:java程序一直向java虚拟机申请内存,当程序所需要的内存空间超出初始内存空间时,java虚拟机会再次向操作系统申请更多的内存供程序使用!
3、内存溢出:程序接着运行,当java虚拟机已申请的内存达到了规定的最大内存空间,但程序还需要跟多的内存,这时会出现内存溢出的错误!
由此,我们知道java程序所用的内存是有java虚拟机进行管理、分配的。java虚拟机规定了java程序的初始内存空间和最大内存空间。
内存空间逻辑划分:
JVM会把申请的内存从逻辑上划分为三个区域,即:方法去、栈与堆。
方法区:方法去默认最大容量为64M,java虚拟机会将加载的java类存入方法区,保存类的结构(属性与方法),类静态成员等内容。
堆:默认最大容量为64M,堆存放对象持有的数据,同事保持对原类的引用。可以简单地理解为对象属性的值保存在堆中,对象调用的方法保存在方法区。
栈:默认最大容量为1M,在程序运行时,每当遇到方法调用时,java虚拟机就会在栈中划分一块内存称为栈帧(Stack frame),栈帧中的内存供局部变量(包括基本类型与引用类型)使用,当方法调用结束后,java虚拟机会收回此栈帧占用的内存。
在介绍了java内存相关知识点后,就来说说对内存大小的设置,对于一些规模比较大的应用,JVM的内存设置的好,就可以在项目中取得好的效率。
Java Heap也即堆分为三个区:Young、Old和Permanent。
Young保存刚实例化的对象,当该区被填满时,GC会将对象已到Old区,Permanent区则负责保存反射对象。JVM的Heap分配可以使用-X参数设定,-Xms对应初始Heap大小,-Xmx对应Java Heap最大值,-Xmn对应Young Generation的heap大小。
JVM有2个GC线程,第一个线程负责回收Heap的Young区,第二个线程在Heap不足时,遍历Heap,将Young区升级为Older区,Older区的大小等于-Xmx减去-xmn,不能将-Xms的值设的过大,因为第二个线程被迫运行会降低JVM的性能。
主要内存溢出类型:
1、OutOfMemoryError:PermGen space
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,主要是被JVM存放Class和Meta信息的,Class在被Loader的时候就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同,GC不会在主程序运行期对PermGen space进行清理,如果程序中有很多Class的话,就很可能出现PermGen space错误(比如webapp下用了大量第三方jar,其大小超过了JVM默认的大小4M就会产生比错误),网上一个比较推崇的设置 set JAVA_OPTS=-Xms800m -Xmx800m -XX:PermSize=128m -XX:MaxNewSize=256m -XX:MaxPermSize=256m
2、OutOfMemoryError:JavaHeap space
JVM默认空间即(-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4,如果内存剩余不到40%,JVM就会增大堆到Xmx设置的值,内存剩余超过70%,JVM就会减小到Xms设置的值,所以服务器的Xmx和Xms设置一般应该设置相同避免每次GC后都要调整虚拟机堆的大小。假设物理内存无限大,那么JVM内存的最大值跟操作系统有关,一般32位机是1.5G到3G之间,而64为的就不会有限制了。(注:如果Xms超过了Xmx值,或者堆最大值和非堆最大值的总和超过了物理内存或者操作系统的最大限制都会引起服务器启动不起来)
而在设置虚拟机内存时,一般将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值,Heap Size最大不要超过可用物理内存的80%。