zoukankan      html  css  js  c++  java
  • Java堆外内存之六:堆外内存溢出问题排查

     一、堆外内存组成

    通常JVM的参数我们会配置

    -Xms 堆初始内存
    -Xmx 堆最大内存
    -XX:+UseG1GC/CMS 垃圾回收器
    -XX:+DisableExplicitGC 禁止显示GC
    -XX:MaxDirectMemorySize 设置最大堆外内存,默认是-xmx-survivor,也就是基本上和-xmx大小相等
    -Xss:每个线程的堆栈大小,默认1M
    -Xmn: 年轻代大小(eden区+2 survivor)
    -XX:newRatio: 4 年轻代与老年代1:4
    -XX:survivorRatio: 8Eden区与survivor大小比值

    java整个进程占用的内存:
    - 堆内存
    - metaspace(堆内) JDK8使用metaspace来替代了permsize:永久代大小
    - 堆外内存使用
    - 线程栈空间

    堆外内存回收: 堆外内存的回收是通过system.gc()来的,依赖于目前的gc机制。
    通常是通过DirectByteBuffer对象来分配堆外内存,gc的时候就是判断这个对象是否被引用,来决定是否回收。

    二、堆外内存参数配置

    -XX:InitialCodeCacheSize=64M
    -XX:CodeCacheExpansionSize=1M
    -XX:CodeCacheMinimumFreeSpace=1M
    -XX:ReservedCodeCacheSize=200M
    -XX:MinMetaspaceExpansion=1M
    -XX:MaxMetaspaceExpansion=8M
    -XX:MaxDirectMemorySize=96M
    -XX:CompressedClassSpaceSize=256M

     三、问题排查

    3.1、首先确认堆占用

    1、用jmap,jmap 查看heap内存使用情况

    jmap -heap pid

    可以查看到MetaspaceSize,CompressedClassSpaceSize,MaxMetaSize
    jmap和jdk版本有关系,有些jdk版本会查看不到内存信息,可以使用jstat来查看统计信息

    2、jstat 收集统计信息

    jstat -gc pid 1000
    S0C/S0U            S1C/S1U           EC/EU    CCSC/CCSU                  YGC/YGCT          FGC/FCGT         GCT
    survivor0容量和使用 survivor1容量和使用 Eden jdk8是meta,以前应该是PC,PC young gc次数和耗时 full gc次数和耗时 total gc时间

    如果能排除掉heap的问题,就要分析堆外内存情况了。

    3.2、分析堆外情况

    NMT(native memory tracking)
    使用
    在JVM参数中添加 -XX:NativeMemoryTracking=[off | summary | detail]

    -XX:NativeMemoryTracking=detail

    在JVM运行过程中,使用jcmd获取相关信息
    jcmd pid VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]

    jcmd pid VM.native_memory detail

    baseline个基准,之后会输出diff参数,来和这个基线版本进行比较,可以两次的内存差

    NMT报告会显示内存使用情况

    类别                  含义
    Java Heap        堆大小
    Thread              线程
    Thread Stack    线程栈

    NMT可以得到线程栈大小,排除栈空间影响。

    pmap 查看进程内存地址空间

    pmap -x pid | sort xx

    可以结合pmap,和nmt得到内存地址空间。和堆外占用情况了。

    接下来需要做的就是分析堆外内存的内容了。

    gdb dump查看内存空间内容

    gdb dump查看内存空间内容

    (gdb) dump binary memory ./file BEGIN_ADDRESS END_ADDRESS

    将内存内容dump到文件中,就可以查看到文件中的内容了。
    但是这种方式不直观,所以可以使用其他工具

    gperf
    google的,使用gperf2.5即可,网上很多安装都说一定要安装libunwind,其实都是瞎抄抄,老版本确实需要,2.5的版本不需要了。

    https://blog.csdn.net/unix21/article/details/79161250
    另外一个注意点就是虽然heap文件只有1M,但是可以分析出堆外内存的大小。
    不过我在实际使用过程中,gperf并没有分析出实际的堆外内存情况,通过pmap可以看出堆外内存占用有几个G,但是gperf始终只有200M

    Jemalloc
    https://github.com/jemalloc/jemalloc/releases
    安装

    ./configurate –enable-prof
    make
    sudo make install
    配置

    export LD_PRELOAD=/usr/local/lib/libjemalloc.so
    export MALLOC_CONF=prof:true,lg_prof_interval:31,lg_prof_sample:17,prof_prefix:/output/jeprof
    https://github.com/jemalloc/jemalloc/wiki/Getting-Started

    环境:基于BS的点子考试系统,为了发现客户端能实时地从服务端接收考试数据,系统使用了逆向AJAX技术(也称Comet或Server Side Push),选用CometD1.1.1作为服务端推送框架,服务器是Jetty7.1.4,硬件为一台普通PC机,Core i5 CPU,

            4G内存,运行32位Windows操作系统。

    说明:测试期间发现服务端不定时抛出内存溢出异常,服务器不一定每次都会出现异常,但是假如正式考试时奔溃一次,那估计整场考试都会全乱套,网站管理员尝试过把堆开到最大,32位系统最多到1.6GB基本无法再加大了,而且开大量也基本没效果,抛出

            内存溢出异常好像更加繁琐了。加入-XX:+HeapDumpOnOutOfMemoryError,居然也没有任何反应,抛出内存溢出异常时什么文件都没产生。无奈之下只好挂着jstat使劲盯屏幕,发现GC并不频繁,Eden区,Survivor区,老年代及拥挤代内存全部

           表示"情绪稳定,压力不大",但是照样不停的抛出内存溢出异常,管理员鸭梨很大。最后,在内存溢出后从系统日志中找到异常堆栈。

    分析:大家都知道操作系统对每个进程能管理的内存是有限的,这台服务器使用的32位Windows平台的限制是2GB,其中给了Java堆1.6GB,而Direct Memory 并不算在1.6GB的堆之内,因此它只能在剩余的0.4GB空间分出一部分。在此应用中导致内 

            存溢出的关键是:垃圾收集进行时,虚拟机虽然会对Direct Memory进行回收,但是Direct Memory 却不能像新生代和老年代那样,发现空间不足了就通知收集器进行垃圾回收,他只能等到抛出内存溢出异常时,先catch掉,再在catch块里面“大喊”

            “System.gc”.要是虚拟机还是不听(如:打开了-XX:+DisableExplicitGC开关),那就只能眼睁睁地看着堆中还有许多空闲内存,自己却不得不抛出内存异常了。而本案例中使用的Comet1.1.1框架,正好有大量的NIO操作需要用到Direct Memory。

    总结:从实践经验来看,除了java堆和永久代之外,我们注意到下面这些区域也会占用较多的内存,这里所有的内存总和会受到操作系统进程最大内存的限制:

            1.Direct Memory:可以通过-XX:MaxDirectMemorySize调整大小,内存不足时抛出OutOfMemoryError或OutOfMemoryError:Direct buffer memory。

            2.线程堆栈:可通过-Xss调整大小内存不足时抛出StackoverflowErroe(纵向无法分配,即无法分配新的栈帧)或OutOfMemoryError:unable to create new native thread(横向无法分配,即无法建立新的线程)。

            3.Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB的内存,连接多的话这块内存占用也比较可观。如果无法分配,则可能会抛出IOException:Too many open files异常。

            4.JNI代码:如果代码中使用JNI调用本地库,那么本地库使用内存也不在堆中

            5.虚拟机和GC:虚拟机和GC的代码执行也要消耗一定的内存。

  • 相关阅读:
    cors
    js 离开页面
    移动端 touch
    eclipse
    java获取classpath以外的路径
    Javascript类型转换的规则
    myeclipse开发安装C++
    MyEclipse10中配置开发Python所需要的PyDev 绝对靠谱 不忽悠!
    POI生成EXCEL,公式不自动执行的有关问题
    POI中设置Excel单元格格式样式(居中,字体,边框等)
  • 原文地址:https://www.cnblogs.com/duanxz/p/6089421.html
Copyright © 2011-2022 走看看