资讯专栏INFORMATION COLUMN

jvm性能优化

WelliJhon / 2597人阅读

摘要:前言入门垃圾回收机制后,接下来可以学习性能调优了。输出老年代空间的性能数据。新生代最小空间容量,单位。拥有者表示线程成功竞争到对象锁。线程状态,未启动的。,无限期等待另一个线程执行特定操作。主要调优参数设定堆内存大小,这是最基本的。

Java程序员进阶三条必经之路:数据库、虚拟机、异步通信。

前言

入门JVM垃圾回收机制后,接下来可以学习性能调优了。主要有两部分内容:

JDK工具的使用。

调优策略。

兵器谱 jps

列出正在运行的虚拟机进程,用法如下:

jps [-option] [hostid]

| 选项 | 作用 |
| -------- | -----: |
| q | 只输出LVMID,省略主类的名称 |
| m | 输出main method的参数 |
| l | 输出完全的包名,应用主类名,jar的完全路径名 |
| v | 输出jvm参数 |

jstat

监视虚拟机运行状态信息,使用方式:

jstat -

| 选项 | 作用 |
| -------- | -----: |
| gc | 输出每个堆区域的当前可用空间以及已用空间,GC执行的总次数,GC操作累计所花费的时间。|
| gccapactiy | 输出每个堆区域的最小空间限制(ms)/最大空间限制(mx),当前大小,每个区域之上执行GC的次数。(不输出当前已用空间以及GC执行时间)。|
| gccause | 输出-gcutil提供的信息以及最后一次执行GC的发生原因和当前所执行的GC的发生原因。 |
| gcnew | 输出新生代空间的GC性能数据。|
| gcnewcapacity | 输出新生代空间的大小的统计数据。|
| gcold | 输出老年代空间的GC性能数据。|
| gcoldcapacity | 输出老年代空间的大小的统计数据。|
| gcpermcapacity | 输出持久带空间的大小的统计数据。|
| gcutil | 输出每个堆区域使用占比,以及GC执行的总次数和GC操作所花费的事件。|
比如:

jstat -gc 28389 1s

每隔1秒输出一次JVM运行信息:

S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
52416.0 52416.0 4744.9  0.0   419456.0 28180.6  2621440.0   439372.6  131072.0 33564.8 160472 1760.603  61      2.731 1763.334

| 列 | 说明 | jstat参数 |
| -------- | -----: | -----: |
| S0C | Survivor0空间的大小。单位KB。| -gc -gccapacity -gcnew -gcnewcapacity |
| S1C | Survivor1空间的大小。单位KB。| -gc -gccapacity -gcnew -gcnewcapacity |
| S0U | Survivor0已用空间的大小。单位KB。| -gc -gcnew |
| S1U | Survivor1已用空间的大小。单位KB。| -gc -gcnew |
| EC | Eden空间的大小。单位KB。| -gc -gccapacity -gcnew -gcnewcapacity |
| EU | Eden已用空间的大小。单位KB。| -gc-gcnew |
| OC | 老年代空间的大小。单位KB。| -gc -gccapacity -gcold -gcoldcapacity |
| OU | 老年代已用空间的大小。单位KB。| -gc -gcold |
| PC | 持久代空间的大小。单位KB。| -gc -gccapacity -gcold -gcoldcapacity -gcpermcapacity |
| PU | 持久代已用空间的大小。单位KB。| -gc -gcold |
| YGC | 新生代空间GC时间发生的次数。| -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause |
| YGCT | 新生代GC处理花费的时间。| -gc-gcnew-gcutil-gccause |
| FGC | full GC发生的次数。| -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause |
| FGCT | full GC操作花费的时间。| -gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause |
| GCT | GC操作花费的总时间。| -gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause |
| NGCMN | 新生代最小空间容量,单位KB。| -gccapacity -gcnewcapacity |
| NGCMX | 新生代最大空间容量,单位KB。| -gccapacity -gcnewcapacity |
| NGC | 新生代当前空间容量,单位KB。| -gccapacity -gcnewcapacity |
| OGCMN | 老年代最小空间容量,单位KB。 | -gccapacity-gcoldcapacity |
| OGCMX | 老年代最大空间容量,单位KB。| -gccapacity-gcoldcapacity |
| OGC | 老年代当前空间容量制,单位KB。| -gccapacity -gcoldcapacity |
| PGCMN | 持久代最小空间容量,单位KB。| -gccapacity -gcpermcapacity |
| PGCMX | 持久代最大空间容量,单位KB。| -gccapacity -gcpermcapacity |
| PGC | 持久代当前空间容量,单位KB。| -gccapacity -gcpermcapacity |
| PC | 持久代当前空间大小,单位KB。| -gccapacity-gcpermcapacity |
| PU | 持久代当前已用空间大小,单位KB。| -gc -gcold |
| LGCC | 最后一次GC发生的原因。| -gccause |
| GCC | 当前GC发生的原因。| -gccause |
| TT | 老年化阈值。被移动到老年代之前,在新生代空存活的次数。| -gcnew |
| MTT | 最大老年化阈值。被移动到老年代之前,在新生代空存活的次数。| -gcnew |
| DSS | 幸存者区所需空间大小,单位KB。| -gcnew |

