Java非功能问题分析方法


Java非功能问题分析方法

线程堆栈分析

用于哪些问题?

  • 系统无缘无故CPU过高
  • 系统挂起无响应
  • 系统运行越来越慢
  • 性能瓶颈(如无法充分使用CPU)
  • 线程死锁、死循环、饿死等
  • 线程数量太多导致系统失败(如无法创建线程等)

具体知识

如何输出线程堆栈?

在windoes上:使用jvm指令,jps和jstack

我们看如下一个实例,一个普通的死循环。

 public static void main(String[] args) {
         while (true) {

         }
 }

执行jps指令,查看pid,
PS C:\CT\WorkSpace\Test> jps
13408
18704 org.eclipse.equinox.launcher_1.6.900.v20240613-2009.jar
23456 Recusion
10104 Jps
然后再根据pid用jstack查看堆栈
jstack 23456

在Linux上:ps -ef | grip java p stack

在linux的方法与windows上类似,这里不做示例。

如何解读线程堆栈?

对线程堆栈的解读,我们主要从三个方面展开,第一、线程解读,第二、锁的解读,第三、线程状态解读

线程解读

在dump的线程堆栈日志中,

搜索 标签,这代表类初始化;

搜索 标签,这代表对象初始化。

锁的解读

wait

在线程执行到wait时会释放监视锁,在wait执行完成后再持有该锁。

sleep

当线程执行到sleep时会继续持有监视锁 -> 该方法是线程的一个静态方法,与锁无关

lock

  • 当占有锁时:locked<0x25156....>
  • 当等待其它线程释放该锁:wait to lock<0x22552....>
  • 当一个线程占有一个锁,但又执行到该锁的wait上:先locked,后waiting on<0x666225....>
线程状态解读

首先我们看下,一个线程有哪些状态

注:当线程挂起时,都不消耗CPU,即当线程处于TIMED_WAITING和WAITING状态时肯定不消耗CPU;
当线程处于RUNNABLE状态时,需要看具体状态,一般线程是socket、IO操作和挂起时,不消耗CPU,在做运算时才消耗CPU

RUNNABLE

当locked时,一般不消耗CPU资源
TIMED_WAITING

  • on object monitor -> 表示线程被挂起一段时间,表示正在执行object.wait(int)方法
  • sleeping -> 表示线程被挂起一段时间,表示正在执行Thread.sleep(long)方法
  • parking -> 表示线程被挂起一段时间,表示正在执行Lock support.parkNanos(long)方法

WAITING(on object monitor)

表示线程被挂起一段时间,正在执行object.wait()方法,此时线程只能被notify唤醒

线程堆栈不能分析什么类型的问题?

  • 线程为什么跑飞(跑飞:通常指的是一个线程在执行过程中,由于某种原因偏离了预定的执行路径)
  • 并发的BUG导致的数据混乱
  • 数据库锁表问题

如何借助线程堆栈进行问题分析?

信息收集
  • 堆栈局部信息 -> 一般用于线程死锁分析
    • 每个线程的调用关系
    • 每个线程的状态
  • 一次堆栈的统计信息 -> 笨贼理论:出现最多的线程,性能有待推敲分析,资源不足、性能瓶颈分析
    • 锁争用情况
    • 是否有死锁
    • 是否达到性能瓶颈导致锁竞争
    • 结合代码分析是否有死循环
    • 大部分线程在做什么
    • 线程总数量
  • 多次线程的比对信息 -> 线程过高、死循环、性能瓶颈分析
    • 一个线程是否在长期执行
    • 某个线程是否长期获取不到锁
死锁分析

发生场景:当两个或多个线程正在等待被对方占用的锁时,死锁就会发生

分析:死锁的情况一般在堆栈信息中会有明确打印"Found on java-level deadlock"

CPU过高
  • 死循环导致 -> 计算机总有一个核的CPU达到100%,通过top指令查看

    分析多次堆栈信息,去除wait和lock的线程,每次都存在的线程都有风险

  • 系统设计不合理,譬如不间断轮询、过于复杂的算法
  • JNI中有死循环
  • 不恰当的堆内存设置,导致频繁GC从而CPU过高

    举个例子:应用启动时在一个大循环中创建对象,导致GC的场景

  • 32位JDK下,堆内存设置太大造成频繁GC
  • JDK自身死循环BUG
资源不足导致性能下降

表象:越来越慢,并最终停止响应

特点:大量线程停在同样的调用上下文中

原因

  • 资源数量配置太少
  • 获得资源的线程把持太久
  • 设计不合理
    • 慢SQL、索引等
  • 资源用完后没有及时关闭或者回收导致资源泄露或者减少
线程不退出导致系统挂死

分析方法:多次打印线程堆栈,找出一直活跃的线程

常见原因:

  • 该线程存在死循环
  • 资源不足或者资源泄露,造成当前线程阻塞在锁对象上
  • 与外部通信,外部系统无返回

性能瓶颈分析

目的

用更少的资源做更多的事

不消耗CPU的操作

  • 磁盘IO
  • 网络IO
  • 带有3D加速卡的图形运算

性能瓶颈

什么是一个好的程序?

我们都说性能调优,那我们进行性能调优的目的是什么呢?

这里可以给个答案:逐步增大压力,系统的CPU利用率能够接近100%。

所以我们可以两个方面来评估我们的程序

  • 增加压力,CPU的使用率能不能接近100%
  • 在CPU使用率到100%时,代码是否可以优化

常见场景

  • 不恰当的同步导致的资源争用
  • sleep滥用
  • string + 的滥用
  • 不恰当的线程模型
  • 效率低下的SQL
  • 不恰当的数据库设计
  • 不恰当的GC参数
  • 线程数量不足
  • 内存泄露导致频繁GC
  • 过度磁盘IO

内存泄露和堆内存设置

垃圾回收算法

不管什么垃圾回收算法,其实就做两件事

  1. 检测出垃圾对象
  2. 回收垃圾对象使用的堆空间

内存泄露

现象:不在需要的对象存在外部引用,导致虚拟机认为这些对象不是垃圾

场景

  • 全局容器
  • java虚拟机自身管理的对象
  • 调用外部函数

java的三种内存

  • 堆内存
  • 本地内存
  • 加载类的perm

症状

  • 系统越来越慢
  • 抛出OOM异常
    • 检查方向
    • 应用程序中缓存功能
    • 大量长期活动对象
    • 堆内存泄露
    • 本地内存泄露
  • core dump

定位

  • 堆内存
    • 堆内存设置大小
    • 代码泄露问题
    • 设计原因,如过多缓存数据库中数据
  • 本地内存
    • JNI调用
    • JDK的BUG
    • 操作系统的BUG
  • perm内存

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注