jvm的调优

时间:2021-11-14 14:29:38

  首先我们要知道jvm的调优,主要是对那些部分的优化。通过jvm内存模型我们可以,首先是分析遇到的问题,然后通过一些工具或者手段找到问题所在,然后通过一定的措施解决问题,下面我们也将按着这个思路来给出具体的操作。

问题分析

  这个主要是根据我们在运行程序时出现的问题:内存溢出,栈溢出,或者请求停顿。

解决方案

  堆内存溢出的话我们首先看jvm的配置参数:

  • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0

    -Xmx3550m:设置JVM最大可用内存为3550M。

-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5。但是如果设置了年轻代的大小和最大最小对内存,这个值就没有必要设置了。

-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6.一般默认比值为2/8

-XX:MaxPermSize=64m:设置永久代大小为64m。一般设置为物理内存的1/64。(这个可以看着做事方法区的大小,在HotSpot虚拟机中方法去和永久带是一个概念)

-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

jvm的参数详解

栈溢出

  主要有栈溢出两种情景:

  • 线程请求的栈的深度大于虚拟机所允许的最大深度,将抛出*Error

    栈的深度和设置的-xss的大小以及和局部变量的数量以及大小有关,-xss越小,局部变量个数越多长度越长,深度就越小。栈帧存放着局部变量,方法返回值,这些值越多,或者值越大,那么战阵就越大,占用的内存就越大,栈的深度就越小。

  • 虚拟机在扩展栈时无法申请到足够的空间,将抛出OutOfMemoryError

  栈的大小(虚拟机栈和本地方法栈瓜分的大小)=系统限制的内存的大小-最大堆内存的大小(Xmx)-最大方法区内存(MaxPermSize)-程序计数器(很小)-虚拟机本身耗费的内存(很小)

方法区溢出

  常量池内存溢出:运行时常量池也属于方法区

  其他方法区内存溢出:主要是存放class相关信息的地方。主要是大量使用cglib技术,增强类越多,就需要越大的方法区内存。

使用到的工具

  • jstack:java堆栈跟踪工具

  此工具是查看某个进程下的当前时刻线程的状态信息的,生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机中每一条线程正在执行的方法堆栈的集合,主要目的是定位线程长时间停顿的原因。

命令格式:

  jstack [ option ] pid

基本参数:

  -F 当'jstack [-l] pid'没有相应的时候强制打印栈信息

  -l 长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.

  -m 打印java和native c/c++框架的所有栈信息. -h | -help打印帮助信息

  pid 需要被打印配置信息的java进程id,可以用jps工具查询

重点关注的线程状态:   

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

线程状态解析 

  Deadlock:死锁线程,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况。
  Runnable:一般指该线程正在执行状态中,该线程占用了资源,正在处理某个请求,有可能正在传递SQL到数据库执行,有可能在对某个文件操作,有可能进行数据类型等转换。
  Waiting on condition
    该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stacktrace来分析。最常见的情况是线程在等待网络的读写,
  比如当网络数据没有准备好读时,线程处于这种等待状态,而一旦有数据准备好读之后,线程会重新激活,读取并处理数据。在 Java引入 NewIO之前,对于每个网络连接,
  都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪费,而且给操作系统的线程调度也带来压力。
  在 NewIO里采用了新的机制,编写的服务器程序的性能和可扩展性都得到提高。
  如果发现有大量的线程都在处在 Wait on condition,从线程 stack看, 正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。
  一种情况是网络非常忙,几 乎消耗了所有的带宽,仍然有大量数据等待网络读 写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。
  所以要结合系统的一些性能观察工具来综合分析,比如 netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制 ; 观察 cpu的利用率,如果系统态的 CPU时间,
  相对于用户态的 CPU时间比例较高;如果程序运行在 Solaris 10平台上,可以用 dtrace工具看系统调用的情况,如果观察到 read/write的系统调用的次数或者运行时间遥遥领先;
  这些都指向由于网络带宽所限导致的网络瓶颈。另外一种出现 Wait on condition的常见情况是该线程在 sleep,等待 sleep的时间到了时候,将被唤醒。
  blocked:线程阻塞,是指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。
  Waiting for monitor entry 和 in Object.wait():Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。