jmap

生成堆存储快照,使用方式:

jmap [ -option ] 

| 选项 | 作用 |
| -------- | -----: |
| dump | 生成堆存储快照,格式为:-dump:[live, ]format=b, file=,live说明是否只dump出存活的对象。 |
| heap | 显示java堆详细信息,如使用那种回收器、参数配置、分代状况等。|
| histo | 显示堆中对象统计信息,包括类、实例数量、合计容量。|

jstack

生成虚拟机当前时刻的线程快照,帮助定位线程出现长时间停顿的原因,用法:

jstack 

Monitor

Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者Class的锁。每一个对象都有,也仅有一个 monitor。下面这个图,描述了线程和 Monitor之间关系,以及线程的状态转换图:

进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁,但并未得到。
拥有者(The Owner):表示线程成功竞争到对象锁。
等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。

线程状态

NEW,未启动的。不会出现在Dump中。

RUNNABLE,在虚拟机内执行的。

BLOCKED,等待获得监视器锁。

WATING,无限期等待另一个线程执行特定操作。

TIMED_WATING,有时限的等待另一个线程的特定操作。

TERMINATED,已退出的。

举个例子:

package com.jiuyan.mountain.test;

import java.util.concurrent.TimeUnit;

/**
 * Hello world!
 * 
 */
public class App {

     public static void main(String[] args) throws InterruptedException {
         MyTask task = new MyTask();
         Thread t1 = new Thread(task);
         t1.setName("t1");
         Thread t2 = new Thread(task);
             t2.setName("t2");
             t1.start();
             t2.start();
    }

}

class MyTask implements Runnable {

     private Integer mutex;
      
     public MyTask() {
         mutex = 1;
     }
      
     @Override
     public void run() {
         synchronized (mutex) {
             while(true) {
                 System.out.println(Thread.currentThread().getName());
                 try {
                     TimeUnit.SECONDS.sleep(5);
                 } catch (InterruptedException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 }
              }
          }
     }
    
}

线程状态:

"t2" prio=10 tid=0x00007f7b2013a800 nid=0x67fb waiting for monitor entry [0x00007f7b17087000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:35)
  - waiting to lock <0x00000007d6b6ddb8> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007f7b20139000 nid=0x67fa waiting on condition [0x00007f7b17188000]
 java.lang.Thread.State: TIMED_WAITING (sleeping)
  at java.lang.Thread.sleep(Native Method)

t1没有抢到锁,所以显示BLOCKED。t2抢到了锁,但是处于睡眠中,所以显示TIMED_WAITING,有限等待某个条件来唤醒。
把睡眠的代码去掉,线程状态变成了:

