JVM——Java应用问题总结

文章探讨了Java应用中CPU消耗过高时,us和sy指标的含义,以及如何通过top和jstack命令定位问题。同时,解释了IO问题,包括文件IO和网络IO的监控与优化策略。此外,文章还讨论了JVM内存问题和GC调优,以及代码层面的优化建议,如释放引用、使用对象缓冲池和智能引用类型。
摘要由CSDN通过智能技术生成

对于java应用而言,CPU消耗严重时,主要体现在us、sy两个值上,wa的值变高是IO等待造成的,hi的值变高主要为硬件中断造成的,例如网卡接收数据频繁的状况

什么是CPU的问题

表现为us、sy的值过高

us

分析:当us值过高时表示运行的应用消耗了大部分的CPU,通常是在线程执行阻塞、循环、正则或者纯粹的计算等动作造成的(但还有一种可能us过高是频繁的GC导致的,如每次的请求都需要分配较多内存,当访问量高时,就会导致频繁的GC,以至于系统响应速度下降,进而造成堆积的请求更多,消耗内存更严重,最严重的时候可能还会导致系统不断进行Full GC,对于频繁GC的状况需要通过分析JVM内存的消耗来查找原因),并且CPU在执行该线程是并没有任何的挂起动作,导致CPU没有机会去调度其他线程。在这种情况下最主要的是找到消耗CPU的线程的代码

解决方法:首先可以通过top命令找到消耗CPU严重的线程及其ID,之后通过kill命令结束进程,或者使用jstack的方式打印线程的堆栈信息(dump出当前jvm线程的堆栈信息)。(注意:线程id对应的jvm线程id应该把top命令查到的线程改为十六进制才是jvm的线程id,因为堆栈里线程id使用十六进制表示的),也有一种优化方法是对造成us值高的线程的动作添加Thread.sleep,以释放CPU的执行权,降低CPU的消耗。

sy

分析:当sy值高时,表示linux花了更多的时间进行现成的切换(也有可能是线程之间锁竞争激烈),java应用造成这种现象的主要原因是线程比较多,且这些线程多数都处于不断地阻塞(例如wait,IO等待状态)和执行状态的变化过程中,这就导致产生了大量的上下文切换。在这种状态下最重要的就是找到线程不断切换的原因。可采用通过top命令或者jvm提供的工具找到不断切换状态的线程id,并将id转换为十六进制,通过jstack -l [ javapid ]的方式dump出java程序的线程信息,查看线程的状态以及锁信息,以此查询原因

解决方法:CPUsy高主要是因为线程的运行状态要经常切换,对于这种情况,最好的优化方法就是减少线程的数量。可以通过优化代码逻辑减少频繁创建的线程的数量。(例如可能是代码逻辑中创建了Thread对象,这就意味着运行了一个原生线程,当这个线程有任何阻塞动作,这个线程就会被挂起,但仍然占据着线程的资源。挂起的时候,无疑就浪费了原生的线程资源),也可以通过使用协程来管理线程资源,协程可以做到,在线程挂起时立即释放线程资源给其他请求,等到挂起结束再让线程继续执行。Java中可用的协程框架有kotlin和kilim。在使用kilim执行一项任务时,并不创建Thread,而是改为创建Task,Task相对于Thread而言就轻量级多了,但由于要在JVM堆中保存Task上下文信息,因此在采用Kilim的情况下要消耗更多的内存。

什么是IO类问题

文件IO问题:

Linux在操作文件时,会将数据放进文件缓冲区,因此查看Linux内存状况时经常发现物理内存用的不多,cached用了很多,这是Linux提升文件IO的一种做法。

在Linux中,要跟踪线程的文件IO的消耗,主要方法是通过pidstat来查找,输入如pidstat -d -t -p [oud] 1 100(其中-d 、-t、-p为pidstat的参数) ,即可查看线程IO消耗状况,

其中KB_rd/s表示每秒读取的KB数,KB_ wr/s表示每秒写入的KB数。

也可用iostat关注io状况

Device表示设备卷标名或分区名,tps时每秒的IO请求数,参数含义:

  • kb_read /s: 设备每秒读取的block数. -m 可以换成 MB
  • kb_wrtn/s: 每秒写入的block数.
  • kb_read:  读入的block总数.
  • kb_wrtn:  写入的block总数.

iostat -x中:

  • r/s表示每秒的请求数
  • w/s表示每秒写的请求数
  • await表示每次IO操作的等待时间
  • svctm表示平均每次设备执行IO操作的时间
  • util表示一秒钟用于IO操作时间的百分比

分析:IO的问题一般要关注%iowait的百分比,当iowait占据了主要的百分比时,就要关注IO方面的具体消耗状况了,可通过iostat -x打印详细信息,造成IO消耗严重主要是多个线程需要进行大量内容写入;或磁盘设备本身处理速度慢;或操作的文件本身已经很大造成的。可以通过iostat -x打印信息,发现异常的话,找到对应线程堆栈信息来分析。