jstack报告分析

   jvm的调优

     jvm的调优

  可以看到thread1在进行等待获取到锁,此时进入waiting for monitor entry,并是阻塞状态。而main线程提前获取到锁,当由于调用了sleep此时进入到Timed_waiting状态,此时man线程锁住的对象地址是7f3167cf0,而thread1正在等待获取这个锁对象。
prio:线程的优先级
tid:线程id
nid:操作系统映射的线程id, 非常关键,后面再使用jstack时补充;
1103e9000
106692000 :表示线程栈的起始地址。
run():这个方法里面包含的出现问的对象以及代码行号
  从jstack日志中,可以看到:主线程获取到thread2对象上的锁,因此正在执行sleep操作,状态为TIMED_WAINTING, 而thread2由于未获取到thread2对象上的锁,因此处于BLOCKED状态。再细看,thread2 正在"waiting to lock <7f3167cf0>",即试图在地址为7f3167cf0所在的对象获取锁,而该锁却被main线程占有(locked <7f3167cf0>)。main线程正在"waiting on condition",说明正在等待某个条件触发,由jstacktrace来看,此线程正在sleep。
经验:如果在jstack日志发现大量的线程在waiting to lock 某个地址,只要能查到哪个线程获取到锁就可以方便定位问题了
  • jstat:虚拟机统计信息监视工具

    主要显示虚拟机进程中的类装载,内存,垃圾收集,jit编译等运行时的数据,是定位云定期虚拟机问题的首选工具

命令格式:

  jstat -<option> [-t] [-h<lines>] <vmid> [<interva[s|ms]> [<count>]]

参数解释:

  Options — 选项,我们一般使用 -gcutil 查看gc情况

  vmid      — VM的进程号,即当前运行的java进程号

  interval[s|ms]  ——  间隔时间,单位为秒或者毫秒,默认为ms。必须是正整型。

  count     — 打印次数,如果缺省则打印无数次

  选项参数

    jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id,和所选参数。以下详细介绍各个参数的意义。 
      jstat -class pid:显示加载class的数量,及所占空间等信息。 
      jstat -compiler pid:显示VM实时编译的数量等信息。 
      jstat -gc pid:可以显示gc的信息,查看gc的次数,及时间。其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。 
      jstat -gccapacity:可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,如:PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。其他的可以根据这个类推, OC是old内纯的占用量。 
      jstat -gcnew pid:new对象的信息。 
      jstat -gcnewcapacity pid:new对象的信息及其占用量。 
      jstat -gcold pid:old对象的信息。 
      jstat -gcoldcapacity pid:old对象的信息及其占用量。 
      jstat -gcpermcapacity pid: perm对象的信息及其占用量。 
      jstat -gcutil pid:统计gc信息统计。 
      jstat -printcompilation pid:当前VM执行的信息。

使用详情分析

  • jmap:java内存映像工具

  主要是用于生成堆转存快照。

命令行格式:

  jmap [option] LVMID(LVMID指的是进程ID)

option参数:

  • dump : 生成堆转储快照,可以再windows下使用
  • finalizerinfo : 显示在F-Queue队列等待Finalizer线程执行finalizer方法的对象,不可以再windows下使用
  • heap : 显示Java堆详细信息,不可以再windows下使用
  • histo : 显示堆中对象的统计信息,包括类,实例数量,合计荣力量。可以再windows下使用
  • permstat : to print permanent generation statistics,不可以再windows下使用
  • F : 当-dump没有响应时,强制生成dump快照,不可以再windows下使用

  

  • jinfo
  • jps
  • jhat

jvm调优工具大全

可视化工具

  • jconsole

  jconsole图形界面分析

  • visualVM