`

JVM调优:Tomcat Context reloadabled与OutOfMemory(PermSpace)

 
阅读更多

转载自:http://ayufox.iteye.com/blog/646125

 

我们知道,Sun JVM分代垃圾回收器把堆空间分成3块:

  • Young Gen:年轻代,包括1个Eden区和2个Suvivor区,新创建的对象(大部分为短周期的对象)将进入这个区,虚拟机会频繁地对这个区进行垃圾回收。
  • Old Gen:年老代,当对象在Young Gen呆地足够久(经过几次的垃圾回收仍然存在)或Young Gen空间不足时,对象将进入Old Gen,由于一般是生命周期比较长的对象,因此虚拟机对这块内存的回收频度会比较低,一旦回收,使用的将是一个耗时的Full GC,另外,一旦堆空间不足时,虚拟机也会尝试去回收这个区。
  • Perm Gen:持久代,一些常量定义和类、方法声明及其bytecode都会放在这个区

      问题在于,Perm Gen又是什么样的回收策略呢?

      一直,我都以为Sun JVM对Perm Gen使用的是不垃圾回收的策略,这个观点起源于我之前的一个测试,使用Tomcat Context的reloadabled=”true”,而且项目的jar包比较多,则只要修改几次(事实上是3次)的类定义,然后Tomcat Reload之后,WOW,抛出OutOfMemory(PermSpace)的异常。
      最近,有个同事在做一个项目的时候也碰到这个问题,他是需要应用到IDC环境下的,动态根据配置生成一些新的jar包,在不重新Tomcat的情况下,利用Context的reload属性,自动把新生成的jar加载进来。我当时给他的答案是:

  • 可以使用JVMTI技术,JDK 6新引入的启动后Instrutment,在生成jar包之后,启动Attach到当前JVM,将旧的class卸栽掉(本来也可以使用JVM的debug模式——也是利用这种技术——但debug模式对性能影响会更大)。但限制比较大,类声明不允许发生变更(譬如不能新增属性、方法或者修改方法声明),事实上也不符合他当前的需求。
  • 学过ClassLoader的同学都知道,类定义一旦加载,即使你修改ClassLoader的加载方式,强制再加载一次,也是不行的,因为加载的类定义会被JVM Cache,是不允许重新再加载的。那么Tomcat reloadable是怎么做的呢?因为JVM识别两个类定义是否一致的方法是根据加载的ClassLoader和类全名(即包括package名和类名),那么Tomcat reloadable可以重新加载同一个类定义方法是,将context旧的WebappClassLoader丢弃,创建一个新的WebappClassLoader来达到加载新的类定义的。其实就是将context stop再start,所以代价非常昂贵。这种方式相当于在PermSpace又存放了一份jar定义,所以会导致PermSpace猛增。出现OutOfMemory(PermSpace),说明对旧的class没有回收,建议可以看看JVM的启动参数,有没有对PermSpace垃圾回收的选项。

        由于我之前错误的观点,我以为Sun JVM的PermSpace是不垃圾回收的,为了证明这一点,我查了一些资料;

JVM参考规范 写道
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This version of the Java virtual machine specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.


       JVM规范并没有规定实现对方法区(PermSpace)一定要垃圾回收,“simple implementations may choose not to either garbage collect or compact it”。很不爽的”may”,当然不能作为问题的答案。
       重新猜想,也许JVM的启动参数是可以指定的,确实如此,然而确是很明确地指出我的想法是错误的:

-Xnoclassgc
Disable class garbage collection. Use of this option will prevent memory recovery from loaded classes thus increasing overall memory usage. This could cause OutOfMemoryError to be thrown in some applications.

 

      也就是说,默认情况下,JVM是会对这块区域进行垃圾回收的。难道是由于Tomcat的策略导致的?我们一步一步地来进行验证
      准备工作:建立测试项目test2(引用的jar包不要太大也不能太小,我建立的项目启动后JVM的PermSpace占用大概20M),在tomcat下配置该应用的reloadabled=”true”。
       猜测1:Tomcat废弃WebappClassLoader没有被置null,导致相应的引用类都没有非卸载。验证步骤如下(Tomcat测试版本是6.0.14):
       步骤1:启动Tomcat,并启动JConsole,监控,并dump下堆内存。
       步骤2:多次修改测试代码,引起Tomcat的reload生效,每次强制在JConsole上进行GC,并dump下堆内存。
       结果如下(JConsole监控图):
 
       可以看到,PermSpace在reload的时候,内存会有一个陡增,第一个陡增没有下降下来,第二个陡增后都可以通过GC下降下来,猜测是Tomcat对一个Context,在reload后会维持两个WebappClassLoader,一个当前的WebappClassLoader和一个历史的WebappClassLoader。我们通过jhat分析dump出来的堆文件证实了有2个WebapClassLoader这种猜测

2 instances of class org.apache.catalina.deploy.NamingResources
2 instances of class org.apache.catalina.loader.WebappClassLoader
2 instances of class org.apache.catalina.mbeans.MBeanFactory

      注意:用其他的tomcat版本做了相同的测试,6.0.20版本与此版本测试结果一致,6.0.26版本结果会比较好,只维持一个WebappClassLoader,即上面的JConsole曲线第一次上升之后也可以通过GC降下来。
      猜测2:Tomcat在新的WebappClassLoader把其他类加载进来之前,不会将旧的WebappClassLoader置null。验证步骤如下(Tomcat测试版本是6.0.14):
      我们先看一下Tomcat中context reload方法,可以看到reload其实就是一次stop和一次start

Java代码 复制代码 收藏代码
  1. public synchronized void reload() {   
  2.     // Validate our current component state   
  3.     if (!started)   
  4.         throw new IllegalStateException   
  5.             (sm.getString("containerBase.notStarted", logName()));   
  6.     setPaused(true);   
  7.     try {   
  8.         stop();   
  9.     } catch (LifecycleException e) {   
  10.         log.error(sm.getString("standardContext.stoppingContext"), e);   
  11.     }   
  12.     try {   
  13.         start();   
  14.     } catch (LifecycleException e) {   
  15.         log.error(sm.getString("standardContext.startingContext"), e);   
  16.     }   
  17.     setPaused(false);   
  18. }  
    public synchronized void reload() {
        // Validate our current component state
        if (!started)
            throw new IllegalStateException
                (sm.getString("containerBase.notStarted", logName()));
        setPaused(true);
        try {
            stop();
        } catch (LifecycleException e) {
            log.error(sm.getString("standardContext.stoppingContext"), e);
        }
        try {
            start();
        } catch (LifecycleException e) {
            log.error(sm.getString("standardContext.startingContext"), e);
        }
        setPaused(false);
    }

 

       步骤1:将tomcat的JVM启动参数中设置调试模式,在StanardContext的reload的start那里设一个断点,确保start之前会停住,我们将观察这种时候进行垃圾回收能否将PermSpace空间回收
       步骤2:启动JConsole,attach到Tomcat进程上
       步骤3:修改测试代码,引起Tomcat的reload生效,在第二次reload  start停止的时候,使用JConsole强行做一个垃圾回收(注意,如果是6.0.26版本,应该在第一次reload的时候观察)
        可以看到JConsole的图如下
 
       可以看到,在start停住的时候,我们进行一个GC后,PermSpace会有一个陡降,说明在stop的时候,已经会把WebappClassLoader中的内存释放掉
       猜测3:JVM在PermSpace空间不足的时候,并不会试图先去回收PermSpace
       一般情况下,我们当然无法在start之前停住,即使在这个阶段强行做一个System.gc(),然而gc的调用无法预料的,假设在start之前PermSpace内存没有被释放,而start的时候又会在PermSpace分配大量的内存,如果此时PermSpace空间不足,是否会引起JVM对PermSpace进行垃圾回收呢?
       验证步骤如下(Tomcat测试版本是6.0.14):
       步骤1:从上面我们可以看到,2个WebappClassLoader及其他classloader加载的类占用的PermSpace空间大概是25M,因此我们在启动参数中设置PermSpace最大的空间值为26M(-XX:MaxPermSize=26MB)
       步骤2:修改测试代码,引起reload,在第2次reload的时候。WOW,OutOfMemory

Caused by: java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)

       可以看到,非常遗憾,JVM在PermSpace空间不足的时候,并不会先主动去释放PermSpace空间。
       我们可以得出如下结果:

  • 对于Tomcat6.0.20及以下版本,要保证reload的时候不出现OutOfMemory的错误,必须维持PermSpace空间为:其他loader加载的类定义占的空间+3 * 应用类占的空间(WebappClassLoader加载)
  • 对于Tomcat6.0.26及以下版本,要保证reload的时候不出现OutOfMemory的错误,必须维持PermSpace空间为:其他loader加载的类定义占的空间+2 * 应用类占的空间(WebappClassLoader加载)

 

分享到:
评论

相关推荐

    JVM面试资料:JVM结构、JVM调优、四大垃圾回收算法、七大垃圾回收器

    JVM面试资料。 JVM结构:类加载器,执行引擎,本地方法接口,本地内存结构; 四大垃圾回收算法:复制算法、标记-清除算法、标记-整理算法、分代收集算法 ...JVM调优:命令行指令,设置堆内存大小的参数

    推荐一些JVM原理,JVM调优,JVM内存模型,JAVA并发 电子书1

    标题中提到了JVM原理、JVM调优、JVM内存模型和JAVA并发,这些都是Java虚拟机(JVM)相关的核心概念。JVM是运行Java字节码的虚拟计算机,为Java提供了一个跨平台的环境,确保Java程序可以在不同的操作系统上运行而...

    JVM调优总结

    1.1 JVM调优总结-序3 1.2 JVM调优总结(一)-- 一些概念 4 1.3 JVM调优总结(二)-一些概念 7 1.4 JVM调优总结(三)-基本垃圾回收算法 9 1.5 JVM调优总结(四)-垃圾回收面临的问题 12 1.6 JVM调优总结(五)-分代...

    JVM调优实践 ⼀、JVM调优准备⼯作 实验报告 pdf

    Java虚拟机(JVM)是Java程序运行的基础,它的调优是提高应用程序性能的关键环节。在JVM调优实践中,了解各个运行时数据区的工作原理至关重要。以下是对这些区域的详细解析: 1. **虚拟机栈**:每个线程都有一个...

    tomcat out of memory

    标题 "Tomcat Out of Memory" 指的是在运行Apache Tomcat服务器时遇到的一种常见问题,即内存溢出。这通常发生在Tomcat处理大量请求、加载过多应用或资源,或者配置不当导致内存分配不足时。当Java虚拟机(JVM)无法...

    JVM调优.pdf

    ### JVM调优详解 #### 一、JVM调优概述 在现代软件开发中,Java虚拟机(JVM)作为Java应用程序的运行环境,对于提高应用程序的性能至关重要。JVM调优是指通过调整JVM的各种参数来优化Java应用程序的运行效率,减少...

    JVM调优工具命令详解.pdf

    在现代的软件开发与运行环境中,Java虚拟机(JVM)的性能调优是非常重要的一环,特别是在处理大型应用程序或者服务时,合适的JVM调优能够显著提升系统性能和稳定性。本篇文档详细介绍了JVM调优工具的命令使用及其...

    JVM调优总结:调优方法.docx

    - **持久代满异常**:`java.lang.OutOfMemoryError: PermGen space`,可能由于大量动态反射类加载导致,可通过调整`MaxPermSize`参数或更换JVM版本来解决。 - **堆栈溢出异常**:`java.lang.StackOverflowError`,...

    马士兵jvm调优笔记.docx

    ### 马士兵JVM调优笔记知识点梳理 #### 一、Java内存结构 Java程序运行时,其内存被划分为几个不同的区域,包括堆内存(Heap)、方法区(Method Area)、栈(Stack)、程序计数器(Program Counter Register)以及...

    monkey老师的jvm 调优

    《Monkey老师的JVM调优深度解析》 在Java开发领域,JVM(Java Virtual Machine)是每一个程序员都需要深入了解的关键组成部分。Monkey老师的JVM调优课程,无疑为我们提供了一个宝贵的平台,来深入探究JVM的工作原理...

    java高级篇:JVM调优.mp4

    分析常见的JVM调优技术及其相关条件,以及在实战中怎么使用到相关的技术进行调优,比较适合中高级开发进行学习,适合对JVM理解较深的开发学习

    jvm调优的实际应用

    《JVM调优实战解析》 在Java开发领域,JVM(Java Virtual Machine)是运行所有Java应用程序的基础,它的性能直接影响着程序的运行效率。因此,掌握JVM调优技术对于提升系统的稳定性和性能至关重要。本文将围绕"JVM...

    JVM调优篇.pdf

    JVM类加载机制详解 JVM类加载机制是Java虚拟机中的一种机制,它负责加载Java类文件到内存中,以便执行Java程序。类加载机制分为五个阶段:加载、验证、准备、解析和初始化。 加载 加载是类加载过程中的一个阶段,...

    JVM调优实战(转)

    JVM调优实战 本文档旨在介绍JVM调优实战的各个方面,包括JVM内存、垃圾回收、性能优化等。通过对JVM内存结构、垃圾回收机制和性能优化策略的详细讲解,帮助读者深入理解JVM的工作原理和优化方法。 1. JVM内存结构 ...

    JVM调优实战.doc

    如果老年代也无法容纳,就会出现"Out of Memory"错误。 实战篇涉及如何进行JVM调优,包括明确调优目标、准备测试环境、录制测试脚本、定义测试场景、执行性能测试、选择调优方案等步骤。调优过程中会观察JVM监控图...

    马老师jvm调优实战笔记

    马老师 JVM 调优实战笔记 JVM 调优是 Java 开发者们不可或缺的技能,它直接影响着 Java 应用程序的性能和稳定性。本笔记是马老师的 JVM 调优实战笔记,涵盖了 JVM 的概述、内存结构、堆内存、垃圾回收算法、JVM ...

    Tomcat JVM的参数调优

    【标题】"Tomcat JVM参数调优"涉及的是在运行Apache Tomcat服务器时优化Java虚拟机(JVM)性能的过程。Tomcat是一个流行的开源Java应用服务器,它用于部署和运行Java Servlets和JavaServer Pages(JSP)。由于JVM是...

    JVM调优总结 JVM调优总结

    本文将围绕JVM调优展开讨论,重点分析数据类型、堆与栈的关系及其对程序性能的影响,并探讨Java中的参数传递机制。 #### 二、数据类型 在Java虚拟机中,数据类型分为两大类:基本类型和引用类型。 - **基本类型**...

    深入虚拟机---JVM调优总结(摘自网上网上大牛分享).pdf

    在深入讨论JVM(Java虚拟机)调优之前,我们有必要先了解一下虚拟机的基本概念和堆栈的区分。Java程序在运行时,所有的数据都存储在JVM的内存模型中。在内存模型中,有两大重要区域,即堆(Heap)和栈(Stack)。堆主要...

Global site tag (gtag.js) - Google Analytics