Xinfeng Liu

http://developers.sun.com.cn/blog/lxf/date/20080912 星期五 九月 12, 2008

使用Sun Studio 和Solaris工具解决Java的性能问题

Sun Studio的performance analyzer是能同时对JVM和Java代码的profiling工具,而且比 起那些基于Java的Profiling工具要好用的多。使用Netbeans Profiler时候最好把项目工程放进Netbeans IDE里才好用,否则不方便对被profiling的应用做细粒度的profiling控制,导致大型应用被profiling后运行非常慢。而Sun Studio的performance analyzer对应用性能的影响要小得多,而且使用起来很简便。只要 collect -j on java <arguments> 就可以了。运行完后会生成一个结果数据目录比如test.1.er,然后用analyzer test.1.er 进行图形化的显示和分析,或使用er_print test.1.er进行命令行的显示。

Sun Studio performance analyzer对java profiling结果的分析,可以按三种模式显示: user, expert, machine。因此可以同时分析用户的Java代码和JVM的C++代码。如果你要分析JNI的代码,这个工具更是离不开了。下面是一个例子:

er_print test.1.er

(er_print) limit 20
(er_print) func
Functions sorted by metric: Exclusive User CPU Time

Excl.     Incl.      Name
User CPU  User CPU
   sec.      sec.
