美文网首页
Java所占内存中神奇的64MB

Java所占内存中神奇的64MB

作者: 明翼 | 来源:发表于2020-06-13 13:33 被阅读0次

    一 前言

    在生产环境,Java应用程序设置了最大JVM内存后,经常发现实际使用的内存,可能超过设置的JVM最大内存数jmap -heap pid 通过这个命令可以方便查看java的内存分配情况。一般情况下,是因为系统使用了堆外内存,比如使用了netty框架,里面就使用了堆外内存,堆外内存减少了java的heap和系统直接内存的复制,所以在网络应用中作为连接的缓存使用比较合适。堆外内存默认使用的最大大小是JVM虚拟机的最大内存大小,当然可以通过-XX:MaxDirectMemorySize来限定最大大小,有些情况有效,有些情况无效,无效的情况下,此篇文章是针对无效的一种可能情况分析。

    二 堆外内存使用查看

    由于堆外内存是调用系统默认的内存分配器分配内存的,所以查看内存不能仅从java上面看,最好还要看下各类内存占用情况分类,可以用pmap 命令或者直接cat /proc/pid/maps 来查看内存来的更直接:

    [root@izbp14xswj2tx6qgnz9dllz ~]# pmap 2888|grep anon
    0000000000ed3000    132K rw---   [ anon ]
    00007efea4461000     20K rw---   [ anon ]
    00007efea4c70000     84K rw---   [ anon ]
    00007efea4e97000     20K rw---   [ anon ]
    00007efea4ea4000      8K rw---   [ anon ]
    00007efea4ea8000      4K rw---   [ anon ]
    00007fff795e9000      8K r-x--   [ anon ]
    ffffffffff600000      4K r-x--   [ anon ]
    

    来查看内存分配,匿名内存多数是不是文件或动态库映射的内存,申请的heap内存就在这里面。
    为了方便说明,我写个java的测试程序,代码如下:

    import java.nio.ByteBuffer;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    
    public class test   {
    
        private static int i;
        private static CountDownLatch cd = new CountDownLatch(10);
    
        public static void main(String[] args) throws InterruptedException {
            int i = 0;
            CountDownLatch cd = new CountDownLatch(10);
            List<Thread> lst = new ArrayList<>();
            for (int j = 0; j < 10; j++) {
                lst.add(new Thread() {
                    @Override
                    public void run() {
                        System.out.println("Thread" + Thread.currentThread().getName()+"Start.");
                        ByteBuffer buffer = ByteBuffer.allocateDirect(1);
                        try {
                            Thread.sleep(500000);
                            cd.countDown();
                            System.out.println("Thread" + Thread.currentThread().getName()+"Over.");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                    }
                });
            }
            for (Thread thread : lst) {
                thread.start();
            }
            cd.await();
            System.out.println("Running is over!");
        }
    }
    

    如果直接简单编译运行:

    javac test.java
    java test
    

    这时候直接用top看占用内存大概为19MB左右,但是用pmap去查看的时候觉得有些异常,显示大概如下:

    [root@localhost ~]# pmap 10377
    10377:   java -Xmx1g -XX:NativeMemoryTracking=summary test
    0000000000400000      4K r-x-- java
    0000000000600000      4K rw--- java
    0000000001d46000    132K rw---   [ anon ]
    00000000c0000000 339968K rw---   [ anon ]
    00000000d4c00000 359424K -----   [ anon ]
    00000000eab00000 169984K rw---   [ anon ]
    00000000f5100000 179200K -----   [ anon ]
    ...
    ...
    ffffffffff600000      4K r-x--   [ anon ]
     total          4220956K
    

    占用内存4220956K,明显比我们内存中申请的要多的多,算起来有4GB,虽然没有真正使用,但是占这么多内存,也有点奇怪,来分析下内存:

    [root@localhost ~]# pmap 10377|grep anon|grep 65404K
    00007fe270021000  65404K -----   [ anon ]
    00007fe274021000  65404K -----   [ anon ]
    00007fe278021000  65404K -----   [ anon ]
    ...
    

    统计下:

    [root@localhost ~]# pmap 10377|grep anon|grep 65404K|wc -l
    22
    

    这些匿名的内存65404K 即是神秘的64MB内存,这些内存来自哪里那?原来是java堆外内存,调用系统的默认的c的内存分配器来分配内存,glibc在 2.11版本以后,其内存分配器ptmalloc2为每个线程都分配了一个thread arena,以前只有一个main thread arena。

    一个32位的应用程序进程,最大可创建 2 CPU总核数个arena内存池(MALLOC_ARENA_MAX),每个arena内存池大小为1MB。
    一个64位的应用程序进程,最大可创建 8 CPU总核数个arena内存池(MALLOC_ARENA_MAX),每个arena内存池大小为64MB

    三 如何解决

    3.1 更改配置参数

    我们可以限制arena内存池的总个数,通过设置环境变量来更改:

    export MALLOC_ARENA_MAX=4
    

    3.2 用TCMalloc内存分配器来替换ptmalloc2

    TCMalloc 库全称Thread-Caching Malloc 是谷歌开源工具google-perftools中的成员,是一个内存分配器,这个内存分配器与我们上面说的linux系统下默认的glibc中的ptmalloc2内存分配器有什么不同那。
    主要优点:

    • tcmalloc 一次malloc或free操作更快,据说ptmalloc2需要300ns,tcmalloc 需要50ns。
    • tcmalloc 优化小对象的存储,需要更少的空间。
    • tcmalloc 对多线程分配内存,小对象基本不存在锁竞争,因为分配器会给现场分配本地缓存,长期空闲的情况下也不会被回收。
      当然也不能无脑的都采用这种方式,因为TCMalloc 虽然适合多线程情况下的小内存分配,但是如果大内存分配,大于256KB的内存分配,性能并没有ptmalloc2好。

    四 TCMalloc 安装和使用

    4.1 基本前置工具安装:

    yum -y install gcc make
    yum -y install gcc gcc-c++
    yum -y perl
    

    4.2 libunwind 安装

    这个为什么安装我不是太明白,在64位系统下必须安装,据说gperftools使用glibc内置的stack-unwinder可能会引发死锁。

    wget http://download.savannah.gnu.org/releases/libunwind/libunwind-1.1.tar.gz
    tar xvf libunwind-1.1.tar.gz
    cd libunwind-1.1
    ./configure && make && make install
    

    4.3 安装perftools

     wget http://code.google.com/p/google-perftools/gperftools-2.7.tar.gz
    
    tar zxvf google-perftools-2.7.tar.gz
    ./configure
    make
    make install
    

    4.4 java使用

    export LD_PRELOAD="/usr/local/lib/libtcmalloc.so"; java -Xmx1g  -XX:NativeMemoryTracking=summary  test
    

    再次通过pmap去查看内存,虚拟内存占用会少很多,也看出里面已经使用了tcmalloc

    [root@localhost soft]# pmap -p 17484
    17484:   java -Xmx1g -XX:NativeMemoryTracking=summary test
    0000000000400000      4K r-x-- /usr/java/jdk1.8.0_77/bin/java
    0000000000600000      4K rw--- /usr/java/jdk1.8.0_77/bin/java
    ...
    00007fd904341000    280K r-x-- /usr/local/lib/libtcmalloc.so.4.5.3
    00007fd904387000   2044K ----- /usr/local/lib/libtcmalloc.so.4.5.3
    00007fd904586000      4K r---- /usr/local/lib/libtcmalloc.so.4.5.3
    00007fd904587000      4K rw--- /usr/local/lib/libtcmalloc.so.4.5.3
    ...
    00007ffdebdcc000      8K r-x--   [ anon ]
    ffffffffff600000      4K r-x--   [ anon ]
     total          2612376K
    

    长期使用,可以用:

    export LD_PRELOAD=/usr/local/lib/libtcmalloc.so
    

    如果需要调试可以通过下面文件生成profile文件:

    mkdir  /home/logs/gperftools/tcmalloc/heap  
    chmod 0777 /home/logs/gperftools/tcmalloc/heap  
    export HEAPPROFILE=/home/logs/gperftools/tcmalloc/heap
    

    分析这些文件通过:

    pprof --pdf  java xxx.heap > xxx.pdf
    

    4.5 C 程序使用

    编译时候连接下:

    g++ main.cpp -o main -ltcmalloc -g -O0
    

    五 诗词欣赏

    八归·秋江带雨
    [宋] [史达祖] 
    
    秋江带雨,寒沙萦水,人瞰画阁愁独。
    烟蓑散响惊诗思,还被乱鸥飞去,秀句难续。
    冷眼尽归图画上,认隔岸、微茫云屋。
    想半属、渔市樵村,欲暮竞然竹。
    须信风流未老,凭持酒、慰此凄凉心目。
    一鞭南陌,几篙官渡,赖有歌眉舒绿。
    只匆匆眺远,早觉闲愁挂乔木。应难奈,故人天际,望彻淮山,相思无雁足。
    

    相关文章

      网友评论

          本文标题:Java所占内存中神奇的64MB

          本文链接:https://www.haomeiwen.com/subject/wkantktx.html