查看JVM进程的内存情况

// DirectMemory.java
package com.infuq.memory;

import org.jctools.util.UnsafeAccess;
import sun.misc.Unsafe;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import java.util.Scanner;

public class DirectMemory {

    public static void main(String[] args) throws Exception {

        Scanner scanner = new Scanner(System.in);

        long _30M = 30 * 1024 * 1024;
        long direct = 0;
        Unsafe unsafe = UnsafeAccess.UNSAFE;
        while (scanner.hasNext()) {
            String input = scanner.next();

            if (input.equals("1")) {
                System.out.println("malloc...");
                // 向操作系统申请内存,底层调用glibc库的malloc库函数
                direct = unsafe.allocateMemory(_30M);
            }
            else if (input.equals("2")) {
                System.out.println("init...");
                byte b = 6;
                // 使用上一步向操作系统申请的内存
                unsafe.setMemory(direct, _30M, b);
            }
        }
    }
}

上面这个程序的功能, 执行之后, 等待用户输入, 如果输入1,那么程序会向操作系统申请30M的内存, 如果输入2, 那么程序会初始化申请的30M内存.

这里说的初始化的言外之意是模拟程序使用向操作系统申请的内存

程序运行之后, 我们会通过使用JDK自带的jconsole(或jvisualvm)工具查看进程内存情况, 使用top,ps等命令查看进程内存情况, 使用JDK自带的jcmd命令查看进程内存情况, 使用pmap命令查看进程内存情况, 使用阿里云的arms查看进程的内存情况, 使用smem工具查看进程的内存情况. 从多维度查看内存情况 .

本次实验的环境: JDK1.8 Win10下的WSL2的Ubuntu20
还会使用2个三方包: jctools-core-2.1.2.jar jol-core-0.9.jar

文件结构如下图

查看JVM进程的内存情况_第1张图片
run.py中的内容如下
查看JVM进程的内存情况_第2张图片其实就是调用了 javac 和 java 命令而已,

设置堆空间50M, -XX:MaxMetaspaceSize=16M

访问 https://www.selenic.com/smem/download/ 下载一个smem工具, 可以用于查看进程的内存
查看JVM进程的内存情况_第3张图片
解压下载的 smem-1.4.tar.gz

最后我们的目录结构如下
查看JVM进程的内存情况_第4张图片运行程序
查看JVM进程的内存情况_第5张图片运行之后, 程序阻塞, 等待用户的输入

使用 jps 查看进程的PID = 15933
在这里插入图片描述我们先使用 smem 工具查看下内存, 如下图

./smem -t -k
查看JVM进程的内存情况_第6张图片
以上输出当前系统所有进程的内存情况, 由于我实验使用的是Win10的WSL系统, 所以系统里的进程很少. 能够看出进程15933使用的内存, USS=27.8M, PSS=28M, RSS=30.3M

查看JVM进程的内存情况_第7张图片

USS,PSS,RSS都是表示进程实际使用的内存. 更多关于USS,PSS,RSS关系和区别, 读者自行了解.

我们经常听到RSS/RES, 在使用top和ps命令的时候会看到, 如下图

查看JVM进程的内存情况_第8张图片
查看JVM进程的内存情况_第9张图片
如上图, 使用 top 和 ps 查看进程15933的RSS/RES = 54552KB, 即53.27M, 约等于使用 smem 工具查看的RSS=54.2M内存.

RSS是常驻于内存的内存, RSS中还会包含与其他进程一起共享的内存.

我们使用如下shell命令可以每隔2秒打印进程15933的实际使用的内存情况
i=0;while true; do echo $((i++)) $(./smem -t -k | tail -3 | head -1); sleep 2; done

我们还会使用如下shell命令每隔2秒打印进程15933的committed内存
i=0;while true; do echo $((i++)) $(pmap -d 15933 | tail -1); sleep 2; done