网络IO问题:

网络IO消耗是通过sar命令查看的,sar -n DEV -f,java应用一般不会造成网络IO消耗严重,对于java应用而言当网络IO消耗高时,也只能对线程进行dump。

IO严重解决办法:

首先文件IO,就程序角度而言,造成文件Io严重的原因主要是多个线程在写大量数据到同一文件,导致文件很快变得很大,从而写入速度越来越main,并造成各线程激烈争抢文件锁。

解决的办法:

异步写文件

将写文件的动作从同步改成异步

批量读写

频繁的读写操作堆IO消耗会很严重,批量动作将大幅度提高IO操作的性能

限流

将文件IO消耗控制到一个可以接受的范围

限制文件大小

对于每个输出的文件,都应做大小的限制,在超过限制后可以生成一个新文件

其次网络IO,造成网络IO消耗严重的原因主要是同时需要发送或接受的包太多,常见的调优方法为限流,限制发送packet(包)的频率

什么是JVM的问题

分析:java程序中jvm的问题主要包括内存消耗过多、GC频繁或OOM的情况

如果出现以上问题就需要分析所耗费的时JVM外的物理内存还是JVM的堆区,如果时JVM外的物理内存,则要分析线程数量以及直接内存的使用情况,如果是JVM堆区,则要结合JDK工具分析层序中具体对象的内存占用情况

解决方式(JVM调优):一般来说JVM导致的问题都是因为JVM堆区或者其他区域的空间资源分配不均导致的,由此就要合理使用-Xms、 -Xmx、-Xmn、-XX:MaxTenuringThreshold、-XX:SurvivorRatio等参数

  • 首先-Xms和-Xmx通常设为相同的值,避免运行时要不断扩展JVM内存空间(经常拓展会导致程序运行速度下降)。
  • -Xmn决定了新生代空间的大小,新生代Eden区,S0,S1区域的比例可以通过-XX:SurvivorRatio来控制
  • -XX:MaxTenuringThreshold控制对象在经历多少次Minor GC才转入老年代。通常又被称为新生代存活周期

避免新生代空间设计过小

当新生代空间太小时,会导致频繁的Minor GC,二时有可能让导致Minor GC的对象直接进入老年代,此时如果老年代空间也不够用了,就会触发Full GC(可以通过jps和jstat命令观察到)

避免新生代设计过大

由此会导致两个现象,一是老年代变小可能会导致频繁的Full GC,二是minor GC的耗时会大大增加

避免Survivor区设置的过大或过小

调大SurvivorRatio值意味着Eden区域变大,minor GC的触发次数会降低,但此时Survivor区域变小,就会导致如果有超过Survivor空间大小的对象没有被回收,就会直接进入老年代。调小SurvivorRatio意味着Eden区域变小,minor GC的次数会增加,Survivor区域变大,意味着可以存储更多minor GC后存活的对象,避免进入老年代

可通过jps获得jvm线程id,再通过jstat [ javaid ]  [ 时间 ] [ 次数 ]查看GC信息的情况 

代码优化:

除了JVM调优外,在寻找到内存消耗严重的代码后,还可以从代码本身进行优化

释放不必要的引用

内存消耗严重的情况中最典型的一种现象就是代码中持有了不需要的对象的引用,造成这些对象无法被GC,从而占据了JVM堆内存。最典型的一个例子就是在复用线程的情况下使用ThreadLocal,由于线程复用,ThreadLocal中存放的对象如未做主动释放的话就不会被GC。

使用对象缓冲池

创建对象的实例要耗费一定的CPU以及内存,使用对象缓冲池一定程度上可降低JVM Heap内存的使用。

合理使用SoftReference和WeakReference

对于占据内存但又不是必须存在的对象,例如缓存对象,也可以基于SoftReference或WeakReference的方式来进行缓存。

此外还有一些使得程序执行慢的原因

锁竞争激烈

锁竞争激烈就会导致程序执行慢,一个典型例子就是数据库连接池,通常连接池提供的连接数都是有限的,假如连接数为10个,那么就意味着只有十个线程可以连接,如果此时有五十个线程准备进行连接池的操作,那么另外的40个线程会处于等待状态。导致程序执行的慢

未充分使用硬件资源

并发包中的类多数都采用了lock-free、nonblocking算法,减少了多线程情况下多线程情况下资源的锁竞争,因此对于多线程的环境,应尽量使用并发包中的类来实现比如双核CPU执行的程序都是单线程串行的操作,并没有充分发挥硬件资源的作用,就可以通过一些优化充分使用硬件资源,提升速度

  1. 使用Michael-Scott非阻塞队列算法
  2. 尽量少用锁,尽量让锁仅在需要的地方出现,通常没必要对整个方法加锁,而只对需要控制的资源做加锁操作。

数据量增长

例如当数据库数据从100万个上涨到1亿个,数据库的读写速度下降了,也会导致程序执行的速度下降。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值