目录
1 JVM 内存模型中的区域
1.1 线程栈区
1.2 Java Heap 区
1.3 静态方法区
1.4 JDK 8.0 中的元空间
2 JDK 工具的使用
3 查看 GC 日志信息
4 添加 JMS 远程监控
Tomcat 是运行在 JVM(Java Virtual Machine) 中的一个 Java 进程, 它在运行过程中对内存的占用情况, 可以借助一些 JDK 的工具进行监控, 为优化提供数据支撑.
1 JVM 内存模型中的区域
1.1 线程栈区
压入线程栈的每个栈帧 (Stack Frame) 中, 包含了程序指令以及局部变量表, 每个方法调用对应一个栈帧.
程序指令包括程序计数器(PCR), 记录程序执行到的位置, 便于方法调用或多线程切换结束后的工作恢复.
局部变量表包括各种基本数据类型: boolean,byte,char,short,int,float,long,double 以及对象的引用.
注意: 每个线程都有独立的栈, 称之为线程栈, 它们是互相隔离的.
1.2 Java Heap 区
Java Heap 是被所有线程共享的一块内存区域, 在虚拟机启动时创建. 此内存区域的唯一目的就是存放对象实例, 几乎所有的对象实例都在这里分配内存.
1.3 静态方法区
又称为永久代(Perm Generation), 用于存储已被虚拟机加载的类信息, 常量, 静态变量, 即时编译器编译后的代码等数据.
对于永久代的常见配置有: -XX:PermSize=256m -XX:MaxPermSize=512m.
说明: JDK 8.0 中用 Meta Space(元空间)替代 Perm Generation, 因此在配置 JVM 启动参数的时候, 需要作如下配置:
- -XX:MetaspaceSize=256m # 对比 JDK 7.0 的 PermSize
- -XX:MaxMetaspaceSize=512m # 对比 JDK 7.0 的 MaxPermSize
1.4 JDK 8.0 中的元空间
JDK 8.0 的 HotSpot 实现中, 使用本地内存来存储类的元数据, 这个区域被称为元空间.
元空间有如下特点:
充分利用了 Java 语言规范中的好处: 类及相关元数据的生命周期与类加载器的生命周期一致;
每个类加载器都有各自专用的存储空间;
元空间只进行线性分配;
不会单独回收某个类;
省掉了 GC 扫描及压缩的时间;
元空间中对象的位置是固定的;
如果 GC 发现某个类加载器不再存活, 它就会把与这个类加载器相关的空间全部回收.
元空间的内存分配模型:
绝大多数类的元数据空间都从本地内存中分配;
用来描述类的元数据的类也被删除了;
分元数据分配了多个虚拟内存空间;
给每个类加载器分配一个内存块的列表, 块的大小取决于类加载器的类型; sun / 反射 / 代理对应的类加载器的块会小一些;
归还内存块, 释放内存块列表;
一旦元空间的数据被清空了, 虚拟内存的空间就会被回收;
减少碎片的策略.
2 JDK 工具的使用
JDK 自带的工具位于 ${JAVA_HOME}/bin / 目录下.
JConsole 可以简单明了地查看到内存的使用情况, 线程的状态, 当前加载的类的总量等.
JVisualVM 可以下载插件(如 GC 等), 进而查看更丰富的信息. 如果是分析本地的 Tomcat 的话, 还可以进行内存抽样等, 检查每个类的使用情况.
jps 查看本地运行着的 Java 进程, 及其进程号, 进程启动的路径等信息;
jmap 查看垃圾收集策略即 JVM 内存占用情况:
- jmap -heap pid # 查看垃圾收集策略, 以及堆内存的分配, 使用情况.
- jmap -clstats pid # 查看类加载器的统计数据 --- 此命令调用了 sun.jvm.hotspot.runtime.VM.initialize() 方法, 会导致该 pid 对应的 JVM 进程阻塞.
- jmap -histo [pid] # 按照内存使用大小倒序列出内存中的实例类型.
jstack 查看线程栈:
jstack pid # 列出该 pid 对应 JVM 的所有线程栈描述, 主要包括每个线程的状态以及堆栈内各栈帧的方法全限定名, 代码位置. 注意: 这些信息的显示只是为了便于开发人员阅读, 并不是栈中存的就是这些信息.
jstat 实时查看堆内存的使用情况:
- # 使用方法:
- jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
- # 查看可使用的选项:
- jstat -options
- -class # 类加载情况的统计
- -compiler # HotSpot 中即时编译器编译情况的统计
- -gc
- -gccapacity # 新生代, 老年代以及永久代的存储容量情况
- -gccause
- -gcmetacapacity # 元数据区的容量
- -gcnew # 新生代垃圾回收信息
- -gcnewcapacity # 新生代的存储容量
- -gcold # 老年代垃圾回收信息
- -gcoldcapacity # 老年代的存储容量
- -gcutil # 实时查看 GC 信息
- -printcompilation # HotSpot 编译方法的统计
使用示例: 间隔 5s, 每隔 10 条输出一次头信息, 打印进程号为 3308 的 JVM 进程的堆内存使用情况, 以及各代垃圾回收的次数及时间:
jstat -gcutil -h10 77545 5000
显示信息如下:
参数说明:
S0: Heap 上的 Survivor Space 0 区已使用空间的百分比
S1: Heap 上的 Survivor Space 1 区已使用空间的百分比
E: Heap 上的 Eden Space 区已使用空间的百分比
O: Heap 上的 Old Space 区已使用空间的百分比
M: Meta Space(元数据区)已使用空间的百分比
YGC: 从应用程序启动到采样时发生 Young GC 的次数
YGCT: 从应用程序启动到采样时 Young GC 所用的时间(单位: 秒)
FGC: 从应用程序启动到采样时发生 Full GC 的次数
FGCT: 从应用程序启动到采样时 Full GC 所用的时间(单位: 秒)
GCT: 从应用程序启动到采样时用于垃圾回收的总时间(单位: 秒)
3 查看 GC 日志信息
可以通过配置 JVM 的启动参数, 打印类的加载情况及对象的回收信息, 可以打印到屏幕或指定文件中, 默认也会打印到 catalina.log 中. Tomcat 容器的 JVM 启动参数配置文件是: ${TOMCAT_HOME}/bin/catalina.sh, 具体参数如下:
- -verbose:gc # 在输出设备显示垃圾收集信息(JVM 发生内存回收时输出相关信息)
- -XX:+PrintGC # 输出 GC 日志, 形式: Full GC 118250K->113543K(130112K), 0.0094143 secs
- -XX:+PrintGCDetails # 输出 GC 详细日志, 形式: GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs[Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
- -XX:+PrintGCTimeStamps # 输出 GC 的时间戳, 以基准时间的形式输出: 11.851: [GC 98328K->93620K(130112K), 0.0082960 secs], 11.851 是 JVM 启动后的秒数.
- -XX:+PrintGCDateStamps # 输出 GC 的时间戳, 以日期的形式输出: 2018-08-28T21:53:59.234+0800.
- -XX:+PrintGCApplicationStoppedTime # 打印垃圾回收期间程序暂停的时间, 即 GC 消耗的时间. 可与上面混合使用. 输出形式: Total time for which application threads were stopped: 0.0468229 seconds
- -XX:+PrintGCApplicationConcurrentTime # 打印每次垃圾回收前, 程序未中断的执行时间, 即相邻 2 次 GC 的间隔. 可与上面混合使用. 输出形式: Application time: 0.5291524 seconds
- -XX:+PrintTenuringDistribution # 观察各个 Age 的对象总大小
- -XX:PrintHeapAtGC # 打印 GC 前后的详细堆栈信息.
- -XX:+HeapDumpOnOutOfMemoryError # 发生 OOM 时自动 dump 堆栈信息, 以便后续分析.
- -Xloggc:../logs/gc.log # 与上面选项配合使用, 将日志信息输出到指定的文件以便后续分析.
4 添加 JMS 远程监控
对部署在局域网内其他服务器上的 Tomcat, 可以打开 JMX 监控端口, 就可以在另外的服务器上通过该端口查看常用的参数(一些比较复杂的功能不支持).
配置方法: 同样是在 JVM 启动参数中配置, 配置如下:
- -Dcom.sun.management.jmxremote.ssl=false
- -Dcom.sun.management.jmxremote.authenticate=false
- -Djava.rmi.server.hostname=172.16.11.62 # 设置 JVM 的 JMS 监听的 IP 地址, 防止错误监听为本机 127.0.0.1 地址
- -Dcom.sun.management.jmxremote.port=1090 # 设置 JVM 的 JMS 监控的端口
- -Dcom.sun.management.jmxremote.ssl=false # 设置 JVM 的 JMS 监控不实用 SSL
- -Dcom.sun.management.jmxremote.authenticate=false # 设置 JVM 的 JMS 监控不需要认证
参考资料:
JVM 运行时内存使用情况监控
来源: https://www.cnblogs.com/shoufeng/p/9673975.html