Java进程使用的虚拟内存确实比Java Heap要大很多。JVM包括很多子系统:垃圾收集器、类加载系统、JIT编译器等等,这些子系统各自都需要一定数量的RAM才能正常工作。

当一个Java进程运行时,也不仅仅是JVM在消耗RAM,很多本地库(Java类库中引用的本地库)可能需要分配原生内存,这些内存无法被JVM的Native Memory Tracking机制监控到。Java应用自身也可能通过DirectByteBuffers等类来使用堆外内存。

那么,当一个Java进程运行时,有哪些部分在消耗内存呢?这里我们只展示哪些可以被Native Memory Tracking监控到的部分。

一、JVM部分

Java Heap: 最明显的部分,Java对象在这个区域分配和回收,Heap的最大值由-Xmx决定。

Garbage Collector:GC的数据结构和算法需要额外的内存对堆内存进行管理。这些数据结构包括:Mark Bitmap、Mark Stack(用于跟踪存活的对象)、Remembered Sets(用于记录region之间的引用)等等。这些数据结构中的一些是可以直接调整的,例如:-XX:MarkStackSizeMax,其他的则依赖于堆的分布,例如:分区大小,-XX:G1HeapRegionSize,这个值越大Remembered Sets的值越小。不同的GC算法需要的额外内存是不同的,-XX:+UseSerialGC和-XX:+UseShenandoahGC需要较小的额外内存,G1和CMS则需要Heap size的10%作为额外内存。

Code Cache:用于存放动态生成的代码:JIT编译的方法、拦截器和运行时存根。这个区域的大小由-XX:ReservedCodeCacheSize确定(默认是240M)。使用-XX-TieredCompilation关掉多层编译,可以减少需要编译的代码,从而减少Code Cache的使用。

Compiler:JIT编译器需要一些内存来才能工作。这个值可以通过关闭多层编译或减少执行编译的线程数(-XX:CICompilerCount)来调整.

Class loading:类的元数据(方法的字节码、符号表、常量池、注解等)被存放在off-heap区域,也叫Metaspace。当前JVM进程加载了越多的类,就会使用越多的metaspace。通过设置-XX:MaxMetaspaceSize(默认是无限)或-XX:CompressedClassSpaceSize(默认是1G)可以限制元空间的大小

Symbol tables:JVM中维护了两个重要的哈希表:Symbol表包括类、方法、接口等语言元素的名称、签名、ID等,String table记录了被interned过的字符串的引用。如果Native Tracking表明String table使用了很大的内存,那么说明该Java应用存在对String.intern方法的滥用。