关于reserved(预留内存), committed(提交内存), used(已使用内存)的关系如下图, 更详细内容读者自行了解
查看JVM进程的内存情况_第10张图片
比如我们向操作系统申请30M的内存, 则committed=30M. 但是操作系统并不会马上将真实的30M内存全部分配给进程, 只会先分配一小部分真实内存给进程使用, 当再次需要真实内存的时候再次分配. 因此一个进程的committed内存一定大于等于used的内存.

好了, 我们把上面两个shell命令运行起来

然后我们捕捉某一时刻的内存情况如下图
查看JVM进程的内存情况_第11张图片
进程15933当前时刻实际使用的内存54.2M, 虚拟内存1641572K=1603M, committed内存114552K=111.86M

接下来输入1,那么我们的程序会向操作系统申请30M的内存

查看JVM进程的内存情况_第12张图片
如上图, 我们向操作系统申请了30M的内存, 而进程的已使用内存并没有变化, 但是进程commited内存从114552K->145276K, 相差30724K=30M.

我们继续再输入1, 结果如下图
查看JVM进程的内存情况_第13张图片如上图, 继续向操作系统申请30M内存, 进程已使用的内存也没有变化, 而进程committed内存又从145276增长到176000K, 又相差了30M.

我们不做任何操作, 时间过去了一会…

进程的内存如下图所示
查看JVM进程的内存情况_第14张图片
在这一段时间我们并没有任何操作, 内存有了一些小变化, 这很正常, 毕竟JVM进程里面还有一些JVM自身的线程也要随着程序的运行需要申请一些内存, 后面我们使用 jconsole 连接到进程, 内存也会发生一些增长, 这都是正常情况.

我们使用JDK自带的 jcmd 命令查看内存
查看JVM进程的内存情况_第15张图片
committed=173226KB 与上图使用pmap显示的176000KB有一些差. 毕竟它们是两个不同的命令, 统计的角度不一样.

pmap 命令统计的会比 jcmd统计的更准确. 查看man手册, pmap统计的是进程自身的smaps文件

查看JVM进程的内存情况_第16张图片
查看JVM进程的内存情况_第17张图片
接下来
查看JVM进程的内存情况_第18张图片如上图, 重点需要关注Heap和Internal内存的情况

我们使用JDK自带的 jconsole 工具查看内存

查看JVM进程的内存情况_第19张图片
上图查看的是堆空间的内存情况, committed=49152KB, 与使用 jcmd 命令查看的51200KB有一些差, 可以忽略, 毕竟是2个不同的工具统计的. 上图同时也说明了, 虽然向操作系统申请了50M的堆空间, 但是目前实际使用了Used=10578KB, 此时操作系统也只是把部分真实内存分配给进程, 只有随着进程的运行需要的内存越多, 操作系统才会分配更多的真实内存给进程, 当分配的真实内存一旦超过committed时, 也就会报OOM了.

我们再次捕捉某一时刻的内存情况
查看JVM进程的内存情况_第20张图片
接下来我们输入2, 我们写的程序就会使用申请到的内存

查看JVM进程的内存情况_第21张图片

如上图, 当我们真正使用内存的时候, committed(374664KB)内存没有发生变化, 而使用内存发生了变化, 增大了30M, 和我们之前申请的30M是一致的.

这个时候我们看一下通过 jconsole 统计的非堆内存的情况

查看JVM进程的内存情况_第22张图片我们继续输入1, 再申请30M内存, 再输入2, 使用申请的内存,

看一下内存的变化

查看JVM进程的内存情况_第23张图片

和之前的实验一样, 当输入1申请内存时, committed内存发生了变化, 已经使用内存没有发生变化

输入2之后
查看JVM进程的内存情况_第24张图片committed内存没有发生变化, 已使用内存增长了30M

而且我们再次看一下非堆内存, 与之前的统计几乎一样, 没变化.

