关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JAVA »分析:为什么JVM指定-Xmx参数后占用内存会变少?

    分析:为什么JVM指定-Xmx参数后占用内存会变少?

    2015-05-12 00:00:00 出处:InfoQ - 李林锋
    分享

    “嘿,你能顺便过来看看这个奇怪的事情吗?” 就是让我提供支持的这个事情,驱使我写下这篇博客的。这个特殊的问题是,不同工具给出的可用内存的报告是不一样的。

    简而言之,工程师正在调查特定应用程序的内存使用。根据以往的经验,他给这个应用指定了2G堆内存。但是不知道什么原因,JVM工具似乎不能确定这个程序到底有多少内存。例如 jconsole 探测可用堆总共为1963M,但 jvisualvm 报告称堆为2048M。到底哪一个是正确的呢?为什么另一个给出了不一样的信息呢?

    这的确很不可思议,特别是以往的认知被突然改变。表面上JVM没有耍任何花招:

    -Xmx 和 -Xms 是相等的,这就使得报告的数字不会随着堆实时增加。 JVM避免通过内存的自适应策略(-XX:-UseAdaptiveSizePolicy)动态改变内存池的大小。

    重现不同

    搞懂这个问题的第一步是深入这些工具的实现方式。一般通过标准API查看可用内存会像下面这样:

    System.out.println("Runtime.getRuntime().maxMemory()="+Runtime.getRuntime().maxMemory());

    的确,这好像是工具首先会被用到的方式。寻找答案的第一步是找出可复现的测试用例。为了这个目的,我写了下面这段代码:

    package eu.plumbr.test;
    //imports skipped for brevity
    
    public class HeapSizeDifferences {
    
      static Collection<Object> objects = new ArrayList<Object>();
      static long lastMaxMemory = 0;
    
      public static void main(String[] args) {
        try {
          List<String> inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
          System.out.println("Running with: " + inputArguments);
          while (true) {
            printMaxMemory();
            consumeSpace();
          }
        } catch (OutOfMemoryError e) {
          freeSpace();
          printMaxMemory();
        }
      }
    
      static void printMaxMemory() {
        long currentMaxMemory = Runtime.getRuntime().maxMemory();
        if (currentMaxMemory != lastMaxMemory) {
          lastMaxMemory = currentMaxMemory;
          System.out.format("Runtime.getRuntime().maxMemory(): %,dK.%n", currentMaxMemory / 1024);
        }
      }
    
      static void consumeSpace() {
        objects.add(new int[1_000_000]);
      }
    
      static void freeSpace() {
        objects.clear();
      }
    }

    这段代码通过在一个 new int[1000000] 的循环中分配内存块,检测当前在实时JVM中的可用内存。无论何时,只要最后知道的内存大小改变时,都会通过打印出 ofRuntime.getRuntime().maxMemory()__ 报告出来,类似于如下这样:

    Running with: [-Xms2048M, -Xmx2048M]
    Runtime.getRuntime().maxMemory(): 2,010,112K.

    结果确实如此——有时甚至指定JVM有2G可用堆,但是运行着莫名其妙地发现其中的85M找不到了。你可以通过运用 2,010,112K 除以 1024 转化Runtime.getRuntime().maxMemory() 的输出到MB来复查我的计算。实际结果等于1963M,比起实际的 2048M 少了 85M。

    寻求根本原因

    重现这个现象之后,我做了如下的笔记——采用不同的GC算法运行似乎也产生不同的结果:

    GC algorithm Runtime.getRuntime().maxMemory()
    -XX:+UseSerialGC 2,027,264K
    -XX:+UseParallelGC 2,010,112K
    -XX:+UseConcMarkSweepGC 2,063,104K
    -XX:+UseG1GC 2,097,152K

    除了G1消费了我实际给的2G之外,任何其它GC算法似乎始终会半随机地丢失一部分内存。

    现在是时候剖析一下JVM的源代码了,在 CollectedHeap 的源代码中,我发现了下面这些:

    //对java.lang.Runtime.maxMemory()的支持:
    //返回虚拟机提供给“标准”java对象的最大内存。
    //这个基于保留的地址空间,但是不应该包括虚拟机使用内部统计或临时存储的这部分空间。
    //(例如:在青年代中,残留空间之一)
    virtual size_t max_capacity() const = 0;

    不得不承认答案隐藏得很深。但真相还是在好奇心的驱使下找到——事实上,某些情况下残留空间其中一些可能被排除在内存计算之外。

    从这里开始就一帆风顺了。打开GC日志发现,确实在设置2G内存时,Parallel和CMS算法都会在不同程度上,设置残留的空间是可变的。例如,以Parallel算法为例GC的日志演示如下所示:

    Running with: [-Xms2g, -Xmx2g, -XX:+UseParallelGC, -XX:+PrintGCDetails]
    Runtime.getRuntime().maxMemory(): 2,010,112K.
    
    ... rest of the GC log skipped for brevity ...
    
     PSYoungGen      total 611840K, used 524800K [0x0000000795580000, 0x00000007c0000000, 0x00000007c0000000)
      eden space 524800K, 100% used [0x0000000795580000,0x00000007b5600000,0x00000007b5600000)
      from space 87040K, 0% used [0x00000007bab00000,0x00000007bab00000,0x00000007c0000000)
      to   space 87040K, 0% used [0x00000007b5600000,0x00000007b5600000,0x00000007bab00000)
     ParOldGen       total 1398272K, used 1394966K [0x0000000740000000, 0x0000000795580000, 0x0000000795580000)

    从上面你可以看到,Eden空间被设置为了524800K,残留空间都被设为了 87040K,Old空间大小为 1398272K。把Eden、Old和残留空间之一加在一起等于2010112K,确认丢失的 85 或 87040K 确实是保留的残留空间。

    总结

    读完这篇文章后,相信你现在已经准备好以一种新的视角深入到Java API的实现细节。下次遇到可视化工具的总可用堆大小略低于Xmx规定的大小时,你就知道少的那部分等于你一个残留空间的大小。

    不得不承认的一个事实是,在日常的编程中不是特别有用,但是这不是我写这篇文章的初衷。相反地,写这篇文章目的是为了强调我在优秀工程师身上看到的特质——好奇心。优秀的工程师总是想去知道,那些东西的工作方式并探究为什么它们会像那样工作。有时候答案藏匿地很深,但仍然建议你去试图寻求答案。最终,在这个过程中获取的知识,将会让你受益无穷。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    小米路由器买哪款?Miwifi热门路由器型号对比分析

    DESTOON标签(tag)调用手册说明(最新版)

    Destoon 9.0全站伪静态规则设置清单(Apache版)

    Destoon 9.0全站伪静态规则设置清单(Nginx版)

    Destoon 8.0全站伪静态规则设置清单(Apache版)

    Destoon 8.0全站伪静态规则设置清单(Nginx版)

    Destoon会员公司地址伪静态com/目录如何修改?两步轻松搞定,适合Nginx和Apache

    Python 并行处理列表的常见方法及其优缺点分析

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    IT头条

    StorONE 的高效平台将 Storage Guardian 数据中心占用空间减少 80%

    11:03

    年赚千亿的印度能源巨头Nayara 云服务瘫痪,被微软卡了一下脖子

    12:54

    国产6nm GPU新突破!砺算科技官宣:自研TrueGPU架构7月26日发布

    01:57

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    技术热点

    最常用的 Eclipse 快捷键整理

    多表多查询条件对SQL Server查询性能的优化

    浅谈如何优化SQL Server服务器

    HTTP 协议中使用 Referer Meta 标签控制 referer

    好用的mysql备份工具

    Android开发中的MVP架构详解

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。