Threads:线程栈也会使用RAM,栈的大小由-Xss确定。默认是1个线程最大有1M的线程栈,幸运得失事情并没有这么糟糕——OS使用惰性策略分配内存页,实际上每个Java线程使用的RAM很小(一般80~200K),作者使用这个脚本(https://github.com/apangin/jstackmem)来统计有多少RSS空间是属于Java线程的。

二、堆外内存(Direct buffers)

Java应用可以通过ByteBuffer.allocateDirect显式申请堆外内存;默认的堆外内存大小是-Xmx,但是这个值可被-XX:MaxDirectMemorySize覆盖。在JDK11之前,Direct ByteBuffers被NMT(Native Memory Tracking)列举在other部分,可以通过JMC观察到堆外内存的使用情况。

除了DirectByteBuffers,MappedByteBuffers也会使用本地内存,MappedByteBuffers的作用是将文件内容映射到进程的虚拟内存中,NMT没有跟踪它们,想要限制这部分的大小并不容易,可以通过pmap -x 命令观察当前进程使用的实际大小:

Address           Kbytes    RSS    Dirty Mode  Mapping
...
00007f2b3e557000 39592 32956 0 r--s- some-file-17405-Index.db
00007f2b40c01000 39600 33092 0 r--s- some-file-17404-Index.db

三、本地库(Native libraries)

由System.loadLibrary加载的JNI代码也会按需分配RAM,并且这部分内存不受JVM管理。在这里需要关注的是Java类库,未关闭的Java资源会导致本地内存泄漏,典型的例子是:ZipInputStream或DirectoryStream。

JVMTI agent,特别是jdwp调试agent,也可能导致内存的过量使用(PS:去年写memory agent代码造成的内存泄漏记忆犹新)。

四、Allocator issues

一个Java进程可以通过系统调用(mmap)或标准库(malloc)方法来向OS申请内存。malloc自己又通过mmap来向OS申请比较大的内存,并通过自己的算法来管理这些内存,这可能会导致内存碎片,从而导致过量使用虚拟内存。jemalloc是另外一个内存分配器,它比常规的malloc分配器需要更少的footprint,因此可以在自己的C++代码中尝试使用jemalloc方法。

结论

无法准确统计一个Java进程使用的虚拟内存,因为有太多因素需要考虑,列举如下:

Total memory = Heap + Code Cache + Metaspace + Symbol tables +
Other JVM structures + Thread stacks +
Direct buffers + Mapped files +
Native Libraries + Malloc overhead + ...

本号(javaadu)专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。

为什么Java进程使用的RAM比Heap Size大?的更多相关文章

  1. 故障重现, JAVA进程内存不够时突然挂掉模拟

    背景,服务器上的一个JAVA服务进程突然挂掉,查看产生了崩溃日志,如下: # Set larger code cache with -XX:ReservedCodeCacheSize= # This ...

  2. 死磕内存篇 --- JAVA进程和linux内存间的大小关系

    运行个JAVA 用sleep去hold住 package org.hjb.test; public class TestOnly { public static void main(String[] ...

  3. java进程卡死问题

    原文地址:http://stackoverflow.com/questions/28739600/jvm-hang-and-kill-3-jmap-failed tomcat进程出现了如下异常,并且卡 ...

  4. 通过JDK常用工具监控Java进程的内存占用情况

    目录 1 JDK 工具的使用 2 查看 GC 日志信息 3 添加 JMS 远程监控 Tomcat是一款常用的Web容器, 它是运行在 JVM(Java Virtual Machine) 中的一个Jav ...

  5. JVM源码分析之一个Java进程究竟能创建多少线程

    JVM源码分析之一个Java进程究竟能创建多少线程 原创: 寒泉子 你假笨 2016-12-06 概述 虽然这篇文章的标题打着JVM源码分析的旗号,不过本文不仅仅从JVM源码角度来分析,更多的来自于L ...

  6. 利用JMX统计远程JAVA进程的CPU和Memory---jVM managerment API

    从JAVA 5开始,JDK提供了一些JVM检测的API,这就是有名的java.lang.management 包,包里提供了许多MXBean的接口类,可以很方便的获取到JVM的内存.GC.线程.锁.c ...

  7. JVM调优之Java进程消耗CPU过高

    JVM调优之Java进程消耗CPU过高 查找问题思路 1.查看cpu使用率,发现有线程cpu占用率很高  tops 咱们拿18092线程举例示范 2.查询pid对应的进程 ps -ef|grep 18 ...

  8. 分析java进程假死状况

    摘自: http://www.myexception.cn/internet/2044496.html 分析java进程假死情况 1 引言 1.1 编写目的 为了方便大家以后发现进程假死的时候能够正常 ...

  9. 利用JMX统计远程JAVA进程的CPU和Memory

    http://songzi0206.iteye.com/blog/1541636 ******************** 从JAVA 5开始,JDK提供了一些JVM检测的API,这就是有名的java ...

随机推荐

  1. Selenium Webdriver元素定位的八种常用方式【转】

    在使用selenium webdriver进行元素定位时,通常使用findElement或findElements方法结合By类返回的元素句柄来定位元素.其中By类的常用定位方式共八种,现分别介绍如下 ...

  2. ID转名称到手方案01

    > 好久没有写技术文章了,那就重新捡起来,从今天开始,分享这段时间的收获吧 ------------ > ## 其实很多时候,我们只需要鱼,而不是渔,呐,给你鱼. ### 这次的分享主题是 ...

  3. Example With JdbcDaoSupport

    By extended the JdbcDaoSupport, set the datasource and JdbcTemplate in your class is no longer requi ...

  4. 做「容量预估」可没有true和false

    如果第二次看到我的文章,欢迎右侧扫码订阅我哟~ 

  5. Leetcode之分治法专题-169. 求众数(Majority Element)

    Leetcode之分治法专题-169. 求众数(Majority Element) 给定一个大小为 n 的数组,找到其中的众数.众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素. 你可以假设数组是 ...

  6. 基于Taro与Typescript开发的网易云音乐小程序

    基于Taro与网易云音乐api开发,技术栈主要是:typescript+taro+taro-ui+redux,目前主要是着重小程序端的展示,主要也是借此项目强化下上述几个技术栈的使用,通过这个项目也可 ...

  7. 互联网从此没有 BAT

    根据 Wind 数据截止2019年8月30日,中国十大互联网上市公司排名中,百度排名第 6 位市值 365 亿美元,阿里巴巴排名第一市值高达 4499 亿美元,腾讯排名第二市值 3951 亿美元. 1 ...

  8. volatile、Synchronized实现变量可见性的原理,volatile使用注意事项

    变量不可见的两个原因 Java每个线程工作都有一个工作空间,需要的变量都是从主存中加载进来的.Java内存模型如下(JMM): 线程访问一个共享的变量时,都需要先从主存中加载一个副本到自己的工作内存中 ...

  9. springboot-vue前后端分离session过期重新登录

    springboot-vue前后端分离session过期重新登录 简单回顾cookie和session cookie和session都是回话管理的方式 Cookie cookie是浏览器端存储信息的一 ...

  10. 你真的了解MyBatis中${}和#{}的区别吗?

    动态sql是mybatis的主要特性之一.在mapper中定义的参数传到xml中之后,在查询之前mybatis会对其进行动态解析. mybatis提供了两种支持动态sql的语法:#{} 和 ${}. ...