`
jsczxy2
  • 浏览: 1269624 次
  • 性别: Icon_minigender_1
  • 来自: 常州
文章分类
社区版块
存档分类
最新评论

tomcat频繁内存溢出,但是查看jvm并没有不够用,怎么回事?请来设置下启动参数吧

    博客分类:
  • java
阅读更多

XSocket内存泄漏问题深度分析


大概一个月前在一个数据迁移的过程中,在数据迁移到900多W的时候程序崩溃了,系统最后记录的日志是这样的:

 Exception in thread "xDispatcher#CLIENT" java.lang.OutOfMemoryError
        at sun.misc.Unsafe.allocateMemory(Native Method)
        at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:99)
        at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
        at org.xsocket.connection.spi.AbstractMemoryManager.newBuffer(AbstractMemoryManager.java:219)
        at.......

从 中不难看出这是xsocket的内存管理层程序通过JVM的nio创建DirectByteBuffer时抛出了错误。在这里先要说明一下的是,当时的系 统使用的xsocket2.0版,2.0版连接读取数据默认就是使用DirectByteBuffer的,目前2.4.X版已经默认都改为使用 ByteBuffer,而不再是DirectByteBuffer了。
DirectByteBuffer是由jvm调用jni程序在操作系统内存 中分配的空间的,不需要占用JVM的Heap Size。当程序需要读取Big Size或者Huge Size数据的时候,使用DirectByteBuffer优势尤其明显。但使用DirectByteBuffer会产生一个问题,当JVM的空间还没满 但系统内存空间已经被消耗的差不多的时候,gc如何被触发呢。这个问题在一个叫Harmony的开源Java SE项目的mail list中曾进行过热烈的讨论。参考资料1中有当时讨论过程的链接。
在跟踪JVM的一些源码后,发现,很庆幸,sun的jvm已经解决这个问题 了。但是对于尚不清楚其内存处理机制的开发人员来说。在使用DirectByteBuffer是还是很可能把系统置身于莫大的风险中。而很不幸这个问题隐 藏的相当的隐秘,对于不明就里的人,最后可能只好采取定时重启系统或者在系统中设定一些条件显式调用gc来曲线解决资源释放的问题。

题外话说了不少,下面我们直接从JVM的一些代码片段去分析文章最开始的Exception是在什么条件下引发的。首先我们来看看DirectByteBuffer的构建函数代码:

     DirectByteBuffer(int cap) {
        super(-1, 0, cap, cap, false);
        Bits.reserveMemory(cap);
        int ps = Bits.pageSize();
        long base = 0;
        try {
            base = unsafe.allocateMemory(cap + ps);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(cap);
            throw x;
        }
        unsafe.setMemory(base, cap + ps, (byte) 0);
        if (base % ps != 0) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, cap));
    }

注意以上片段中红色加亮的部分,然后我们再来看看Bits.reserveMemory这个方法的代码:

     // -- Direct memory management --

    // A user-settable upper limit on the maximum amount of allocatable
    // direct buffer memory.  This value may be changed during VM
    // initialization if it is launched with "-XX:MaxDirectMemorySize=<size> ".
    private static volatile long maxMemory = VM.maxDirectMemory();
    private static volatile long reservedMemory = 0;
    private static boolean memoryLimitSet = false;

    // These methods should be called whenever direct memory is allocated or
    // freed.  They allow the user to control the amount of direct memory
    // which a process may access.  All sizes are specified in bytes.
    static void reserveMemory(long size) {
        synchronized (Bits.class) {
            if (!memoryLimitSet && VM.isBooted()) {
            maxMemory = VM.maxDirectMemory();
            memoryLimitSet = true;
            }
            if (size <= maxMemory - reservedMemory) {
            reservedMemory += size;
            return;
            }
        }

        System.gc();
        try {
            Thread.sleep(100);
        } catch (InterruptedException x) {
            // Restore interrupt status
            Thread.currentThread().interrupt();
        }
        synchronized (Bits.class) {
            if (reservedMemory + size > maxMemory)
            throw new OutOfMemoryError("Direct buffer memory");
            reservedMemory += size;
        }
    }

从 以上代码我们可以看到,JVM在通过DirectByteBuffer使用OS内存时(无论是分配还是释放),是有统计的,通过跟可使用的最大OS内存 (VM.maxDirectMemory())作比较,如果不够用,那显式调用gc,如果经过gc后还是没有足够的空分配内存,那么从Bit抛出 Exception。注意:这一步并未真正的像OS申请内存,只是VM通过计算得出的结论。而真正想OS申请内存是在 unsafe.allocateMemory这个方法里面通过JNI实现的。显然,文章最初的Exception是由Unsafe抛出而不是Bit抛出。 也就是说JVM认为OS还有足够的可分配内存,而当JVM真正向OS申请内存分配的时候却出错了。那么接下来我们就得看看,这个max direct memory值是如何设定的。

现在我们看看VM.java的代码:

   private static long directMemory = 67108864L;
  ...
  public static long maxDirectMemory()
  {
    if (booted)
      return directMemory;
    Properties localProperties = System.getProperties();
    String str = (String)localProperties.remove("sun.nio.MaxDirectMemorySize");
    System.setProperties(localProperties);
    if (str != null)
      if (str.equals("-1"))
      {
        directMemory = Runtime.getRuntime().maxMemory();
      } else {
        long l = Long.parseLong(str);
        if (l > -1L)
          directMemory = l;
      }
    return directMemory;
  }

可以看出来,max direct memory可以有三种值:
1)默认值,64M;
2)maxMemory,也就是通过-Xmx设定的值,默认也是64M;
3)通过-XX:MaxDirectMemorySize=<size>指定的值;

问 题到这里基本就是水落石出了,当时系统启动的时候设定-Xmx2048M,未指定MaxDirectMemorySize,也就是说max direct memory被认为是2048M,整个系统的物理内存为4G,除掉系统进程占用的内存,剩下的物理内存加上swap空间也就接近3G。设想JVM的 heap size占用了1.5G,direct memory使用了1.5G,这时候程序申请一200M的direct内存,在这种情况下无论是JVM heap size还是direct memory不满足触发gc的条件,于是jvm向os申请分配内存,OS无可分配的内存了就会抛出OOM错误。

补 充说明一下:在OS已经把物理内存+Swap都耗光都不足够分配内存空间的时候,不同OS可能会有不同的表现。LInux的内核有可能会尝试努力腾出更多 的内存空间。可能会杀掉某些进程。(这是参考资料一中Ivan Volosyuk所提出来的问题)。而我在使用以下程序进行测试时,出现整个OS系统假死的状况,过一段时间回复过来了。但整个过程可以肯定的一件事是 gc始终没有被触发到。

以下是我用来验证我的以上分析的测试程序:

 import java.lang.management.ManagementFactory;
import java.nio.ByteBuffer;
import java.util.logging.Logger;

import com.sun.management.OperatingSystemMXBean;

public class TestMemoryLeak {
    private static Logger logger = Logger.getAnonymousLogger();

    public static void main(String[] args) throws Exception{
        OperatingSystemMXBean osmb = (OperatingSystemMXBean) ManagementFactory
                .getOperatingSystemMXBean();
        System.out.println("Total physic memory:"
                + osmb.getTotalPhysicalMemorySize() / 1024 / 1024 + "MB");
        System.out.println("Free physic memory:"
                + osmb.getFreePhysicalMemorySize() / 1024 / 1024 + "MB");
        System.out.println("Max memory:" + Runtime.getRuntime().maxMemory());
        System.out
                .println("Total memory:" + Runtime.getRuntime().totalMemory());
        System.out.println("Free memory:" + Runtime.getRuntime().freeMemory());
        System.out.println("====================================");
        
        //testDirectByteBuffer();
        testByteBuffer();
        
        System.out.println("====================================");
        System.out.println("Free physic memory:"
                + osmb.getFreePhysicalMemorySize() / 1024 / 1024 + "MB");
    }
   
    public static void testDirectByteBuffer(){
        try{
            ByteBuffer bb1 = ByteBuffer.allocateDirect(1024 * 100000 * 4);
            bb1 = null;
            System.out.println("Free memory:" + Runtime.getRuntime().freeMemory());
            ByteBuffer bb2 = ByteBuffer.allocateDirect(1024 * 100000 * 5);
            bb2 = null;
            System.out.println("Free memory:" + Runtime.getRuntime().freeMemory());
            //System.gc();
            //pause expect for gc
            Thread.sleep(1000*6);
            ByteBuffer bb3 = ByteBuffer.allocateDirect(1024 * 100000 * 8);
            System.out.println("Free memory:" + Runtime.getRuntime().freeMemory());    
        }catch(Exception e){
            e.printStackTrace();
        }
    }   
    public static void testByteBuffer(){
        try{
            ByteBuffer bb1 = ByteBuffer.allocate(1024 * 100000 * 4);
            bb1 = ByteBuffer.allocate(1024 * 10 * 4);
            System.out.println("Free memory:" + Runtime.getRuntime().freeMemory());
            ByteBuffer bb2 = ByteBuffer.allocate(1024 * 100000 * 3);
            bb1 = ByteBuffer.allocate(1024 * 10 * 3);
            System.out.println("Free memory:" + Runtime.getRuntime().freeMemory());
            //System.gc();
            //pause expect for gc
            Thread.sleep(1000*6);
            ByteBuffer bb3 = ByteBuffer.allocate(1024 * 100000 * 6);
            System.out.println("Free memory:" + Runtime.getRuntime().freeMemory());    
        }catch(Exception e){
            e.printStackTrace();
        }        
    }

}

程序启动需用如下命令:
java -verbose:gc -Xms64M -Xmx512M -XX:MaxDirectMemorySize=1000M TestMemoryLeak
-verbose:gc用于开启gc运行情况的输出,可以帮助我们了解jvm垃圾收集的运作情况;
其他参数值应该你机器的实际情况设定。

最后我想总结的是,当我们在使用DirectByteBuffer的时候一定要注意:
1)谨慎设定JVM运行参数,最好用-XX:MaxDirectMemorySize进行设定,否则你就得清楚你设定的mx不单单制定了heap size的最大值,它同时也是direct memory的最大值;
2)在密集型访问中(例如数据迁移工具)适当增加对gc的显式调用,保证资源的释放;

参考资料:
[VM]How to trigue GC while free native memory is low.

分享到:
评论
1 楼 smalleyeit 2012-08-21  

相关推荐

    tomcat6.0 修改启动内存设置 java jvm参数配置

    ### Tomcat 6.0 修改启动内存设置及 Java JVM 参数配置详解 #### 一、背景与目的 在部署和运行 Java Web 应用时,合理地配置应用服务器(如 Apache Tomcat)的内存是非常重要的。这不仅可以提升应用程序的性能,还...

    JVM内存溢出问题解析

    JVM 内存溢出问题解析 JVM 内存溢出是指程序运行所需的内存大于虚拟机能提供的最大内存的情况。这种情况可能是由于数据量过大、死循环、静态变量和静态方法过多、递归、无法确定是否被引用的对象等原因引起的。同时...

    Tomcat JVM内存设置方法

    默认情况下,Tomcat的JVM内存设置可能无法满足所有应用程序的需求,特别是在资源密集型应用或高并发场景下。因此,了解如何手动调整Tomcat的JVM内存设置至关重要。 #### 二、理解JVM内存模型 在深入探讨如何设置...

    myeclipse-tomcat jdk内存溢出

    根据提供的描述“myeclipse-tomcat jdk内存溢出,针对myeclipse做的相应的配置”,我们可以了解到该问题是由于MyEclipse中Tomcat服务器所使用的JDK设置不当导致的JVM内存不足。接下来,我们将详细探讨这一问题的具体...

    eclipse tomcat启动,内存溢出问题

    3. **配置不当**:在配置文件中,如果没有正确地设置JVM参数来调整内存大小,也会导致内存溢出。 #### 三、解决内存溢出问题的方法 针对上述问题,我们可以采取以下措施来解决Eclipse中Tomcat启动时遇到的内存溢出...

    Eclipse + Tomcat 内存溢出参数设置

    ### Eclipse + Tomcat 内存溢出参数设置详解 在Java开发过程中,特别是在使用Eclipse作为集成开发环境(IDE)并结合Apache Tomcat服务器进行Web应用开发时,经常会遇到内存溢出的问题。这类问题通常表现为应用程序...

    TOMCAT内存溢出之解决方法

    为了解决Tomcat内存溢出问题,主要是加大Tomcat可利用内存,并在程序当中加大内存使用。具体解决方法如下: 1. 加大Tomcat可利用内存: 在Tomcat的目录下,也就是在Tomcat41/bin/catalina.bat文件最前面加入set ...

    关于tomcat乱码以及tomcat jvm 内存溢出问题的解决方案和理论

    标题中的“关于tomcat乱码以及tomcat jvm 内存溢出问题的解决方案和理论”涉及了两个关键的IT概念:Tomcat服务器的字符编码问题和Java虚拟机(JVM)内存管理的问题。让我们逐一深入探讨这两个主题。 首先,我们来...

    解决tomcat内存溢出的配置方法

    1. **调整JVM参数**:在Tomcat的启动脚本(如`catalina.sh`或`catalina.bat`)中,可以通过设置JVM的启动参数来控制内存大小。常用的是-Xms和-Xmx参数,分别代表初始堆内存和最大堆内存。例如,可以设置为`-Xms256m ...

    Tomcat JVM的参数调优

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

    Tomcat设置服务启动参数

    ### Tomcat设置服务启动参数详解 #### 一、引言 Apache Tomcat 是一款开源的Servlet容器,主要用于部署和运行Java Web应用。为了确保Tomcat能够高效稳定地运行,合理配置其启动参数至关重要,尤其是与内存相关的...

    tomcat7安装使用及jvm连接数参数调优

    Tomcat7 安装使用及 JVM 连接数参数调优 Tomcat7 是一个流行的 Java Web 服务器,主要用于部署和管理基于 Java 的 Web 应用程序。为了确保 Tomcat7 的稳定运行和高效性能,需要对其进行合理的安装、配置和优化。...

    java excel导出tomcat内存溢出处理方法

    当使用Tomcat部署Java应用程序时,也可以通过调整Tomcat的JVM参数来优化内存配置。 1. 打开Tomcat的安装目录下的`bin`文件夹,找到`catalina.bat`文件。 2. 在文件顶部添加以下配置: ``` set JAVA_OPTS=-Xms1024...

    设置tomcat启动内存大小

    在IT领域,特别是Java应用服务器的管理与优化中,合理设置Tomcat的内存参数是确保应用稳定运行的关键步骤之一。本文将围绕“设置Tomcat启动内存大小”这一主题,深入探讨如何通过修改配置文件来调整Tomcat服务器的...

    Tomcat内存溢出的三种情况及解决办法分析

    Tomcat内存溢出的三种情况及解决办法分析 Tomcat内存溢出是指Tomcat服务器在运行过程中无法分配足够的内存空间...Tomcat内存溢出是由于内存空间不足所致,解决方案是调整参数和调整操作系统和Tomcat JVM参数同时调整。

    java虚拟机jvm及Tomcat中的jvm有关内存的设置与调优

    特别是在大数据处理场景下,合理设置JVM内存参数显得尤为重要。本文将深入探讨JVM内存管理的基础概念、内存设置方法及其调优策略,以帮助开发者避免常见的`java.lang.OutOfMemoryError`异常,提升应用性能。 #### ...

    如何设置Tomcat的JVM虚拟机内存大小

    设置Tomcat的JVM虚拟机内存大小是为了确保服务器能够高效地运行并避免因内存溢出而导致的性能问题或服务中断。 内存溢出是由于程序在运行过程中需要的内存超过了JVM所能提供的最大内存。这可能是由于以下几个原因:...

Global site tag (gtag.js) - Google Analytics