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

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JAVA »遗失的JVM堆内存

    遗失的JVM堆内存

    2015-02-15 00:00:00 出处:ImportNew
    分享

    “HI,你能不能过来帮我看下这个奇怪的现象 ”我之所以会写这篇文章是因为我在一个技术支持的案例中遇到了这么一个情况。这个问题是由于不同的JVM工具所检测出来的可用内存的大小不一致所产生的。

    简言之,就是有一个工程师在排查某个应用内存使用过多的问题,而他一直“认为”这个程序的堆是2G的。由于某些原因,JVM工具貌似也不太确定这个进程的堆到底有多大。比如说,jconsole认为这个堆的最大可用内存为1963M,而jvisualvm检测出来的是2048m。那么到底哪个才是对的,为什么不同的工具会显示出不同的结果呢?

    这的确很蹊跷,尤其是嫌疑最大的JVM也被排除掉了——JVM是没有动过其它手脚的,因为:

    -Xmx与-Xms的配置值相等,因此在运行时堆增长的时候这个数值是不会变的。 由于关掉了自适应调整的策略(-XX:-UseAdaptiveSizePolicy),JVM也无法动态地调整内存池的大小。

    问题重现

    要弄清楚这个问题首先得看一下实现的工具本身。要获取可用内存的信息,最简单的方式就是下面这种了:

    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当前可用内存的大小。如果它发现内存大小发生了变化,它会将Runtime.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的源码中发现了这么一段代码 :

    // Support for java.lang.Runtime.maxMemory():  return the maximum amount of
    // memory that the vm could make available for storing 'normal' java objects.
    // This is based on the reserved address space, but should not include space
    // that the vm uses internally for bookkeeping or temporary storage
    // (e.g., in the case of the young gen, one of the survivor
    // spaces).
    virtual size_t max_capacity() const = 0;

    不得不说这实在是太隐蔽了。不过线索还是有的,只有那些真正好奇的人才能发现——真相就是在计算堆大小的时候,其中的一个存活区在某些情况下可能会被排除在外。

    这之后的事情就比较简单了——打开GC日志后我们可以发现,在2G的堆下,Serial, Parallel以及CMS算法所设置的存活区的大小都恰好是内存缺失的这部分。比如说,上例中的这个ParallelGC的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区的大小是524,800K,两个存活区是87,040K,而老生代的大小是1,398,272K。将Eden区以及老生代,再加上一个存活区的大小,正好就是2,010,112K,也就是说缺失的这85M或者说87,040K,的确就是剩下的那一个存活区。

    总结

    读完ITJS的这篇文章后你会对Java API的实现有一个新的认识。如果下次JVM工具将可用堆的总内存可视化时比-Xmx中配置的要小了那么一点点的话,你就知道这是少了其中的一个存活区了。

    当然我也承认,这在日常的开发工作中并没有什么实际用途,但这并不是ITJS的这篇文章的重点。事实上,ITJS的这篇文章想说的是,通常来说,我认为一名优秀的工程师应该具备的一个特征就是——好奇心。一个优秀的工程师应当时刻保持着一探究竟的热情。有时候答案可能很隐蔽,但我还是建议你尝试去把它找出来。你这一路所收获到的知识最终一定会回馈给你的。

    上一篇返回首页 下一篇

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

    别人在看

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    IT头条

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    严重缩水!NVIDIA将推中国特供RTX 5090 DD:只剩24GB显存

    00:17

    无线路由大厂 TP-Link突然大裁员:补偿N+3

    02:39

    Meta 千万美金招募AI高级人才

    00:22

    技术热点

    微软已修复windows 7/windows 8.1媒体中心严重漏洞 用户可下载安

    卸载MySQL数据库,用rpm如何实现

    windows 7中使用网上银行或支付宝支付时总是打不开支付页面

    一致性哈希算法原理设计

    MySQL数字类型中的三种常用种类

    如何解决SQL Server中传入select语句in范围参数

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

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