在我做J2EE系统开发的工作生涯中,经常遇到技术人员或客户发出诸如此类 的感慨:我的J2EE应用系统处理的数据量不大,系统体积也不大,技术架构也 没有问题,我的应用服务器的内存有4G或8G:系统运行起来很慢,还经常出现 内存溢出错误。真是无奈!每次遇到这样的情况,我心中都会忍不住窃笑之。 其实他们所遇到这种情况,不是技术架构上的问题,不是系统本身的问题,也不 是应用服务器的问题,也可能不是服务器的内存资源真的不足的问题。他们花了 很多时间在J2EE应用系统本身上找问题(当然一般情况下,这种做法是对的: 当出现问题时,在自身上多找找有什么不足),结果还是解决不了问题。他们却 忽略了很重要的一点:J2EE应用系统是运行在J2EE应用服务器上的,而J2EE 应用服务器又是运行在JM(Java Virtual Machine)上的。 其实在生产环境中JVM参数的优化和设置对J2EE应用系统性能有着决定性的作 用。本篇我们就来分析JAVA的创建者SN公司的JVM的内存管理机制(在现实 中绝大多数的应用服务器是运行在SUN公司的JVM上的,当然除了SUN公司的 JVM,还有IBM的JVM,Bea的JVM等);下篇咱们具体讲解怎样优化JVM的参数 以达到优化J2EE应用的目的。 咱们先来看JVM的内存管理制吧,JVM的早期版本并没有进行分区管理:这样的 后果是JVM进行垃圾回收时,不得不扫描JVM所管理的整片内存,所以搜集垃圾 是很耗费资源的事情,也是早期JAVA程序的性能低下的主要原因。随着JVM的 发展,JM引进了分区管理的机制。 采用分区管理机制的JVM将JVM所管理的所有内存资源分为2个大的部分。永久 存储区(Permanent Space)和堆空间(The Heap Space)。其中堆空间又分为 新生区(Young(New)generation space)和养老区(Tenure(Old)generation space),新生区又分为伊甸园(Eden space),幸存者0区(Survivor0 space) 和幸存者1区(Survivor1 space)。具体分区如下图: M所管理内在 自1的09四守甲4◆ 堆空间(The Heap Space) 养老区〔Tenure(Old)generation space) 伊甸园(Eden space】 幸在者O区(Survivor0 space) 幸存者1区(Survivor1 space)
在我做 J2EE 系统开发的工作生涯中,经常遇到技术人员或客户发出诸如此类 的感慨:我的 J2EE 应用系统处理的数据量不大,系统体积也不大,技术架构也 没有问题,我的应用服务器的内存有 4G 或 8G;系统运行起来很慢,还经常出现 内存溢出错误。真是无奈!每次遇到这样的情况,我心中都会忍不住窃笑之。 其实他们所遇到这种情况,不是技术架构上的问题,不是系统本身的问题,也不 是应用服务器的问题,也可能不是服务器的内存资源真的不足的问题。他们花了 很多时间在 J2EE 应用系统本身上找问题(当然一般情况下,这种做法是对的; 当出现问题时,在自身上多找找有什么不足),结果还是解决不了问题。他们却 忽略了很重要的一点:J2EE 应用系统是运行在 J2EE 应用服务器上的,而 J2EE 应用服务器又是运行在 JVM(Java Virtual Machine)上的。 其实在生产环境中 JVM 参数的优化和设置对 J2EE 应用系统性能有着决定性的作 用。本篇我们就来分析 JAVA 的创建者 SUN 公司的 JVM 的内存管理机制(在现实 中绝大多数的应用服务器是运行在 SUN 公司的 JVM 上的,当然除了 SUN 公司的 JVM,还有 IBM 的 JVM,Bea 的 JVM 等);下篇咱们具体讲解怎样优化 JVM 的参数 以达到优化 J2EE 应用的目的。 咱们先来看 JVM 的内存管理制吧,JVM 的早期版本并没有进行分区管理;这样的 后果是 JVM 进行垃圾回收时,不得不扫描 JVM 所管理的整片内存,所以搜集垃圾 是很耗费资源的事情,也是早期 JAVA 程序的性能低下的主要原因。随着 JVM 的 发展,JVM 引进了分区管理的机制。 采用分区管理机制的 JVM 将 JVM 所管理的所有内存资源分为 2 个大的部分。永久 存储区(Permanent Space)和堆空间(The Heap Space)。其中堆空间又分为 新生区(Young (New) generation space)和养老区(Tenure (Old) generation space),新生区又分为伊甸园(Eden space),幸存者 0 区(Survivor 0 space) 和幸存者 1 区(Survivor 1 space)。具体分区如下图:
那JVM他的这些分区各有什么用途,请看下面的解说。 永久存储区(Permanent Space):永久存储区是JVM的驻留内存,用于存放JDK 自身所携带的Class,Interface的元数据,应用服务器允许必须的 Class,Interface的元数据和Java程序运行时需要的Class和Interface的元 数据。被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM时,释放 此区域所控制的内存。 堆空间(The Heap Space):是JAVA对象生死存亡的地区,JAVA对象的出生, 成长,死亡都在这个区域完成。堆空间又分别按JAVA对象的创建和年龄特征分 为养老区和新生区。 新生区(Young(New)generation space):新生区的作用包括JAVA对象的创 建和从JAVA对象中筛选出能进入养老区的JAVA对象。 伊甸园(Eden space):JAVA对空间中的所有对象在此出生,该区的名字因此 而得名。也即是说当你的JAVA程序运行时,需要创建新的对象,JVM将在该区 为你创建一个指定的对象供程序使用。创建对象的依据即是永久存储区中的元数 据。 幸存者0区(Survivor0 space)和幸存者1区(Survivorl space):当伊甸 园的控件用完时,程序又需要创建对象:此时JVM的垃圾回收器将对伊甸园区进 行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁工作。同时 将伊甸园中的还有其他对象引用的对象移动到幸存者0区。幸存者0区就是用于 存放伊甸园垃圾回收时所幸存下来的JAVA对象。当将伊甸园中的还有其他对象 引用的对象移动到幸存者0区时,如果幸存者0区也没有空间来存放这些对象时, JVM的垃圾回收器将对幸存者0区进行垃圾回收处理,将幸存者0区中不在有其 他对象引用的JAVA对象进行销毁,将幸存者0区中还有其他对象引用的对象移 动到幸存者1区。幸存者1区的作用就是用于存放幸存者0区垃圾回收处理所幸 存下来的JAVA对象。 养老区(Tenure(Old)generation space):用于保存从新生区筛选出来的JAVA 对象。 上面我们看了JVM的内存分区管理,现在我们来看JVM的垃圾回收工作是怎样运 作的。首先当启动J2EE应用服务器时,JVM随之启动,并将JDK的类和接口, 应用服务器运行时需要的类和接口以及J2EE应用的类和接口定义文件也及编译 后的Class文件或JAR包中的Class文件装载到JVM的永久存储区。在伊甸园中 创建JM,应用服务器运行时必须的JAVA对象,创建J2EE应用启动时必须创建 的JAVA对象;J2EE应用启动完毕,可对外提供服务。 JVM在伊甸园区根据用户的每次请求创建相应的JAVA对象,当伊甸园的空间不 足以用来创建新JAVA对象的时候,JVM的垃圾回收器执行对伊甸园区的垃圾回 收工作,销毁那些不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没
那 JVM 他的这些分区各有什么用途,请看下面的解说。 永久存储区(Permanent Space):永久存储区是 JVM 的驻留内存,用于存放 JDK 自身所携带的 Class,Interface 的元数据,应用服务器允许必须的 Class,Interface 的元数据和 Java 程序运行时需要的 Class 和 Interface 的元 数据。被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 时,释放 此区域所控制的内存。 堆空间(The Heap Space):是 JAVA 对象生死存亡的地区,JAVA 对象的出生, 成长,死亡都在这个区域完成。堆空间又分别按 JAVA 对象的创建和年龄特征分 为养老区和新生区。 新生区(Young (New) generation space):新生区的作用包括 JAVA 对象的创 建和从 JAVA 对象中筛选出能进入养老区的 JAVA 对象。 伊甸园(Eden space):JAVA 对空间中的所有对象在此出生,该区的名字因此 而得名。也即是说当你的 JAVA 程序运行时,需要创建新的对象,JVM 将在该区 为你创建一个指定的对象供程序使用。创建对象的依据即是永久存储区中的元数 据。 幸存者 0 区(Survivor 0 space)和幸存者 1 区(Survivor1 space):当伊甸 园的控件用完时,程序又需要创建对象;此时 JVM 的垃圾回收器将对伊甸园区进 行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁工作。同时 将伊甸园中的还有其他对象引用的对象移动到幸存者 0 区。幸存者 0 区就是用于 存放伊甸园垃圾回收时所幸存下来的 JAVA 对象。当将伊甸园中的还有其他对象 引用的对象移动到幸存者0区时,如果幸存者0区也没有空间来存放这些对象时, JVM 的垃圾回收器将对幸存者 0 区进行垃圾回收处理,将幸存者 0 区中不在有其 他对象引用的 JAVA 对象进行销毁,将幸存者 0 区中还有其他对象引用的对象移 动到幸存者 1 区。幸存者 1 区的作用就是用于存放幸存者 0 区垃圾回收处理所幸 存下来的 JAVA 对象。 养老区(Tenure (Old) generation space):用于保存从新生区筛选出来的 JAVA 对象。 上面我们看了 JVM 的内存分区管理,现在我们来看 JVM 的垃圾回收工作是怎样运 作的。首先当启动 J2EE 应用服务器时,JVM 随之启动,并将 JDK 的类和接口, 应用服务器运行时需要的类和接口以及 J2EE 应用的类和接口定义文件也及编译 后的 Class 文件或 JAR 包中的 Class 文件装载到 JVM 的永久存储区。在伊甸园中 创建 JVM,应用服务器运行时必须的 JAVA 对象,创建 J2EE 应用启动时必须创建 的 JAVA 对象;J2EE 应用启动完毕,可对外提供服务。 JVM 在伊甸园区根据用户的每次请求创建相应的 JAVA 对象,当伊甸园的空间不 足以用来创建新 JAVA 对象的时候,JVM 的垃圾回收器执行对伊甸园区的垃圾回 收工作,销毁那些不再被其他对象引用的 JAVA 对象(如果该对象仅仅被一个没
有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推), 并将那些被其他对象所引用的JAVA对象移动到幸存者0区。 如果幸存者0区有足够控件存放则直接放到幸存者0区:如果幸存者0区没有足 够空间存放,则JVM的垃圾回收器执行对幸存者0区的垃圾回收工作,销毁那些 不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的 对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他 对象所引用的JAVA对象移动到幸存者1区。 如果幸存者1区有足够控件存放则直接放到幸存者1区;如果幸存者0区没有足 够空间存放,则JVM的垃圾回收器执行对幸存者0区的垃圾回收工作,销毁那些 不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的 对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他 对象所引用的JAVA对象移动到养老区。 如果养老区有足够控件存放则直接放到养老区:如果养老区没有足够空间存放, 则JM的垃圾回收器执行对养老区区的垃圾回收工作,销毁那些不再被其他对象 引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的对象引用的话, 此对象也被归为没有存在的必要,依此类推),并保留那些被其他对象所引用的 JAVA对象。如果到最后养老区,幸存者1区,幸存者0区和伊甸园区都没有空 间的话,则JVM会报告“JVM堆空间溢出(java.lang.Out0 fMemoryError:Java heap space)”,也即是在堆空间没有空间来创建对象。 这就是JVM的内存分区管理,相比不分区来说:一般情况下,垃圾回收的速度要 快很多:因为在没有必要的时候不用扫描整片内存而节省了大量时间。 通常大家还会遇到另外一种内存溢出错误“永久存储区溢出 (java.lang.OutOfMemoryError:Java Permanent Space)" 好,本篇对SUN的JVM内存管理机制讲解就到此为止,下一篇我们将详细讲解 怎样优化SUN的JVM让我们的J2EE系统运行更快,不出现内存溢出等问题。 2.4 SUN JVM调优 JVM相关参数: 参数名参数说明 -server启用能够执行优化的编译器,显著提高服务器的性能,但使用能够执行 优化的编译器时,服务器的预备时间将会较长。生产环境的服务器强烈推荐设置 此参数。 -Xss单个线程堆栈大小值:JDK5.0以后每个线程堆栈大小为1M,以前每个线程 堆栈大小为256K。在相同物理内存下,减小这个值能生成更多的线程。但是操 作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 30005000左右。 -XX:+UseParNewGC可用来设置年轻代为并发收集【多CPU】,如果你的服务器 有多个CPU,你可以开启此参数:开启此参数,多个CPU可并发进行垃圾回收, 可提高垃圾回收的速度。此参数和+UseParallelGC,-XX:ParallelGCThreads搭 配使用。 +UseParallelGC选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上 述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。可提高系统的
有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推), 并将那些被其他对象所引用的 JAVA 对象移动到幸存者 0 区。 如果幸存者 0 区有足够控件存放则直接放到幸存者 0 区;如果幸存者 0 区没有足 够空间存放,则 JVM 的垃圾回收器执行对幸存者 0 区的垃圾回收工作,销毁那些 不再被其他对象引用的 JAVA 对象(如果该对象仅仅被一个没有其他对象引用的 对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他 对象所引用的 JAVA 对象移动到幸存者 1 区。 如果幸存者 1 区有足够控件存放则直接放到幸存者 1 区;如果幸存者 0 区没有足 够空间存放,则 JVM 的垃圾回收器执行对幸存者 0 区的垃圾回收工作,销毁那些 不再被其他对象引用的 JAVA 对象(如果该对象仅仅被一个没有其他对象引用的 对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他 对象所引用的 JAVA 对象移动到养老区。 如果养老区有足够控件存放则直接放到养老区;如果养老区没有足够空间存放, 则 JVM 的垃圾回收器执行对养老区区的垃圾回收工作,销毁那些不再被其他对象 引用的 JAVA 对象(如果该对象仅仅被一个没有其他对象引用的对象引用的话, 此对象也被归为没有存在的必要,依此类推),并保留那些被其他对象所引用的 JAVA 对象。如果到最后养老区,幸存者 1 区,幸存者 0 区和伊甸园区都没有空 间的话,则 JVM 会报告“JVM 堆空间溢出(java.lang.OutOfMemoryError: Java heap space)”,也即是在堆空间没有空间来创建对象。 这就是 JVM 的内存分区管理,相比不分区来说;一般情况下,垃圾回收的速度要 快很多;因为在没有必要的时候不用扫描整片内存而节省了大量时间。 通常大家还会遇到另外一种内存溢出错误“永久存储区溢出 (java.lang.OutOfMemoryError: Java Permanent Space)”。 好,本篇对 SUN 的 JVM 内存管理机制讲解就到此为止,下一篇我们将详细讲解 怎样优化 SUN 的 JVM 让我们的 J2EE 系统运行更快,不出现内存溢出等问题。 2.4 SUN JVM 调优 JVM 相关参数: 参数名 参数说明 -server 启用能够执行优化的编译器, 显著提高服务器的性能,但使用能够执行 优化的编译器时,服务器的预备时间将会较长。生产环境的服务器强烈推荐设置 此参数。 -Xss 单个线程堆栈大小值;JDK5.0 以后每个线程堆栈大小为 1M,以前每个线程 堆栈大小为 256K。在相同物理内存下,减小这个值能生成更多的线程。但是操 作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右。 -XX:+UseParNewGC 可用来设置年轻代为并发收集【多 CPU】,如果你的服务器 有多个 CPU,你可以开启此参数;开启此参数,多个 CPU 可并发进行垃圾回收, 可提高垃圾回收的速度。此参数和+UseParallelGC,-XX:ParallelGCThreads 搭 配使用。 +UseParallelGC 选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上 述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集 。可提高系统的