JVM调优思路和分析办法

JVM调优思路

  1. 常规JVM调优:当 Java 线上出现问题,如 CPU 飙升、负载突高、内存溢出等问题,需要查命令,查网络,然后 top、jps、jstat、jstack、jmap、jhat、hprof 等操作。
    对于OOM Erro dump转储文件、GC日志进行分析,解决问题
  2. 使用 Arthas 轻松定位,图形化界面、迅速解决,及时止损。
    https://blog.youkuaiyun.com/u013735734/article/details/102930307
    监控: arthas / jconsole / jvisualVM / Jprofiler(最好用)
  3. 线上查找 ( cmdline || arthas )
    jstat -gc 动态观察gc情况 stat -gc 4655 500 : 每个500个毫秒打印GC的情况

jps: 查看JVM中运行的进程状态信息
jstack: 查看某个Java进程内的线程堆栈信息
jmap 查看堆内存使用状况,一般结合jhat使用. jmap -dump:format=b,file=dumpFileName pid
jstat: 对应用程序的资源和性能进行实时的命令行的监控

查看运行的 java 进程信息 (top、jps )

  • $ jps -mlvV
  • $ jps -mlvV | grep [xxx]
  • $ top 、 top -Hp pid

(1) top 查看所有进程的 CPU、内存状态

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7945 root 20 0 2813304 301908 14000 S 101.3 30.3 20:44.36 java

(2) top -Hp pid 查看指定进程下所有线程的 CPU、内存状态

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7947 root 20 0 2813304 309196 14064 S 93.7 31.0 28:16.02 java

查看GC汇总信息 ( jstat )

jstat -gcutil 进程ID

S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 99.33 86.40 97.12 94.49 21 2.968 5 1.027 3.996

S0 —> 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1 —> 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E —> 年轻代中Eden(伊甸园)已使用的占当前容量百分比

O —> old代已使用的占当前容量百分比
M —> 元数据空间使用比例
CCS —> 压缩使用比例

YGC —> 从应用程序启动到采样时年轻代中gc次数
YGCT —> 从应用程序启动到采样时年轻代中gc所用时间(s)
FGC —> 从应用程序启动到采样时old代(全gc)gc次数
FGCT —> 从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT —> 从应用程序启动到采样时gc用的总时间(s)

jstat -gc 动态观察gc情况:

jstat -gc 4655 500 : 每个500个毫秒打印GC的情况

进程、线程信息并进行分析 (jinfo 、jstack、jmap 、jhat )

jinfo pid
进程和线程的 具体信息

jstack
jstack 定位线程、进程状况

jmap -histo 4655 | head -20
查找有多少对象产生

jmap -dump:live,format=b,file=heap.bin pid
生成dump转储文件,线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(生产环境慎用)

使用MAT / jhat / jvisualvm 进行dump文件分析
https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html

jhat -J-mx512M xxx.dump
http://192.168.17.11:7000

服务启动方式:
nohup java -jar project.jar >> log.out 2>&1 &
错误输出和标准输出,追加到log.out

jmp内存转存文件分析

dump 文件里,值得关注的线程状态有:

  1. 死锁Deadlock(重点关注)
  2. 执行中Runnable
  3. 等待资源Waiting on condition(重点关注)
  4. 等待获取监视器Waiting on monitor entry(重点关注)
  5. 暂停Suspended
  6. 对象等待中Object.wait()TIMED_WAITING
  7. 阻塞Blocked(重点关注)
  8. 停止Parked

综合示范一:Waiting to lock 和 Blocked

实例如下:

"RMI TCP Connection(267865)-172.16.5.25" daemon prio=10 tid=0x00007fd508371000 nid=0x55ae waiting for monitor entry [0x00007fd4f8684000]
   java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.log4j.Category.callAppenders(Category.java:201)
- waiting to lock <0x00000000acf4d0c0> (a org.apache.log4j.Logger)
at org.apache.log4j.Category.forcedLog(Category.java:388)
at org.apache.log4j.Category.log(Category.java:853)
at org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234)
at com.tuan.core.common.lang.cache.remote.SpyMemcachedClient.get(SpyMemcachedClient.java:110)
……

