WEB开发网
开发学院软件开发Shell JRockit 提高Linux Java性能的分析 阅读

JRockit 提高Linux Java性能的分析

 2009-06-30 04:03:00 来源:WEB开发网   
核心提示:通过分析Java线程堆解决Java应用程序中存在问题的技术,我们可以使用线程堆来分析诸如应用程序挂起,JRockit 提高Linux Java性能的分析,响应时间长以及程序崩溃等情况,在详细介绍分析线程堆的技术之前, 如果在堆中没有“当前线程”的信息,那么我们就需要分析核心堆,我们先来简要地看看线程堆本身, Java线

  通过分析Java线程堆解决Java应用程序中存在问题的技术。我们可以使用线程堆来分析诸如应用程序挂起,响应时间长以及程序崩溃等情况。在详细介绍分析线程堆的技术之前,我们先来简要地看看线程堆本身。
  
  Java线程堆是一个运行中的Java应用程序的所有线程的一个快照。它会显示一些像当前的堆栈跟踪、状态以及线程名称之类的信息。线程列表中包括由JVM本身创建的线程(负责垃圾收集、信号处理等管理工作)和由应用程序创建的线程。
  
  通过给JVM发送一个SIGQUIT信号,您可以得到一个线程堆。在Unix操作系统(Solaris/Linux/HP-Unix等)中,通过kill-3命令可以得到线程堆。输出出线程堆之后,应用程序继续正常运行。当您给JVM发送SIGQUIT信号时,JVM的信号处理器会通过输出线程堆来响应这一信号。当程序运行的时候,您可以在任何点得到线程堆。
  
  线程堆的一个例子
  清单1显示的就是一个使用Sun JVM 1.4.1的单线程应用程序中的线程堆的例子。main线程是主应用程序线程。所有其他的线程都是由JVM创建的,负责完成一些管理工作。当分析应用程序级的问题时,我们通常只关心应用程序线程。下面,我们来分析清单1中main线程的堆栈跟踪。
  
  "main" prio=5 tid=0x002358B8 nid=0x7f8 runnable [6f000..6fc40]
  at test.method1(test.java:10)
  at test.main(test.java:5)
  
  从这个代码片断中您可以看到,一个线程堆栈跟踪有一个名称、线程优先级(prio=5)、状态(可运行的)、源代码行号,以及方法调用。从这个堆栈跟踪中可以得到如下结论:main线程执行test类的method1方法中的一些代码。而对method1方法的调用是由同一个类的main方法完成的。您也可以看到那些方法中确切的源代码行号。
  
  在从一些更复杂的情况去分析线程堆之前,我们先来讨论那些可以在线程堆中看到的线程的不同状态以及它们的意义。
  
  · 可运行的:当获得CPU的使用权时就可以运行或准备好运行的状态。JRockit线程堆把这种状态称为ACTIVE。
  
  · 在监视中等待:指在一个对象上休眠或等待上述时间,或者等待另一个线程的通知。这种情况会出现在对Thread对象调用任何sleep() 方法或者对Object调用wait() 方法时。
  
  例如,在BEA WebLogic Server中,空闲的执行线程就处于这种状态,它们会等待一个socket阅读器线程通知它们去做一些新的工作。堆栈跟踪如下所示:
  
  "ExecuteThread: '2' for queue: 'weblogic.admin.RMI'"daemon prio=5 tid=0x1752F040 nid=0x180c in Object.wait()[1887f000..1887fd8c]at java.lang.Object.wait(Native Method)waiting on (a weblogic.kernel.ExecuteThread)at java.lang.Object.wait(Object.java:426)
  
  JVM的其他版本把这种状态称为CW。JRockit将这种状态称为WAITING。
  
  · 等待监视实体:等待锁住一个对象(其他线程目前正控制着锁)。如果两个或者多个线程都想在同一时刻执行一个对象的某些同步代码块或方法,就会出现这种情况。请注意,锁总是加在一个对象上而不是加在独立的方法上。也就是说,如果一个线程要执行一个对象的同步方法,它必须先给那个对象加锁。
  
  下面显示的就是这种情况下的一个线程的堆栈跟踪的例子:
  
  "ExecuteThread: '24' for queue: 'DisplayExecuteQueue'" daemon prio=5tid=0x5541b0 nid=0x3b waiting for monitor entry [49b7f000..49b7fc24]at weblogic.cluster.replication.ReplicationManager.createSecondary(ReplicationManager.java:908)- waiting to lock (a java.lang.Object)at weblogic.cluster.replication.ReplicationManager.updateSecondary(ReplicationManager.java:715)
  在上述代码片断中,您们可以看到这个线程已经锁住了一个对象(6c408700),并且还在等待锁住另一个对象(6c4b9130)。
  
  JVM的其他版本可能不会在堆栈跟踪中给出对象ID的加锁信息。这种情况下,我们可以从线程的状态中推测出线程正在等待给对象加锁。同样的状态也许会被称为MW。JRockit将这种状态称为LOCKED。
  
  现在,我们来看看各种不同情况下的一些线程堆,并通过分析它们来寻找应用程序中的问题。
  
  死锁应用程序中的线程堆
  在清单2(只显示了部分线程堆)中,您可以看到JVM已经发现了死锁并且在线程堆中给出了该信息。消息中很清楚地表明了“ExecuteThread: '47' for queue: 'default'”正在等待给被"ExecuteThread: '57' for queue: 'default'"锁住的对象加锁。(这个线程堆来自于WebLogic Server。这些线程都是WebLogic Server的工作线程,它们负责处理客户机请求。)同时,执行线程47也锁住了执行线程57正在等待加锁的一个对象。这就是死锁。在这种状态下,这两个线程都无法继续运行,它们都在等待另一个线程释放锁。
  
  在清单2中,执行线程1正在等待给ConnectionScavanger对象加锁,但是这个对象目前已经被线程47锁住了,正如前面提到过的,线程47又被线程57死锁了。因此,执行线程1也无法继续运行下去。这最终将导致程序的挂起,因为服务器中的其他执行线程随后也会试图获得那个被执行线程47和57锁住的对象的锁。
  
  有些JVM不会给出死锁信息。它只会给出线程堆。这时候,我们可以通过查看线程的状态和堆栈跟踪得出同样的结论。
  
  上述这个情况中的问题源于WebLogic中的一个bug,后来已经修复了。
  
  其他挂起的情况
  在一些情况下,您可能会看到大多数线程都是“可运行”的,但是服务器仍然会挂起,无法响应客户机的请求。如果某些线程在做IO(输入输出)操作,它们可能会在执行read()方法的时候被阻塞住,但是状态还是“可运行的”(数据库或其他网络响应的情况可能很糟)。如果出现这种情况,您需要检查数据库和网络是否正常。
  
  一个“可运行的”线程被阻塞住还可能是由下列原因引起的:
  
  · 程序代码中的无限循环。这会导致严重的CPU占用。
  
  · JVM的垃圾收集可能会运行很长时间,占据大量的CPU时间。
  
  · JVM进程可能会用完文件描述符。
  
  如果应用程序的代码调用了wait()方法或sleep()方法,线程在休眠时间内不会继续运行——这时线程的状态就是“在监视中等待”。出现这种情况,应该检查程序代码。
  
  对一个对象的争用也会引起系统性能的下降,最终会导致出现类似于挂起的状态。这种情况下,大多数线程的状态就是“等待监视实体”。所以应该仔细检查程序代码以减少对特定对象的争用。线程可能在等待来自另一个服务器的响应,而另一个服务器可能由于其他原因也处于挂起状态,它就无法给这个服务器发送响应信息。
  
  在上述所有情况中,您都需要每隔几秒取一个线程堆(总共取多个线程堆),从而能够比较所有线程堆中的某一个线程的状态,并且您可以决定线程可以继续运行还是继续等待。
  
  JVM崩溃的情况
  JVM的崩溃可能会由下列原因引起:
  
  1.JVM库中的bug。
  
  2.应用程序错误地使用了JNI API。
  
  3.应用程序(原生数据库驱动等)所用的原生模块中存在bug。
  
  通常,当JVM崩溃的时候,它会给出引起崩溃的Java线程的堆栈跟踪。一些JVM会在退出之前给出一个完整的线程堆(所有线程的堆栈跟踪)。我们关心这些线程堆的目的主要在于寻找崩溃时正在运行的是哪个线程。这个线程称为“当前线程”。如果JVM给出一个完整的线程堆,它会标出“当前线程”。因此,一般来说找出引起崩溃的线程还是比较容易的。
  
  注意,崩溃的线程堆通常输出到stdout或stderr。JVM可能也会生成一个二进制格式的核心文件。
  
  崩溃线程堆的例子
  如果您检查一下清单3中的崩溃线程堆,您可以看到引起崩溃的线程的堆栈跟踪如下所示:
  
  Current Java thread:
  at com.aaa.bbb.qqq.Direct.setString(Native Method)
  at com.aaa.bbb.qqq.PreparedStatement.setString(PreparedStatement.java:51)
  - locked (a com.aaa.bbb.qqq.PreparedStatement)
  最后一个执行的方法(setString)是类“com.aaa.bbb.qqq.Direct”中的一个“原生方法”。因此,我们可以推测出崩溃正是由setString方法的实现中的原生代码引起的。崩溃的原因也可以归结为实现JNI API的JVM库代码有问题。
  
  您还可以看到JVM在堆中给出了下面的信息:
  
  Unexpected Signal : 11 occurred at PC=0x403AC9D1
  Function=(null)+0x403AC9D1
  Library=/opt/sunjdk/1.4.1_01-b01/jre/lib/i386/client/libjvm.so
  # HotSpot Virtual Machine Error : 11
  # Error ID : 4F530E43505002E6
  # Please report this error at
  # http://java.sun.com/cgi-bin/bugreport.cgi
  这意味着引起崩溃(PC=0x403AC9D1)的代码是JVM库的一部分:"/opt/sunjdk/1.4.1_01-b01/jre/lib/i386/client/libjvm.so"。但这并不表示JVM一定有bug。尽管引起崩溃的指示是在JVM代码中,但崩溃也可能是由于应用程序代码(这里是指原生方法setString的实现)的JNI调用引起的,应用程序代码可能传递了一些错误的变量,从而导致JVM模块的崩溃。
  
  如果在堆中没有“当前线程”的信息,那么我们就需要分析核心堆。这一内容不在本文的讨论范围之中。

Tags:JRockit 提高 Linux

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接