• JVM内存占用情况深入分析


    内存分布

    首先,列举一下一个JVM进程主要占用内存的一些地方:

    • Young
    • Old
    • metaspace
    • java thread count * Xss
    • other thread count * stacksize (非Java线程)
    • Direct memory
    • native memory
    • codecache

    说明:包括但不限于此。

    接下来一步一步验证每个区域占用的内存。并且为了验证这个问题,写了一个工具类,里面有给每个区域分配内存的方法,源码在文末。

    • JVM参数

    运行过程中的JVM参数如下:

    -verbose:gc -XX:+PrintGCDetails -Xmx2g -Xms2g -Xmn1g
     
    -XX:PretenureSizeThreshold=2M -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
     
    -XX:CMSInitiatingOccupancyFraction=90 -XX:+UseCMSInitiatingOccupancyOnly
     
    -XX:MaxDirectMemorySize=512m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m

    Young+Old

    我们先从最简单的堆占用内存开始,即Xmx和Xms参数申明,它包括young和old区。分别分配800M和200M内存,main方法如下:

    public static void main(String[] args) throws Exception{
     
    youngAllocate(800);
     
    oldAllocate(200);
     
    Thread.sleep(300000);
     
    }

    通过TOP命令查看,RES为1G:

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
     
    22481 afei 20 0 4366m 1.0g 11m S 0.5 27.0 0:02.41 java

    通过jstat命令也能看到,Old和Eden分别占用200M和800M。

    这里再增加一个有趣的测试,young和old区分别分配1000M和1000M内存,main方法如下:

    public static void main(String[] args) throws Exception{
     
    youngAllocate(1000);
     
    oldAllocate(1000);
     
    // 为了CMS GC顺利触发,这里需要sleep 5s以上,建议时间长一点,让整个CMS GC顺利完成。
     
    Thread.sleep(300000);
     
    }

    这样就会导致发生一次YGC和一个CMS GC,那么你认为这时候通过TOP命令查看RES结果是多少呢?这时候应该是1.8G,除了S0/S1两个区域,eden和Old区域都写入过数据,而JVM使用过的内存就不会归还给操作系统,除非JVM进程宕机或者重启,这个结论很重要:

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
     
    22707 afei 20 0 4366m 1.8g 11m S 0.0 48.7 0:00.90 java

    Young+Old+Metaspace

    接下来,我们再通过程序在Metaspace中重复加载20w个对象,即metaspace分配200M左右的内存,main方法如下:

    public static void main(String[] args) throws Exception{
     
    youngAllocate(1000);
     
    oldAllocate(1000);
     
    metaspaceAllocate(200000);
     
    Thread.sleep(60000);
     
    }

    通过TOP命令查看,RES为2.0G:

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
     
    22781 afei 20 0 4472m 2.0g 12m S 0.0 54.7 0:07.51 java

    即前面分析的1.8G+208M(213822/1024),在JVM进程退出时有一行这样的日志:

     Metaspace       used 213822K, capacity 215618K, committed 215936K, reserved 1165312K

    Young+Old+Metaspace+DirectMemory

    接下来,我们再通过程序给堆外分配400M,main方法如下:

    public static void main(String[] args) throws Exception{
     
    youngAllocate(1000);
     
    oldAllocate(1000);
     
    metaspaceAllocate(200000);
     
    directMemoryAllocate(400);
     
    Thread.sleep(60000);
     
    }

    通过TOP命令查看,RES为2.4G:

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
     
    23329 afei 20 0 4874m 2.4g 12m S 0.0 65.2 0:12.67 java

    Young+Old+Metaspace+DirectMemory+线程栈

    最后就是线程栈,笔者试图通过启动20个线程,并且设置-Xss10240k,但是并没有达到预期,这里作为一个遗留问题。等笔者哪天搞懂了,再发文说明。

    • Xss案例

    曾经群里有一个朋友就是因为Xss配置相当大导致RES占用13G左右。大概情况是这样,-Xms4g,-Xss40940k,dubbo的provider服务。熟悉dubbo服务同学知道,dubbo服务provider默认采用固定200个线程处理的方式。所以200个线程占用8G,加上4G堆,以及一些其他内存,导致RSS高达13G,恐怖!!!

    codecache

    这部分内存一般占用比较少,在JVM崩溃的文件hs_err_pid18480.log中有其内存占用情况:

    CodeCache: size=245760Kb used=47868Kb max_used=47874Kb free=197891Kb
     bounds [0x00007f00b4de4000, 0x00007f00b7d54000, 0x00007f00c3de4000]
     total_blobs=12973 nmethods=12383 adapters=500
     compilation: enabled

    知识总结

    HotSpot VM自己在JIT编译器、GC工作等的一些时候都会额外临时分配一些native memory,在JDK类库也有可能会有些功能分配长期存活或者临时的native memory,然后就是各种第三方库的native部分可能分配的native memory。

    总之,RES占比异常时,一一排查,不要忽略任何一部分可能消耗的内存。

    jvm使用了的内存,即使GC后也不会还给操作系统。

    Direct Memory内存查看:如果是JDK 7及以上版本,可以用jconsole或者VisualVM的MBeans窗口查看java.nio.BufferPool.direct属性。

  • 相关阅读:
    java stackoverflowerror与outofmemoryerror区别
    JVM参数笔记
    记录一次JVM配置优化的案例
    JVM相关内容简介(转)
    Spring boot中的定时任务(计划任务)
    netty学习:UDP服务器与Spring整合(2)
    netty学习:UDP服务器与Spring整合
    maven 纯注解一步一步搭建Spring Mvc项目(入门)
    将class类对象转化成json的数据格式
    Spring五个事务隔离级别和七个事务传播行为
  • 原文地址:https://www.cnblogs.com/duanxz/p/3143548.html
Copyright © 2020-2023  润新知