Java内存不足PermGen space错误探究

2012-04-29 07:00钱宇虹
软件工程 2012年11期
关键词:字节应用程序内存

钱宇虹

摘要:Java程序运行时可能会遇到java.lang.OutOfMemoryError: PermGen space错误;JavaEE服务器(如tomcat、jboss等)在加载war包或ear包时也可能会出现这种错误,这些都是由于永久保存区域内存不足导致的。本文分析了Java程序的运行机制和JVM的内存结构,解释了什么是permanent generation space,分析了PermGen space错误的常见原因,并给出避免这一错误的解决办法。

关键词:JVM; 类加载器; OutOfMemoryError:PermGen space

Java 程序的运行机制与普通程序,如C或C++ 程序的运行机制有很大的区别。

普通程序运行之前必须首先编译成可执行的二进制码或机器码。机器码是与底层的硬件结构相关的,即使书写源代码的时候没有利用平台特定的扩展语言,如特定的文件访问或图形用户界面,生成的机器码仍然被绑定到一个特定的硬件平台,从而只能运行在那个体系结构上,也就是说,为Sun工作站编译的机器码不能运行在PC机上,为PC机编译的机器码不能运行在苹果机上,以此类推。

与此相反,Java源代码不是为某种特定平台编译的,而是编译成与平台无关的字节码(Byte Code),这种字节码叫做Java Class(Java 类文件) 。在任何一种操作系统平台上的字节码都是一样的,因此都可以在任何支持Java 虚拟机(JVM)的计算机上运行。JVM 直接受操作系统控制(而不是Java程序直接受操作系统控制),它负责将字节码转换成在特定平台上能够运行的机器码。正是由于这个原因,Java做到了“write once, run anywhere”(书写一次,到处运行)[1]。

运行Java程序时,首先需要由JVM把Java class加载到JVM里面。通常情况下,我们不会去关注JVM的内部工作细节而只是直接拿来使用。然而,作为一个Java开发人员,你经常会遇到与内存相关的性能问题。这个问题最可怕的就是OutOfMemoryError:PermGen space错误。

这个错误通常会在以下三种情况下发生(服务器以Tomcat为例):

(1)应用程序加载了大量的类。

(2)在单一的Tomcat实例下运行多个Web应用程序。

(3)在运行的Tomcat实例中反复“热部署”Web应用程序。

下面我们对该错误进行探究,分析该错误的常见原因,并给出避免这一错误的解决办法。

1.JVM 的内存结构

为了理解这一错误,我们必须了解JVM的内存是如何构造的。

JVM有两个内存区域,一个是堆(heap),另一个是栈(stack)。局部的变量和方法驻留在栈里面,其余的一切都驻留在堆中。

Java的堆又进一步按照区域进行组织,这些区域被称为generation。一个对象在JVM存在的时间越长,它被升迁到老的generation中的几率就越高。年轻的generation要比老的generation更多地被垃圾回收。同时还存在一些单独的堆空间,被称为永久保存区域(permanent generation),它们不属于Java堆的一部分,用来存放类和类的描述。

类加载器的工作是不断地部署和取消部署Java类。例如,将一个Web应用程序部署到Web服务器或取消部署。在Web服务器上,所有应用程序都有自己的类加载器,部署或取消部署应用程序时,它的类定义和类加载器分别投入到永久保存区域中或者从永久保存区域中删除。

2.OutOfMemoryError: PermGen space

当永久保存区域的空间耗尽时OutOfMemoryError: PermGen space就会发生,这个错误一般是由于内存泄漏导致的。所谓内存泄漏,是指java类和类加载器在被取消部署后不能被垃圾回收[2]。怎么会发生这种情况呢?举个例子:假如我们有一个Student类,这个类是Web应用程序jar包的一部分,同时在Web服务器的lib文件夹中包含了某种日志框架,其中有一个Log类提供register方法调用,从而使得别的类通过注册就可以使用日志功能。如果Student类被注册了,那么Log类就开始拥有了一个对Student对象的引用(reference)。当Student类取消部署时,它仍然是注册Log类的,Log类仍然拥有对Student对象的引用,因此,Student对象永远不会被垃圾回收。此外,由于Student对象拥有一个对它的ClassLoader的引用,所以ClassLoader本身永远也不会被垃圾回收,从而导致由它加载的所有类都不会被回收。

一个更为典型的例子是使用代理对象。Spring和Hibernate常常为某些类生成代理类,这些代理类也是通过类加载器加载的,并且存储在永久保存区域的堆空间,它们永远不会被丢弃,从而会导致永久保存区域的堆空间被填满。

3.如何避免永久保存区域内存不足

3.1 增加PermGen堆的最大尺寸

当遇到java.lang.OutOfMemoryError:PermGen space错误时,我们可以做的第一件事情是增加永久保存区域的最大尺寸,该尺寸的缺省设置是64 M,我们可以将它设置成128 M以上。这个工作不能通过常规的JVM参数 -Xms(设置初始堆大小)和-Xmx(设置最大堆大小)来完成,因为前面已经提到,永久保存区域完全独立于普通的Java堆,这些参数是用来设置普通的Java堆的。不过也有类似参数用于设置永久保存区域的规模:

java-XX:MaxPermSize=128 M

该设置将永久保存区域设置为128 M,这个大小是默认设置的两倍。

对于Tomcat服务器,则需要修改TOMCAT_HOME/bin/catalina.sh

SET JAVA_OPTS=-XX:PermSize=64 M -XX:MaxPermSize=128 M [3]

3.2 避免使用静态字段

确保在编写Java类时,不要使用静态变量作为对其他对象的引用。

3.3使用JDK动态代理,而不是CGLIB代理

一些第三方的框架,如CGLIB会吞食大量的PermGen。因此,当遇到PermGen错误时,应尽快升级cglib到最新版;改用JDK动态代理,也是一个不错的选择。

3.4更新到最新版本Hibernate3.2

此外,新版本的Hibernate不再使用CGLIB作为字节码提供者了,所以及时升级Hibernate,会大大降低出错的机会。

3.5共用的jar文件放到共享目录下

如果在服务器上同时发布了多个应用,那么应该把共用的jar文件放到所有应用都可以访问的目录下。针对Tomcat而言,如果Tomcat下面有多个应用,应尽可能地把lib目录下共用的jar文件放到Tomcat的common\lib或shared\lib下,以避免重复发布,发布速度和运行速度上也会有所提升。

4.结束语

内存不足问题是潜伏较深的问题,且不容易解决。一般情况下,当发生该错误的时候,需要确定为什么某些类不被垃圾回收,只有这样做才能够消除这个错误。

参考文献

[1] Jacquie Barker.Beginning Java Objects From Concept to Code[M] . Birmingham, UK. Published by Wrox Press Ltd.,2000.

[2]Oracle Docs, Chapter 3, Troubleshooting Memory Leaks,

http://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/memleaks.html.

[3] Eric Simmerman,Preventing Java's java.lang.OutOfMemoryError: PermGen space failure [EB],2006.

猜你喜欢
字节应用程序内存
No.8 字节跳动将推出独立出口电商APP
外部高速缓存与非易失内存结合的混合内存体系结构特性评测
删除Win10中自带的应用程序
“春夏秋冬”的内存
No.10 “字节跳动手机”要来了?
谷歌禁止加密货币应用程序
简谈MC7字节码
基于内存的地理信息访问技术
人类进入“泽它时代”
三星电子将开设应用程序下载商店