JVM调优篇-09

举报
kwan的解忧杂货铺 发表于 2024/08/29 20:07:46 2024/08/29
【摘要】 1.CPU 过高问题排查?1.top 命令使用 top 查询到 cup 过高的进程 PIDtop2.查线程#根据进程id查询占用高的线程idps H -eo pid,tid,%cpu | grep 19235#查看十六进制的异常线程PIDprintf "%x\n" 线程PId#生成stack文件jstack -l 3033 > ./3033.stack#查看线程信息cat 3033.st...

1.CPU 过高问题排查?

1.top 命令

使用 top 查询到 cup 过高的进程 PID

top

2.查线程

#根据进程id查询占用高的线程id
ps H -eo pid,tid,%cpu | grep 19235

#查看十六进制的异常线程PID
printf "%x\n"  线程PId

#生成stack文件
jstack -l 3033 > ./3033.stack

#查看线程信息
cat 3033.stack |grep 'xxxx'

#查看使用最耗费cpu的线程堆栈信息
cat stack | grep -i 34670 -C10 --color

3.异常信息

#使用jstack查看进程,并查找指定线程的前100行信息
jstack pid |grep 线程id -A100

4.查看gc信息

 jstat -gc pid 1000 100 > gctimes.log

5.使用脚本

有用的脚本地址

sudo sh show-busy-java-threads -p 19235

2.生产环境访问慢?

1.查看错误日志

#最后1000行
tail  -1000   xxx.log

如果看到 OOM

2.进容器排查

#获取容器id,并查看容器运行状态
docker stats

#进入容器
docker exec -it 47863d1021e9   /bin/bash

#十进制转十六进制
printf "%x\n"  24306

#找到进程pid
ps -ef |grep java

#查看gc情况,1000ms执行一次,一共执行10次
jstat -gc 进程pid 1000 10

#线程快照,用于排查线程间死锁、死循环、请求外部资源导致的长时间挂起等
jstack 进程pid

#打印出堆内存相关信息
jmap -heap 进程pid

#生成dump文件
jmap -dump:format=b,file=/home/heap.hprof 进程pid

#图形化展示
jhat heap.hprof

3.OOM 问题定位

#可以查看新生代,老年代堆内存的分配大小以及使用情况,看是否本身分配过小
jmap -heap pid

#结果以表格的形式显示存活对象的信息,并按照所占内存大小排序,找到最耗内存的对象
jmap -histo:live 进程pid | more

3.visualVM

在服务器上使用命令导出文件

#检测容器占用的内存和cpu,以及IO情况
docker stats

#进入容器
docker exec -it 容器id bash

#进入home目录
cd  /home

#查找进程id
ps -ef|grep java

#生成dump文件
jmap -dump:format=b,file=/home/heap-$(date +%F%n).hprof  进程id

#拷贝文件到宿主机
docker cp   ce4830fa74f7:/home/heap-2023-03-23.hprof  /home/app/deepexi-dsc-belle-insight-command/

打开 visualVM 软件,导入生成的 dump 文件,主要观测概览,对象,线程三个主要信息

summary:概览

image-20230310233754036

objects:对象信息

image-20230310233911817

线程:观测可能出现的死锁信息

image-20230310233845034

在 idea 中使用,需要使用 visualVM 启动应用

image-20230310235929611

image-20230310235942070

image-20230310235950090

4.jvm 调优如何提前调参?

JVM(Java 虚拟机)调优是优化 Java 应用程序性能的关键步骤之一。在实际应用中,提前调整 JVM 参数可以在一开始就为应用程序的性能和稳定性奠定良好的基础。以下是在提前调优 JVM 时应该考虑的一些关键方面:

  1. 了解应用需求: 在开始调优之前,了解应用程序的性能需求、资源使用情况以及预期的负载情况。这将有助于您根据实际情况选择合适的调优策略。

  2. 硬件资源: 考虑应用程序运行所需的硬件资源,例如内存、CPU 核数等。这有助于您设置合适的 JVM 参数,以充分利用可用资源。

  3. 内存设置: 调整堆内存大小是 JVM 调优的一个关键因素。您可以使用 -Xms(初始堆大小)和 -Xmx(最大堆大小)参数来配置。合理地设置堆内存可以避免频繁的垃圾回收,从而提高性能。

  4. 垃圾回收: 选择适当的垃圾回收器和调整相关参数是重要的。不同的应用场景可能需要不同的垃圾回收策略,例如 CMS、G1、Parallel 等。

  5. 线程设置: 根据应用程序的并发需求,调整与线程相关的参数,如线程池大小、并发线程数等。

  6. GC 日志和监控: 启用 GC 日志记录以及相关监控工具(如 VisualVM、JConsole)有助于您分析垃圾回收性能,以及发现内存泄漏等问题。

  7. PermGen/Metaspace 调整: 对于较老的 JVM 版本,考虑调整 PermGen 空间大小(使用-XX:PermSize-XX:MaxPermSize参数)。在 Java 8+中,PermGen 已被 Metaspace 取代。

  8. 健康监控: 配置应用程序的健康监控,以及性能指标采集和报警机制,以便在出现问题时及时做出反应。

  9. 压力测试: 使用压力测试工具模拟实际负载,以观察应用程序在不同负载下的表现,从而根据测试结果进行调优。

  10. 逐步调整: 调优过程中建议逐步更改参数,然后进行测试和评估,避免一次性更改过多参数造成不可预测的结果。

调优是一个实验和优化的过程。根据应用程序的性质和需求,您可能需要尝试不同的参数设置并进行测试,以找到最佳的配置。同时,时刻关注最新的 JVM 版本和最佳实践,以确保您的应用程序在性能和稳定性方面保持在最佳状态。