1)线程状态是 Blocked,阻塞状态。说明线程等待资源超时!
2)waiting to lock <0x00000000acf4d0c0>指,线程在等待给这个 0x00000000acf4d0c0 地址上锁(英文可描述为:trying to obtain 0x00000000acf4d0c0 lock)。
3)在 dump 日志里查找字符串 0x00000000acf4d0c0,发现有大量线程都在等待给这个地址上锁。如果能在日志里找到谁获得了这个锁(如locked < 0x00000000acf4d0c0>),就可以顺藤摸瓜了。
4)waiting for monitor entry 说明此线程通过 synchronized(obj) {……} 申请进入了临界区,从而进入了下图1中的Entry Set队列,但该 obj 对应的 monitor 被其他线程拥有,所以本线程在 Entry Set 队列中等待。
5)第一行里,RMI TCP Connection(267865)-172.16.5.25是 Thread Name 。tid指Java Thread id。nid指native线程的id。prio是线程优先级。[0x00007fd4f8684000]是线程栈起始地址。

Dump文件中的线程状态含义及注意事项

含义如下所示:
Deadlock:死锁线程,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况。
Runnable:一般指该线程正在执行状态中,该线程占用了资源,正在处理某个请求,有可能正在传递SQL到数据库执行,有可能在对某个文件操作,有可能进行数据类型等转换。
Waiting on condition:等待资源,或等待某个条件的发生。具体原因需结合 stacktrace来分析。
如果堆栈信息明确是应用代码,则证明该线程正在等待资源。一般是大量读取某资源,且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取。
又或者,正在等待其他线程的执行等。如果发现有大量的线程都在处在 Wait on condition,从线程 stack看,正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。(1)一种情况是网络非常忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写;(2)另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。另外一种出现 Wait on condition的常见情况是该线程在 sleep,等待 sleep的时间到了时候,将被唤醒。
Blocked:线程阻塞,是指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。
Waiting for monitor entry 和 in Object.wait():Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。从下图1中可以看出,每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 Active Thread,而其它线程都是 Waiting Thread,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 Waiting for monitor entry,而在 “Wait Set”中等待的线程状态是 in Object.wait()
在这里插入图片描述

综合示范二:Waiting on condition 和 TIMED_WAITING

实例如下:

"RMI TCP Connection(idle)" daemon prio=10 tid=0x00007fd50834e800 nid=0x56b2 waiting on condition [0x00007fd4f1a59000]
   java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for  <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:198)
at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:424)
at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:323)
at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:874)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:945)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
at java.lang.Thread.run(Thread.java:662)

1)TIMED_WAITING (parking)中的 timed_waiting 指等待状态,但这里指定了时间,到达指定的时间后自动退出等待状态;parking指线程处于挂起中。
2)waiting on condition需要与堆栈中的parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)结合来看。首先,本线程肯定是在等待某个条件的发生,来把自己唤醒。其次,SynchronousQueue 并不是一个队列,只是线程之间移交信息的机制,当我们把一个元素放入到 SynchronousQueue 中时必须有另一个线程正在等待接受移交的任务,因此这就是本线程在等待的条件。
3)别的就看不出来了。

综合示范三:in Obejct.wait() 和 TIMED_WAITING

实例如下:

"RMI RenewClean-[172.16.5.19:28475]" daemon prio=10 tid=0x0000000041428800 nid=0xb09 in Object.wait() [0x00007f34f4bd0000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000aa672478> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
- locked <0x00000000aa672478> (a java.lang.ref.ReferenceQueue$Lock)
at sun.rmi.transport.DGCClient$EndpointEntry$RenewCleanThread.run(DGCClient.java:516)
at java.lang.Thread.run(Thread.java:662)

1)TIMED_WAITING (on object monitor),对于本例而言,是因为本线程调用了 java.lang.Object.wait(long timeout) 而进入等待状态。
2)Wait Set中等待的线程状态就是in Object.wait()。当线程获得了 Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。只有当别的线程在该对象上调用了 notify() 或者 notifyAll() ,“ Wait Set”队列中线程才得到机会去竞争,但是只有一个线程获得对象的 Monitor,恢复到运行态。
3)RMI RenewClean 是 DGCClient 的一部分。DGC 指的是 Distributed GC,即分布式垃圾回收。
4)请注意,是先 locked <0x00000000aa672478>,后 waiting on <0x00000000aa672478>,之所以先锁再等同一个对象,请看下面它的代码实现:

