`
gucci1983
  • 浏览: 3303 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
文章分类
社区版块
存档分类
最新评论

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 *;
            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.
http://mail-archives.apache.org/mod_mbox/harmony-dev/200702.mbox/%3Ce66844de0702012308r619847e4wc66f692d7ac67a8b@mail.gmail.com%3E
分享到:
评论

相关推荐

    xSocket api 2.6.6version

    xSocket api 2.6.6version

    NIO网络框架 xSocket

    NIO网络框架 xSocket

    xsocket NIO框架示例

    xsocket NIO框架示例 resources 中有相关的 资料。telnet服务测试教程。和相关jar

    xsocket 2.5.4 源代码

    xSocket-2.5.4-sources.jar , 2.5.4版的源代码jar包,引入项目即可查看

    xsocket.jar包

    socket通讯框架xsocket所需的jar包

    xsocket使用指南

    xSocket 使用指南 xSocket 是一个轻量级的基于 NIO 的服务器框架,用于开发高性能、可扩展、多线程的服务器。该框架封装了线程处理、异步读写等方面。下面是 xSocket 的一些核心功能和使用指南: 核心功能 ...

    xSocket-2.8.1.jar

    xSocket是一个轻量级的基于nio的服务器框架用于开发高性能、可扩展、多线程的服务器。该框架封装了线程处理、异步读/写等方面。

    轻量级JAVA scoket 服务器XSOCKET

    轻量级JAVA scoket 服务器XSOCKET

    xsocket源码和socket源码,文档

    通过分析这些源码,你不仅可以理解Socket编程的基本原理,还能了解到高级网络编程技巧。对于TCP/IP和UDP的理解也至关重要,因为它们是Socket编程的基础。TCP/IP协议族定义了网络通信的规则,而UDP和TCP是其主要的...

    tcp协议使用xsocket的demo

    通过调试和分析代码,可以学习到如何有效地管理并发连接,保证系统的稳定性和性能。 总的来说,"tcp协议使用xsocket的demo"是一个实践性强、教学价值高的项目,它涵盖了SpringBoot、TCP通信和模块化开发等多个核心...

    XSocket.rar

    在IT行业中,网络编程是不可或缺的一部分,而Socket编程则是实现网络通信的基础。XSocket.rar文件提供的内容显然聚焦...通过研究这个项目,开发者可以深入理解网络通信的核心原理,并能动手实践,提高解决问题的能力。

    ws(websocket)例子(xsocket\xlightweb)

    xLightWeb易于使用,具有低内存占用和高效性能的特点。它允许开发者通过简单的API来创建WebSocket服务,从而快速响应客户端请求,实现双向通信。 4. WebSocket的实现过程: - 建立连接:客户端首先发送一个...

    java源码:NIO网络框架 xSocket.rar

    - **异常处理**:xSocket提供了完善的异常处理机制,确保在网络问题发生时能够优雅地恢复或关闭连接。 6. **应用场景** - **聊天服务器**:高并发的聊天应用,如即时通讯软件,NIO可以有效处理大量用户连接。 - ...

    xSocket sources

    xSocket 是一个开源的、跨平台的网络通信框架,它为Java开发者提供了高效、稳定且易用的网络编程接口。这个压缩包包含了xSocket不同版本的源代码和库文件,让我们来深入了解一下其中的关键知识点。 1. **xSocket ...

    xsocket.7z

    《XSocket:跨平台的C++ Socket库详解》 在软件开发中,网络通信是不可或缺的一部分,而Socket作为网络通信的基础接口,被广泛应用于各种网络应用程序。本文将深入探讨一个名为“XSocket”的C++库,它为开发者提供...

    xSocket-multiplexed-2.1.5-sources.jar

    xSocket-multiplexed-2.1.5-sources.jarxSocket-multiplexed-2.1.5-sources.jarxSocket-multiplexed-2.1.5-sources.jarxSocket-multiplexed-2.1.5-sources.jarxSocket-multiplexed-2.1.5-sources.jarxSocket-...

    基于java的开发源码-NIO网络框架 xSocket.zip

    基于java的开发源码-NIO网络框架 xSocket.zip 基于java的开发源码-NIO网络框架 xSocket.zip 基于java的开发源码-NIO网络框架 xSocket.zip 基于java的开发源码-NIO网络框架 xSocket.zip 基于java的开发源码-NIO网络...

    基于XSocket、mdb、zq构建端到端高性能可视化量化分析交易系统

    "基于XSocket、mdb、zq构建端到端高性能可视化量化分析交易系统"这一项目,就是这样一个综合性解决方案,它涵盖了服务器端、客户端以及多种编程语言的支持。 1. **XSocket**: XSocket 是一个高性能的、跨平台的...

    NIO网络框架 xSocket.7z

    在BIO模型中,每个连接都需要一个独立的线程进行处理,当并发连接数量增大时,服务器需要创建大量线程,这不仅会消耗大量内存,还可能导致线程上下文切换开销增大,从而降低系统性能。而NIO通过引入选择器(Selector...

Global site tag (gtag.js) - Google Analytics