5.如何做 JVM 预热?

通过流量控制来进行预热:

  • 利用网关的流量控制功能,根据新服务上线的时间,给予不同的访问权重。这样,服务能够逐步达到正常访问的热度,避免因为流量过大导致服务崩溃。
  • 使用 sentinel 等组件进行 warmup 限流,在服务刚上线时,将过高的流量直接拦截,防止对服务造成过大的压力,确保服务的稳定运行。
  • spring 的 ribbon 组件策略改造,使其流量控制策略与网关的流量控制策略保持一致。这样,可以更好地协调各个组件之间的流量控制,提高服务的预热效果。

对外服务之前,通过合适的手段提前预热

  • 服务开发者可以在编写代码时,设计一个初始化预热模块,该模块在服务启动后会自动执行。
  • 在这个初始化模块中,可以编写逻辑来遍历所有的重要访问接口,这样在服务启动后,就能对这些接口进行预热。
  • 这种方式能够确保服务在启动后的早期阶段,就对重要的访问接口进行了遍历,提高了服务的响应速度和稳定性。

6.内存溢出查看

docker stats 是 Docker 命令行工具的一部分,用于显示关于运行中容器的实时资源使用情况的统计信息。下面是对 docker stats 命令的中文详细解释:

#查看状态
docker stats

#显示指定容器
docker stats container_name

#显示所有容器(包括停止的)
docker stats --all

#指定格式
docker stats --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"

image-20240110174014079

docker stats 输出的信息包括以下列:

  • CONTAINER:容器的名称或 ID。
  • CPU %:CPU 使用率。
  • MEM USAGE / LIMIT:内存使用量和限制。
  • MEM %:内存使用率。
  • NET I/O:网络输入/输出。
  • BLOCK I/O:块设备输入/输出。
  • PIDS:进程 ID 数量。

如果机器内存充足的情况下,可以适当调大堆内存的大小:

Xmx 不能大于总内存的 3/4

ENV JAVA_OPTS="\
-Xms4g \
-Xmx4g \
-Xmn2g \
-Xss1m \
-XX:SurvivorRatio=8 \
-XX:MaxTenuringThreshold=10 \
-XX:+UseConcMarkSweepGC \
-XX:CMSInitiatingOccupancyFraction=70 \
-XX:+UseCMSInitiatingOccupancyOnly \
-XX:+AlwaysPreTouch \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=:./logs/gc \
-verbose:gc \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintGCTimeStamps \
-Xloggc:./logs/gc/gc.log \
"

7.如何导出堆内存文件

非容器启动

#第一步:通过jps命令确认jvm进程号
[root@dataprocess-server]# jps -l
19570 customer-datap-1.3.2.jar
10589 sun.tools.jps.Jps

#第二步:通过jmap命令dump堆内存文件到指定目录
[root@dataprocess-server]# jmap -dump:format=b,file=/temp/dump.thprof 19570
Dumping heap to /temp/dump.thprof ...
Heap dump file created

配置OOM自动生成dump文件:

在 Java 虚拟机(JVM)启动时,可以通过设置一些参数来配置 OutOfMemoryError(OOM)时自动生成 Dump 文件。Dump 文件是 JVM 在遇到 OOM 时生成的一种内存转储文件,它包含了 JVM 堆内存的快照,有助于诊断内存溢出问题。

#JVM在发生OutOfMemoryError时生成Heap Dump文件
java -XX:+HeapDumpOnOutOfMemoryError -jar your_application.jar

#指定Heap Dump文件的输出路径
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump/files -jar your_application.jar

#当发生OutOfMemoryError时,执行指定的命令
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump/files -XX:OnOutOfMemoryError="kill -9 %p" -jar your_application.jar

容器启动:

#docker容器的基本信息
docker stats
#这里的PIDS是容器内的PID,基本上没什么用

image-20240116231214503

Dockerfile 配置:

FROM openjdk:latest

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone

WORKDIR /home
USER root

ENV PROFILE="dev"
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV JAVA_OPTS="\
-Xms4g \
-Xmx4g \
-Xmn2g \
-Xss1m \
-XX:SurvivorRatio=8 \
-XX:MaxTenuringThreshold=10 \
-XX:+UseConcMarkSweepGC \
-XX:CMSInitiatingOccupancyFraction=70 \
-XX:+UseCMSInitiatingOccupancyOnly \
-XX:+AlwaysPreTouch \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=:./logs/gc \
-verbose:gc \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintGCTimeStamps \
-Xloggc:./logs/gc/gc.log \
"

ENV PARAMS=""

COPY ./insight-provider/target/*.jar /home/app.jar

EXPOSE 80

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
RUN echo -e 'mkdir -p ./logs/gc && java $JAVA_OPTS -jar ./app.jar --spring.profiles.active=$PROFILE $PARAMS' > entrypoint.sh

ENTRYPOINT ["sh", "entrypoint.sh"]

启动脚本:

#!/usr/bin/env bash
CONTAINER_NAME=insight-command
IMAGE_NAME=xxx.xxx.com/xxx-uat/xxx-xx-xx-insight-command:$1
docker rm -f ${CONTAINER_NAME}
docker rmi ${IMAGE_NAME}
docker pull ${IMAGE_NAME}
docker run -d --name ${CONTAINER_NAME} \
--privileged=true \
-e PROFILE=uat \
-e PARAMS="--logging.level.root=info" \
-w /home \
-p 8090:80 \
-v $PWD/logs:/home/logs \
-v /home/uploads:/home/uploads \
--restart=always ${IMAGE_NAME}
docker logs -f  --tail 500  ${CONTAINER_NAME}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。