为什么Java进程使用的RAM比Heap Size大?
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大?的更多相关文章
- 故障重现, JAVA进程内存不够时突然挂掉模拟
背景,服务器上的一个JAVA服务进程突然挂掉,查看产生了崩溃日志,如下: # Set larger code cache with -XX:ReservedCodeCacheSize= # This ...
- 死磕内存篇 --- JAVA进程和linux内存间的大小关系
运行个JAVA 用sleep去hold住 package org.hjb.test; public class TestOnly { public static void main(String[] ...
- java进程卡死问题
原文地址:http://stackoverflow.com/questions/28739600/jvm-hang-and-kill-3-jmap-failed tomcat进程出现了如下异常,并且卡 ...
- 通过JDK常用工具监控Java进程的内存占用情况
目录 1 JDK 工具的使用 2 查看 GC 日志信息 3 添加 JMS 远程监控 Tomcat是一款常用的Web容器, 它是运行在 JVM(Java Virtual Machine) 中的一个Jav ...
- JVM源码分析之一个Java进程究竟能创建多少线程
JVM源码分析之一个Java进程究竟能创建多少线程 原创: 寒泉子 你假笨 2016-12-06 概述 虽然这篇文章的标题打着JVM源码分析的旗号,不过本文不仅仅从JVM源码角度来分析,更多的来自于L ...
- 利用JMX统计远程JAVA进程的CPU和Memory---jVM managerment API
从JAVA 5开始,JDK提供了一些JVM检测的API,这就是有名的java.lang.management 包,包里提供了许多MXBean的接口类,可以很方便的获取到JVM的内存.GC.线程.锁.c ...
- JVM调优之Java进程消耗CPU过高
JVM调优之Java进程消耗CPU过高 查找问题思路 1.查看cpu使用率,发现有线程cpu占用率很高 tops 咱们拿18092线程举例示范 2.查询pid对应的进程 ps -ef|grep 18 ...
- 分析java进程假死状况
摘自: http://www.myexception.cn/internet/2044496.html 分析java进程假死情况 1 引言 1.1 编写目的 为了方便大家以后发现进程假死的时候能够正常 ...
- 利用JMX统计远程JAVA进程的CPU和Memory
http://songzi0206.iteye.com/blog/1541636 ******************** 从JAVA 5开始,JDK提供了一些JVM检测的API,这就是有名的java ...
随机推荐
- 访问CGI程序时不添加 /cgi-bin/ 目录也可访问
配置如下 <VirtualHost *:80> DocumentRoot D:\web_root\test ServerName www.test.com <Directory /& ...
- vue路由传参的三种方式以及解决vue路由传参页面刷新参数丢失问题
最近项目中涉及到跨页面传参数和后台进行数据交互,看到需求之后第一反应就是用路由传参来解决:Vue中给我们提供了三种路由传参方式,下面我们一个一个的来看一下: 方法一:params传参: this.$r ...
- 运行所选代码生成器时出错:“值-1超出了可接受的[0,2147483647]范围。参数名称:value”
在使用vs2019添加mvc控制器的时候 这已经是第二次遇到这个问题了.常言道,多喝热水,重启试试.有时候当应用工作不正常,重启也许能解决问题.但是程序员通常接触不到服务器系统权限.而运维人员和公司流 ...
- 《HelloGitHub》第 41 期
兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这是一个面向编程新手.热爱编程.对开源社区感兴趣 人群的月刊,月刊的内容包括:各种编 ...
- JSP学习笔记(1)——Jsp指令、动作元素和内置对象
简单来说,javaweb技术就是让服务器端能够执行Java代码,之后返回数据给客户端(浏览器)让客户端显示数据 jsp页面中可以嵌套java代码(java小脚本)和嵌套Web前端(html,css,j ...
- MySQL运行时自动生成的性能相关的数据参考
某大师曾说过,一个DBA要像熟悉自己的老婆一样熟悉自己的数据库,个人认为包含了两个方面的熟悉: 1,在稳定性层面来说,更多的是关注高可用.读写分离.负载均衡,灾备管理等等high level层面的 ...
- 2016 ACM-ICPC 青岛站网络赛G题 题解
[参考博客][https://blog.csdn.net/Tawn0000/article/details/82255682] 题意: 将n个数按照每k个一组来合并,合并需要花费的cost是两个数的长 ...
- 学会spss就能找到数据分析工作吗
大学课堂上学习了spss,老师也讲了很多知识,但是现在准备毕业了,我做的实习工作就是用业内的数据进行最新的行业研究.现在真正需要用到spss进行分析了,我却看不懂老板给的数据和分析要求,难道这就是理 ...
- electron-vue-webpack引入bootstrap多实例问题Multiple instances of Vue detected!
在node modules里面找到electron-webpack目录, 修改out->main.js白名单内容,增加 whiteListedModules.add("bootstra ...
- 树状数组求区间和模板 区间可修改 参考题目:牛客小白月赛 I 区间
从前有个东西叫树状数组,它可以轻易实现一些简单的序列操作,比如单点修改,区间求和;区间修改,单点求值等. 但是我们经常需要更高级的操作,比如区间修改区间查询.这时候树状数组就不起作用了,只能选择写一个 ...