查看JVM进程的内存情况_第25张图片
我们所说的非堆内存包括Metaspace, CodeCache, CCS, 使用ByteBuffer.allocateDirect(),使用unsafe.allocateMemory(), 使用FileChannel.map(), 使用FileChannel.transferTo()等申请的内存.其中重点要说的是ByteBuffer.allocateDirect()和unsafe.allocateMemory().虽然都是申请的直接内存, 也就是操作系统本地内存, 但是 jconsole 只能统计到使用ByteBuffer.allocateDirect()申请的直接内存, 它是无法统计到使用unsafe.allocateMemory()申请的直接内存. 我们使用的-XX:MaxMetaspaceSize也是控制ByteBuffer.allocateDirect()申请的直接内存大小, 无法控制unsafe.allocateMemory()申请的直接内存大小.比如我们使用的Dubbo, RocketMQ等底层网络通信都是使用Netty, Netty就是通过unsafe.allocateMemory()向操作系统申请内存并自己管理这块内存, Netty也会自己管理向操作系统申请内存的空间大小, 毕竟不能无限制向操作系统申请内存.

FileChannel.map() 和 FileChannel.transferTo() 涉及到零拷贝知识, 读者朋友可以去了解下, 在我的 https://www.yuque.com/infuq/others/miqbcc 文章也有记录

如果读者朋友所在公司的服务器部署在阿里云上, 通过阿里云的arms监控平台查看服务器的内存情况

查看JVM进程的内存情况_第26张图片上图右下角的直接缓冲区与 jconsole 统计的直接内存一样, 它们都无法统计到使用unsafe.allocateMemory()申请的内存.

如果要查看堆内存的使用情况, 可以使用 jconsole 或者 arms 查看堆内存的情况, 它们的统计没问题.

如果要查看直接内存的情况, 或者查看进程的内存情况, 仅仅使用 jconsole 或者 arms 是不完全的, 看到的内存是比实际要少的.

上图并非此次实验程序的内存统计, 我是从线上找的一个服务器

接下来

当我一直输入1, 也就是一直向操作系统申请内存, 只能表明进程的committed内存一直在增长

查看JVM进程的内存情况_第27张图片
而且我的宿主机Win10的内存也不会随着committed内存增长而增长
查看JVM进程的内存情况_第28张图片

接下来我们输入2, 让进程使用申请到的内存
查看JVM进程的内存情况_第29张图片
进程已使用的内存到了1.5G
查看JVM进程的内存情况_第30张图片
宿主机的内存也从之前的6.6增长到了7.1G, 进程已使用内存也从828M增长到了1.5G, 两者增长量基本吻合的.

【总结1】
通过实验, 零零散散介绍了如何查看进程的内存, 包括committed内存, 已使用内存等. 进程使用unsafe.allocateMemory()申请内存只是属于committed内存, 只有在进程真正使用这块内存的时候, 操作系统才会一部分一部分的将真实的内存分配给进程使用. 通过实验也能知道, 使用unsafe.allocateMemory()方式申请内存是不受-XX:MaxMetaspaceSize参数控制的, 实验中设置-XX:MaxMetaspaceSize=16M , 但是我们程序已经申请使用了好几百M的内存.

【总结2】

1.如果要查看进程的committed内存, 使用pmap -d <进程ID>查看
2.如果要查看进程已使用的内存(USS,PSS,RSS), 使用smem工具查看, 使用top和ps命令也可以查看到RSS值, 但这个RSS值包含共享的内存, 因此我们也要关注PSS,USS
3.如果要查看JVM的直接内存, 可以使用 jcmd <进程ID> VM.native_memory scale=KB
4.当使用unsafe.allocateMemory(30M)申请内存的时候, committed内存会增长30M, 但是已使用内存不会增长
5.当程序使用unsafe.allocateMemory(30M)申请到的内存时, 已使用内存会增长
6.jconsole , jvisualvm, 阿里云arms 是监控不到unsafe.allocateMemory()方式申请的内存

关于如何监控远程Java进程可以查看我的这篇语雀文章
https://www.yuque.com/infuq/default/wwmdfk#rJSoP

关于JVM内存的布局图可以在下面这篇语雀文章中查找到
https://www.yuque.com/infuq/default/bzu9ef

再贴一张图


个人站点
语雀

公众号

你可能感兴趣的:(其他,JVM,内存)