static private class  Lock { };
private Lock lock = new Lock();
public Reference<? extends T> remove(long timeout){
    synchronized (lock) {
	    Reference<? extends T> r = reallyPoll();
	    if (r != null) return r;
	    for (;;) {
	        lock.wait(timeout);
	        r = reallyPoll();
        	……
   }
}

即,线程的执行中,先用 synchronized 获得了这个对象的 Monitor(对应于 locked <0x00000000aa672478> );当执行到 lock.wait(timeout);,线程就放弃了 Monitor 的所有权,进入“Wait Set”队列(对应于 waiting on <0x00000000aa672478> )。
5)从堆栈信息看,是正在清理 remote references to remote objects ,引用的租约到了,分布式垃圾回收在逐一清理呢。

总结

尽管JDK提供了这么多的工具来供我们使用,但是大多数情况是需要我们去选择使用的,一般来说在生产环境主要从这3个方面去考虑。

  • 生产服务器推荐开启

-XX:-HeapDumpOnOutOfMemoryError: 默认关闭,建议开启,在 java.lang.OutOfMemoryError 异常出现时,输出一个 dump 文件,记录当时的堆内存快照。

-XX:HeapDumpPath=./java_pid<pid>.hprof :用来设置堆内存快照的存储文件路径,默认是 java 进程启动位置。

  • 调优之前开启、调优之后关闭

-XX:+PrintGC :调试跟踪,打印简单的 GC 信息参数。

-XX:+PrintGCDetails, +XX:+PrintGCTimeStamps :打印详细的 GC 信息

-Xlogger:logpath :设置 gc 的日志路径,如: -Xlogger:log/gc.log, 将 gc.log 的路径设置到当前目录的 log 目录下。

应用场景:
将 gc 的日志独立写入日志文件,将 GC 日志与系统业务日志进行了分离,方便开发人员进行追踪分析。

  • 考虑使用

-XX:+PrintHeapAtGC: 打印堆信息 。
应用场景:获取堆在每次垃圾回收前后的使用状况 。

-XX:+TraceClassLoading,-XX:+TraceClassUnloading:可以跟踪类加载和卸载的情况,可以用来排查 class 的冲突问题。
应用场景:
(1)在系统控制台信息中看到 class 加载的过程和具体的 class 信息,可用以分析类的加载顺序以及是否可进行精简操作。
(2)如果碰到经常Full GC的情况,但是老年代空间使用的却不多,年轻代GC后的情况也很正常,同时也不存在突然大对象的情况,但是元空间却一直递增,那么可以考虑下是不是使用了反射等手段导致元空间加载的类太多了,导致元空间爆满触发Full GC,那么此时就可以加上这两个参数,看下类加载和卸载的情况,确定下是不是有哪些类反复被生成和加载,找到相应的类,然后跟踪到代码里,排除问题。