"t2" prio=10 tid=0x00007fa0a8102800 nid=0x6a15 waiting for monitor entry [0x00007fa09e37a000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:35)
  - waiting to lock <0x0000000784206650> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

 "t1" prio=10 tid=0x00007fa0a8101000 nid=0x6a14 runnable [0x00007fa09e47b000]
 java.lang.Thread.State: RUNNABLE
  at java.io.FileOutputStream.writeBytes(Native Method)
  

t1显示RUNNABLE,说明正在运行,这里需要额外说明一下,如果这个线程正在查询数据库,但是数据库发生死锁,虽然线程显示在运行,实际上并没有工作,对于IO型的线程别只用线程状态来判断工作是否正常。
把MyTask的代码小改一下,线程拿到锁之后执行wait,释放锁,进入等待区。

public void run() {
     synchronized (mutex) {
         if(mutex == 1) {
             try {
                 mutex.wait();
             } catch (InterruptedException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
      }
  }

线程状态如下:

"t2" prio=10 tid=0x00007fc5a8112800 nid=0x5a58 in Object.wait() [0x00007fc59b58c000]
 java.lang.Thread.State: WAITING (on object monitor)
  at java.lang.Object.wait(Native Method)

"t1" prio=10 tid=0x00007fc5a8111000 nid=0x5a57 in Object.wait() [0x00007fc59b68d000]
 java.lang.Thread.State: WAITING (on object monitor)
  at java.lang.Object.wait(Native Method)

两个线程都显示WAITING,这次是无限期的,需要重新获得锁,所以后面跟了on object monitor
再来个死锁的例子:

package com.jiuyan.mountain.test;

import java.util.concurrent.TimeUnit;

/**
 * Hello world!
 * 
 */
public class App {

  public static void main(String[] args) throws InterruptedException {
      MyTask task1 = new MyTask(true);
      MyTask task2 = new MyTask(false);
      Thread t1 = new Thread(task1);
      t1.setName("t1");
      Thread t2 = new Thread(task2);
      t2.setName("t2");
      t1.start();
      t2.start();
  }

}

class MyTask implements Runnable {

  private boolean flag;
  
  public MyTask(boolean flag) {
      this.flag = flag;
  }
  
  @Override
  public void run() {
      if(flag) {
          synchronized (Mutex.mutex1) {
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
              synchronized (Mutex.mutex2) {
                  System.out.println("ok");
              }
          }
      } else {
          synchronized (Mutex.mutex2) {
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
              synchronized (Mutex.mutex1) {
                  System.out.println("ok");
              }
          }
      }
  }
  
}

class Mutex {
  public static Integer mutex1 = 1;
  public static Integer mutex2 = 2;
}

线程状态:

"t2" prio=10 tid=0x00007f5f9c122800 nid=0x3874 waiting for monitor entry [0x00007f5f67efd000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:55)
  - waiting to lock <0x00000007d6c45bd8> (a java.lang.Integer)
  - locked <0x00000007d6c45be8> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007f5f9c121000 nid=0x3873 waiting for monitor entry [0x00007f5f67ffe000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:43)
  - waiting to lock <0x00000007d6c45be8> (a java.lang.Integer)
  - locked <0x00000007d6c45bd8> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x00007f5f780062c8 (object 0x00000007d6c45bd8, a java.lang.Integer),
which is held by "t1"
"t1":
waiting to lock monitor 0x00007f5f78004ed8 (object 0x00000007d6c45be8, a java.lang.Integer),
which is held by "t2"

这个有点像哲学家就餐问题,每个线程都持有对方需要的锁,那就运行不下去了。

调优策略

两个基本原则:

将转移到老年代的对象数量降到最少。

减少Full GC的执行时间。目标是Minor GC时间在100ms以内,Full GC时间在1s以内。

主要调优参数:

设定堆内存大小,这是最基本的。

-Xms:启动JVM时的堆内存空间。

-Xmx:堆内存最大限制。

设定新生代大小。
新生代不宜太小,否则会有大量对象涌入老年代。

-XX:NewRatio:新生代和老年代的占比。

-XX:NewSize:新生代空间。

-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比。

-XX:MaxTenuringThreshold:对象进入老年代的年龄阈值。

设定垃圾回收器
年轻代:-XX:+UseParNewGC。

老年代:-XX:+UseConcMarkSweepGC。
CMS可以将STW时间降到最低,但是不对内存进行压缩,有可能出现“并行模式失败”。比如老年代空间还有300MB空间,但是一些10MB的对象无法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要比Parallel GC长很多。
G1采用”标记-整理“算法,解决了内存碎片问题,建立了可预测的停顿时间类型,能让使用者指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65568.html

相关文章

  • [译]GC专家系列5-Java应用性能优化的原则

    摘要:在本文中我将会介绍应用性能优化的一般原则。性能优化的流程图摘取自和合著的性能,描述了应用性能优化的处理流程。例如,对每台服务器,你面临着为单个分配堆内存和运行个并为每个分配堆内存的选择。不过位能使用堆内存最大理论值只有。 原文链接:http://www.cubrid.org/blog/dev-platform/the-principles-of-java-application-per...

    lufficc 评论0 收藏0
  • Hack on HHVM —— Facebook是如何优化PHP的

    摘要:周四正式发布了编程语言,将静态类型以及一些现代的语言特性引入了。这是对优化之路上的新里程碑。但是语言层面的优化限制太多,对而言还是不够用。其次是优化运行的步骤。在这方面进行调整,可以提升运行的性能。值得注意的是,给的影响很大。 Facebook周四正式发布了Hack编程语言,将静态类型以及一些现代的语言特性引入了PHP。这是Facebook对PHP优化之路上的新里程碑。 showIm...

    lmxdawn 评论0 收藏0
  • Java性能优化JVM内存模型

    摘要:内存模型首先介绍下程序具体执行的过程源代码文件后缀会被编译器编译为字节码文件后缀由中的类加载器加载各个类的字节码文件,加载完毕之后,交由执行引擎执行在整个程序执行过程中,会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被 [TOC] JVM内存模型 首先介绍下Java程序具体执行的过程: Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(....

    SQC 评论0 收藏0
  • 学习JVM必看书籍

    学习JVM的相关资料 《深入理解Java虚拟机——JVM高级特性与最佳实践(第2版)》 showImg(https://segmentfault.com/img/bVbsqF5?w=200&h=200); 基于最新JDK1.7,围绕内存管理、执行子系统、程序编译与优化、高效并发等核心主题对JVM进行全面而深入的分析,深刻揭示JVM的工作原理。以实践为导向,通过大量与实际生产环境相结合的案例展示了解...

    shaonbean 评论0 收藏0
  • 性能Java代码的最佳实践

    摘要:高性能代码的最佳实践前言在这篇文章中,我们将讨论几个有助于提升应用程序性能的方法。要获得有关应用程序需求的最好最可靠的方法是对应用程序执行实际的负载测试,并在运行时跟踪性能指标。 showImg(https://segmentfault.com/img/bVbtgk4?w=256&h=254); 高性能Java代码的最佳实践前言 在这篇文章中,我们将讨论几个有助于提升Java应用程序性...

    stackfing 评论0 收藏0
  • 面试官问我JVM调优,我忍不住了!

    面试官:今天要不来聊聊JVM调优相关的吧?面试官:你曾经在生产环境下有过调优JVM的经历吗?候选者:没有面试官:...候选者:嗯...是这样的,我们一般优化系统的思路是这样的候选者:1. 一般来说关系型数据库是先到瓶颈,首先排查是否为数据库的问题候选者:(这个过程中就需要评估自己建的索引是否合理、是否需要引入分布式缓存、是否需要分库分表等等)候选者:2. 然后,我们会考虑是否需要扩容(横向和纵向都...

    不知名网友 评论0 收藏0

发表评论

0条评论

WelliJhon

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<