763.864   763.864    <Total>
218.053   218.053    <JVM-System>
 44.511    44.581    <Unknown>
 33.123    35.195    oracle.jdbc.driver.NumberCommonAccessor.getBigDecimal(int)
 21.145    52.287    oracle.jdbc.driver.ClobAccessor.getString(int)
 15.401    30.982    java.util.HashMap.get(java.lang.Object)
 14.630    20.364    sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(java.nio.CharBuffer, java.nio.ByteBuffer)
 13.209    13.259    oracle.jdbc.driver.OracleStatement.prepareAccessors()
 13.159    81.157    org.apache.jsp.home_jsp._jspService(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
 12.429    15.271    oracle.jdbc.driver.T4C8TTIClob.read(byte[], long, long, boolean, char[])
 12.369    16.121    java.lang.String.regionMatches(boolean, int, java.lang.String, int, int)
 11.258    11.258    java.lang.String.equals(java.lang.Object)
 10.667    10.667    java.lang.Throwable.<init>()
  9.517     9.517    oracle.jdbc.driver.T4CConnection.createClobDBAccess()
  8.856    30.671    oracle.jdbc.driver.OracleStatement.getColumnIndex(java.lang.String)
  6.364     6.364    java.lang.String.indexOf(int, int)
  5.844    21.935    java.lang.String.equalsIgnoreCase(java.lang.String)
  5.674     5.674    java.nio.CharBuffer.arrayOffset()
  4.733     4.733    java.util.Arrays.copyOfRange(char[], int, int)
  4.703     4.703    oracle.net.ns.DataPacket.getDataFromBuffer(byte[], int, int)

(er_print) viewmode expert
(er_print) func
Functions sorted by metric: Exclusive User CPU Time

Excl.     Incl.      Name
User CPU  User CPU
   sec.      sec.
763.864   763.864    <Total>
 33.123    35.195    oracle.jdbc.driver.NumberCommonAccessor.getBigDecimal(int)
 21.145    52.287    oracle.jdbc.driver.ClobAccessor.getString(int)
 19.534    19.534    lwp_yield
 17.232    17.232    OopStarTaskQueueSet::peek()
 15.401    30.982    java.util.HashMap.get(java.lang.Object)
 14.630    20.364    sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(java.nio.CharBuffer, java.nio.ByteBuffer)
 13.980    21.855    PhaseChaitin::build_ifg_physical(ResourceArea*)
 13.209    13.259    oracle.jdbc.driver.OracleStatement.prepareAccessors()
 13.159    81.137    org.apache.jsp.home_jsp._jspService(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
 13.129    13.289    IndexSetIterator::advance_and_next()
 12.429    15.271    oracle.jdbc.driver.T4C8TTIClob.read(byte[], long, long, boolean, char[])
 12.369    16.121    java.lang.String.regionMatches(boolean, int, java.lang.String, int, int)
 11.258    11.258    java.lang.String.equals(java.lang.Object)
 10.667    10.667    java.lang.Throwable.<init>()
  9.517     9.517    oracle.jdbc.driver.T4CConnection.createClobDBAccess()
  8.856    30.671    oracle.jdbc.driver.OracleStatement.getColumnIndex(java.lang.String)
  7.475     7.475    arrayof_oop_disjoint_arraycopy
  6.585     8.736    PhaseChaitin::Split(unsigned)
  6.364     6.364    java.lang.String.indexOf(int, int)

(er_print) viewmode machine
(er_print) func
Functions sorted by metric: Exclusive User CPU Time

Excl.     Incl.      Name
User CPU  User CPU
   sec.      sec.
763.864   763.864    <Total>
111.688   546.232    Interpreter
 57.850    60.702    typeArrayKlass::allocate(int,Thread*)
 20.324    20.394    sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(java.nio.CharBuffer, java.nio.ByteBuffer)
 19.534    19.534    lwp_yield
 17.232    17.232    OopStarTaskQueueSet::peek()
 16.291    16.291    java.lang.String.regionMatches(boolean, int, java.lang.String, int, int)
 14.850    31.062    oracle.jdbc.driver.OracleStatement.getColumnIndex(java.lang.String)
 13.980    21.855    PhaseChaitin::build_ifg_physical(ResourceArea*)
 13.129    13.289    IndexSetIterator::advance_and_next()
 11.638    11.638    java.lang.String.equals(java.lang.Object)
 11.588    15.000    java.lang.Object.hashCode()
  7.475     7.475    arrayof_oop_disjoint_arraycopy
  6.585     8.736    PhaseChaitin::Split(unsigned)
  6.545     6.545    java.lang.String.indexOf(int, int)
  5.754     5.754    jshort_disjoint_arraycopy
  5.484     5.484    flush_callers_register_windows
  5.324    16.742    java_lang_Throwable::fill_in_stack_trace(Handle,Thread*)
  5.284     5.284    memcpy
  5.014    18.333    java.util.Collections$SynchronizedMap.get(java.lang.Object)

有时,单单通过Java本身的工具(如jstat, jmap, jinfo, jconsole, java thread dump等)不足以分析出问题,还是需要依靠操作系统的工具来定位。比如pstack, prstat -mL -p <pid>,pmap -xs, DTrace等。假如通过prstat发现Java应用总是不能充分利用CPU,可以通过一个简单的DTrace脚本来找出原因:

offcpu.d
--------
#!/usr/sbin/dtrace -s
off-cpu
/pid == $target/
{
@[jstack(20,200)]=count();
}
tick-2sec
{
trunc(@,10);
printa(@);
clear(@);
}

# ./offcpu.d -p <java process id>

无论你愿不愿意承认,总之Java在Solaris上运行的最好,道理就不用说了。 


使用SUN Studio编译C++程序几个注意问题

不喜欢C++,不规范的C++代码的移植简直就是一场恶梦,即使是在同一种编译器的不同版本间移植也会令人痛苦不堪。Mozilla甚至给出了一个编码规范,以减少跨平台/跨编译器移植的麻烦。不符合该规范的代码一律被认为BUG,即使代码的功能正确。

不同编译器编出来的C++代码通常是不能混用的。因为C++有个符号mangling的问题,不同的编译器有不同的mangling方法,导致符号不通用。Solaris上的Java虚拟机是用Sun compiler编译的,因此如果你有JNI代码是C++写的,你应该用Sun compiler编译JNI代码。(当然Sun compiler是免费的)。编译各种操作系统平台的JNI代码的建议编译选项见Kelly O'Hair的blog。

Sun compiler编译可执行程序时会自动链接 -lCstd -lCrun -lm -lc ,但编共享库时却不会。所以编共享库时应该显式地加上这些链接选项。

Sun compiler 也同时提供标准C++库的stlport实现,编译时可以加上-library=stlport4以使用stlport的实现而不是自带的libCstd.so.1。但这二者在同一程序中不能混用。

如果用DTrace来调试C++程序,函数名要使用mangle过的符号,因为DTrace不认识C++(mdb也是这样)。想得到mangle过的符号可以通过nm -C <binary name> 得到。如果要跟踪函数的参数,第一个参数通常应该是arg1而不是arg0,因为C++中类成员函数映射成C代码后第一个参数一般都是this指针。比如下面的DTrace脚本跟踪某个函数的调用过程:

#!/usr/sbin/dtrace -qFs
pid$target::__xxxxxx_:entry
{
self->traced=1;
printf("thread #%d is doing %s \n", tid, copyinstr(arg1)); /* suppose arg1 type is char* */
}
pid$target::__xxxxxx_:return
{
self->traced=0;
}
pid$target:::entry
/self->traced/
{
printf("thread #%d is calling %s .\n", tid, probefunc);
}

pid$target:::return
/self->traced/
{
printf("thread #%d returns from %s .\n", tid, probefunc);
}