【JVM】实战篇(一)
【JVM】实战篇(一)
- 1. 实战篇内容介绍
- 2. 内存调优
- 2.1 内存溢出(OutOfMemory)和内存泄漏(memory leak)
- 2.1.1 内存泄露的demo
- 3. 解决内存溢出
- 3.1 top命令
- 3.2 VisualVM
- 附录
1. 实战篇内容介绍
实战篇从三方面利用Java虚拟机进行生产环境线上问题解决以及性能问题的优化。
2. 内存调优
2.1 内存溢出(OutOfMemory)和内存泄漏(memory leak)
- 内存泄露(memory leak) :在Java中如果不再使用一个对象,但是该对象依然在GC ROOT的引用链上,这个对象就不会被垃圾回收器回收,这种情况就称之为内存泄漏。
- 内存泄漏绝大多数情况下都是由堆内存泄露引起的。
- 少量的内存泄露可以容忍,但是如果发生持续的内存泄露,不管有多大的内存都会被消耗完,最终导致内存溢出(OutOfMemory)。但是产生的内存溢出不只有内存泄漏这一个原因。
2.1.1 内存泄露的demo
- 内存泄露导致溢出的常见场景是大型的Java后端应用中,在处理用户请求后,没有及时将用户的数据删除。随着用户请求数量越来越多,内存泄漏的对象占满了堆内存最终导致内存溢出。
- 第二种常见的是分布式任务调用系统,比如Elastic-job、Quartz等进行任务调度时,被调度的Java应用在调度任务结束中出现了内存泄露,最终导致多次调度后内存溢出。这种重启之后也可以回复使用,但是过段时间之后还是会内存溢出。
3. 解决内存溢出
解决内存溢出的步骤总共分为四个步骤,其中前两个步骤最为核心:
3.1 top命令
- top命令是Linux下用来查看系统信息的一个命令,它提供给我们去实时的查看系统的资源,比如执行时的进程、线程和系统参数等信息。
- 进程使用的内存为RES(常驻内存) SHR(共享内存)
top - 17:45:57 up 59 days, 1:38, 3 users, load average: 0.27, 0.33, 0.32
Tasks: 233 total, 1 running, 232 sleeping, 0 stopped, 0 zombie
%Cpu(s): 2.8 us, 0.8 sy, 0.0 ni, 96.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 32941696 total, 358424 free, 13474308 used, 19108964 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 17314436 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
10482 3000 20 0 7252020 1.2g 56004 S 10.3 4.0 7720:20 java
26588 root 20 0 13.9g 2.4g 22888 S 9.0 7.6 6400:26 java 1625 root 20 0 461252 107768 4 S 3.0 0.3 211:20.90 scanner_clamav
19239 1000 20 0 21.7g 604880 30152 S 2.0 1.8 2695:05 node 2123 root 20 0 2071528 43008 16456 S 0.7 0.1 159:11.21 containerd 3361 polkitd 20 0 5042896 787592 22072 S 0.7 2.4 663:06.44 mysqld
10055 root 20 0 7949416 1.6g 20368 S 0.7 5.1 496:05.35 java 9 root 20 0 0 0 0 S 0.3 0.0 48:40.62 rcu_sched 1462 root 20 0 1464604 57188 7136 S 0.3 0.2 226:27.61 proxima 1543 root 20 0 1492708 31896 7480 S 0.3 0.1 98:09.50 collector 1602 root 20 0 28228 9504 696 S 0.3 0.0 81:59.17 driver 2662 root 20 0 3525588 126092 31328 S 0.3 0.4 200:42.35 dockerd 3362 root 20 0 138864 12812 5344 S 0.3 0.0 261:14.95 redis-server 5170 root 20 0 3254180 1.1g 17832 S 0.3 3.3 516:11.54 java
10151 3000 20 0 6827048 642972 18236 S 0.3 2.0 282:44.42 java
18566 1000 20 0 6623724 1.2g 17084 S 0.3 3.9 779:59.83 java
22487 root 20 0 1236448 9432 4556 S 0.3 0.0 3:58.98 containerd-shim
31925 root 20 0 9.8g 386188 16092 S 0.3 1.2 306:14.33 java 1 root 20 0 191304 4328 2648 S 0.0 0.0 42:32.37 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:00.05 kthreadd
字段 | 含义 |
---|---|
17:45:57 | 当前系统时间 |
up 59 days, 1:38 | 系统已运行 59 天 1 小时 38 分钟(自上次重启以来) |
3 users | 当前登录的用户数(可以用 who 命令查看) |
load average: 0.27, 0.33, 0.32 | 系统平均负载(1 分钟、5 分钟、15 分钟平均运行队列长度) |
- “平均负载” 是 CPU 正在处理 + 等待处理的任务数。
- 如果平均负载 ≈ CPU 核心数,表示负载饱和。
例如:4 核 CPU → 平均负载 > 4 说明 CPU 忙不过来了。
字段 | 含义 |
---|---|
233 total | 总共有 233 个任务(进程) |
1 running | 当前正在运行(占用 CPU)的进程数量 |
232 sleeping | 休眠中的进程(等待事件,如 I/O、网络、锁等) |
0 stopped | 暂停的进程(被信号停止) |
0 zombie | 僵尸进程(父进程未回收的子进程) |
%Cpu(s): 2.8 us, 0.8 sy, 0.0 ni, 96.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
字段 | 含义 |
---|---|
us (user) | 用户空间占用 CPU 百分比(普通应用程序) |
sy (system) | 内核空间占用 CPU 百分比(系统调用、驱动等) |
ni (nice) | 通过 nice 调整优先级的进程占用 CPU 百分比 |
id (idle) | CPU 空闲时间百分比 |
wa (wait) | 等待 I/O(磁盘、网络等)所花费的时间百分比 |
hi (hardware irq) | 硬中断占用 CPU 百分比 |
si (software irq) | 软中断占用 CPU 百分比 |
st (steal) | 被虚拟化环境(如云主机)“偷走”的 CPU 时间百分比(宿主机忙) |
📘 解释:
- 这里 96.4% id 表示 CPU 基本空闲。
- 如果 wa 高 → I/O 瓶颈。
- 如果 sy 高 → 系统调用或内核开销大。
- 如果 st 高 → 云服务器 CPU 被宿主机抢占。
内存使用情况
KiB Mem : 32941696 total, 358424 free, 13474308 used, 19108964 buff/cache
字段 | 含义 |
---|---|
total | 物理内存总量(约 32GB) |
free | 完全未被使用的内存(仅 350MB 左右) |
used | 已被程序占用的内存 |
buff/cache | 被缓存或缓冲区使用的内存(Linux 会尽量用空闲内存做缓存,加速文件访问) |
交换分区(Swap)
KiB Swap: 0 total, 0 free, 0 used. 17314436 avail Mem
字段 | 含义 |
---|---|
total/free/used | Swap 分区的总量、空闲、已使用情况 |
avail Mem | 应用程序实际可用的内存(包含 cache 可回收部分) |
📘 解释:
- 系统当前 没有启用 Swap(total=0),所以全靠物理内存;
- 若内存不足且无 Swap,会更容易触发 OutOfMemory。
进程详细列表
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
列名 | 含义 |
---|---|
PID | 进程 ID |
USER | 进程所属用户 |
PR | 优先级(priority,数值越小优先级越高) |
NI | nice 值(进程优先级修正值,负数优先级更高) |
VIRT | 进程使用的虚拟内存总量(包含共享库、缓存、Swap 映射等) |
RES | 实际占用的物理内存(Resident Set Size) |
SHR | 共享内存(与其他进程共享的部分) |
S | 进程状态: |
- R = running(运行中)
- S = sleeping(休眠)
- T = stopped(停止)
- Z = zombie(僵尸)
| %CPU | 进程占用的 CPU 百分比 |
| %MEM | 进程占用的内存百分比(相对物理内存总量) |
| TIME+ | 进程累计占用 CPU 的总时间(用户+系统) |
| COMMAND | 启动该进程的命令名 |
举例
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
10482 3000 20 0 7252020 1.2g 56004 S 10.3 4.0 7720:20 java
含义:
- PID = 10482
- 属于用户 3000
- 优先级 20
- 占用物理内存 1.2GB(占总内存 4%)
- 虚拟内存 7.25GB(包含 JVM 映射空间)
- 当前 CPU 占用 10.3%
- 运行累计 7720 分钟(约 5.3 天)
对free -h
命令详解
free -h
是 Linux 上查看内存(RAM)和交换区(swap)使用情况的简单命令,-h 表示以“人类可读”的单位显示(K/M/G 等)。它把内核追踪的几个关键内存指标列出来,帮助判断系统是否缺内存或只是被缓存占用。
[root@iv-ye20h65dz45i3z346rvs opt]# free -htotal used free shared buff/cache available
Mem: 31G 12G 424M 1.7G 18G 16G
Swap: 0B 0B 0B
- total(总物理内存):31G —— 系统检测到的物理内存总量 31 GB
- used(已用):12G —— 这是按 free 定义的“正在被进程真正占用的内存量(不含 buffer/cache)”
计算方式(近似、单位换算为 GB):
used ≈ total - free - buff/cache= 31.000G - 0.424G - 18.000G= 12.576G ≈ 显示的 12G(有四舍五入)
- free(空闲):424M —— 当前完全空着、没有用的物理内存;通常较小,因为 Linux 会把闲置内存用来做缓存。
- shared(共享):1.7G —— 表示被多个进程共享使用的内存(例如 tmpfs、/dev/shm 或某些共享内存段、容器共享页等)。这个字段在不同内核/工具上的解释略有差别,但一般反映共享映射占用。
- buff/cache(缓存/缓冲):18G —— 包括 page cache(页面缓存)和 kernel buffers 等,主要用于加速文件/磁盘 I/O。这部分内存是可回收的:当程序需要更多内存时,内核会回收这些缓存来分配给程序,因此它并不等同于“被浪费的内存”。
- available(可用):16G —— 这是内核估算的“能被新进程或现有进程扩展而无需交换(swap)的内存量”。它比 free 更有参考价值,因为把可回收的缓存考虑进去了。注意这是内核的估算值,不是硬性保证,但比直接看 free 更能反映实际可用内存。
- 关于差异和四舍五入:
free 输出是简化和四舍五入后显示的,所以 used + free + buff/cache 不一定精确等于 total(你看到的 12G + 0.424G + 18G ≈ 30.424G,和 31G 有小差异),这是显示精度造成的。- Swap 行:0B / 0B / 0B —— 没有配置 swap(既没有交换分区也没有交换文件)。没有 swap 时,一旦内存耗尽,系统会触发 OOM(Out-Of-Memory)杀手结束进程来回收内存;在许多服务器上通常建议至少配置适量 swap(依据需求而定)。
结论
系统有 31GB 物理内存:约 12GB 被进程实际占用,18GB 用作缓存/缓冲(可回收),free 显示只有 424MB 完全空闲,但内核估算 16GB 可立即分配给新进程而不需要 swap。Swap 没有配置(0B),如果担心在内存压力下触发 OOM,可以考虑配置适量 swap。总体来看,当前系统并不是“内存吃紧”的状态——因为大量缓存是可回收的。
3.2 VisualVM
下载地址: https://visualvm.github.io/download.html
- VisualVM是多功能合一的Java故障排除工具并且他是一款可视化工具,整合了命令行JDK工具和轻量级分析功能。
- 这款软件在JDK6~8中发版,在JDK9之后不存在了。
- 优点:功能封堵,实时监控CPU、内存、线程等详细信息。
- 支持IDEA插件,开发的过程中也可以使用。
- 缺点:对于大量集群化部署的Java进程需要手动进行管理。
启动一个Java项目
https://www.bilibili.com/video/BV1r94y1b7eS?spm_id_from=333.788.player.switch&vd_source=240d9002f7c7e3da63cd9a975639409a&p=44
https://www.bilibili.com/video/BV1QJ411P78Q/?spm_id_from=333.337.search-card.all.click&vd_source=240d9002f7c7e3da63cd9a975639409a
附录
- 课程地址