<think>好的,我需要处理用户关于Java互联网医院项目中常见的JVM问题及其解决方案的查询。首先,用户提到了他们希望得到结构清晰的回答,并且需要引用提供的参考资料。特别是引用[1]中提到了几种排查死锁的工具,如jps、jstack、jsconsoleVisualVM,这可能与JVM问题相关。 首先,我要确定常见的JVM问题有哪些。通常来说,内存泄漏、内存溢出、频繁的GC、死锁以及类加载问题都是常见的JVM问题。用户的项目是互联网医院,这类系统通常对高并发稳定性要求较高,因此内存管理线程问题可能更为突出。 接下来,我需要结合引用内容来支持回答。例如,引用[1]中的工具可以用来排查死锁内存溢出,比如VisualVM。此外,引用[3]虽然主要关于SQL,但提到了分号的使用习惯,这可能间接提醒在代码规范上注意,避免资源未释放导致内存泄漏。 对于每个问题,我需要举出具体的例子解决方法。例如,内存泄漏问题可能出现在缓存未清理或者数据库连接未关闭,解决方案包括使用弱引用或定期清理。频繁GC可能是因为对象创建过多,需要化代码结构或JVM参数。死锁问题可以通过引用[1]中的工具来检测,并确保锁的顺序一致。 还要注意用户提到的LaTeX格式要求,行内公式用$...$,独立公式用$$...$$。但当前问题涉及JVM,可能数学公式较少,但如果有参数设置比如堆大小,可以用$Xms$$Xmx$来表示。 最后,生成相关问题需要考虑用户可能进一步想了解的内容,比如如何监控JVM状态,参数,或者特定工具的使用方法。确保问题与之前的回答相关,并引导用户深入探讨。 现在需要将以上思路整理成结构化的回答,分点说明每个问题、现象、原因解决方法,并正确引用资料。同时,按照示例的格式,使用代码块展示可能的示例代码,比如模拟内存泄漏的代码片段。</think>### Java互联网医院项目常见JVM问题及解决方法 #### 1. **内存泄漏/内存溢出(OOM)** - **现象**:系统运行时间越长,响应越慢,最终抛出$OutOfMemoryError$错误(如`java.lang.OutOfMemoryError: Java heap space`)。 - **原因**: - 缓存未设置过期策略(如长期未清理的静态Map)。 - 数据库连接未释放。 - 大量大对象未及时回收(如医疗影像文件处理)。 - **解决方法**: - 使用弱引用(`WeakReference`)或缓存框架(如Ehcache、Redis)管理缓存。 - 通过`try-with-resources`确保资源释放,例如数据库连接池配置超时时间。 - JVM堆大小参数(如`-Xms``-Xmx`),并通过VisualVM分析堆转储(Heap Dump)定位泄漏点[^1]。 ```java // 示例:静态Map导致的内存泄漏 public class MedicalCache { private static Map<String, Object> cache = new HashMap<>(); public void addToCache(String key, Object value) { cache.put(key, value); // 长期未清理的静态数据 } } ``` --- #### 2. **频繁Full GC导致系统卡顿** - **现象**:系统周期性卡顿,GC日志中`Full GC`频率高,CPU占用突增。 - **原因**: - 年轻代(Young Generation)空间过小,对象过早晋升到老年代。 - 存在内存泄漏或大对象直接分配在老年代。 - **解决方法**: - 整新生代比例(如`-XX:NewRatio=2`)或使用G1垃圾回收器(`-XX:+UseG1GC`)。 - 避免在代码中创建过大的数组或集合(如一次性加载全部患者数据)。 --- #### 3. **线程死锁** - **现象**:系统部分功能完全无响应,日志中显示线程阻塞。 - **原因**:多线程竞争资源时加锁顺序不一致,例如: ```java // 线程1先锁A再锁B,线程2先锁B再锁A public void transfer(Account from, Account to) { synchronized (from) { synchronized (to) { /* ... */ } } } ``` - **解决方法**: - 使用`jstack`或VisualVM的线程分析功能检测死锁。 - 统一锁的获取顺序,或使用`ReentrantLock`尝试超时获取锁(`tryLock`)。 --- #### 4. **类加载冲突(NoClassDefFoundError/ClassNotFoundException)** - **现象**:依赖冲突导致服务启动失败或方法用异常。 - **原因**:医疗项目中多模块依赖不同版本的库(如Jackson、Fastjson)。 - **解决方法**: - 使用`mvn dependency:tree`排查依赖树。 - 通过`-verbose:class`参数输出类加载日志,定位冲突JAR包。 --- #### 5. **Metaspace溢出** - **现象**:`java.lang.OutOfMemoryError: Metaspace`。 - **原因**:动态生成大量类(如反射、CGLIB代理),或Metaspace配置过小。 - **解决方法**: - 整Metaspace大小(`-XX:MetaspaceSize=128m`)。 - 限制框架的动态类生成(如Spring的`proxyTargetClass`配置)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

phial03

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值