JDK中的SA(ServiceAbility)工具介绍

最近在网上看到rednaxelafx关于HSDB的介绍,感觉打开了解jvm细节的一扇大门,之前只是纯粹的了解理论, 而现在可以通过该工具去深入查看内部的细节;

概述

SA包含在$JAVA_HOME/lib/sa-jdi.jar中,包括三个工具:
1.CLHSDB:

命令行版本

java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB

2.HSDB

图形界面版本

java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB

3.JSDB:

Javascript引擎版本

java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.tools.soql.JSDB

用途

SA的一个限制是它只实现了调试snapshot的功能,因此要么要让被调试的目标进程完全暂停,要么就调试core dump;另外注意,SA调试通常要求拥有root权限,否则会报错

调试本地进程

  1. 获取要调试的进程ID:

    ps -ef|grep java
    jps -mvl
    
  2. attch进程

    java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB
    attach PID
    

    sudo java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
    通过菜单"File"->"Attach to HotSpot process",录入PID
    

调试远程进程

  1. 远程机器上启动rmiregistry服务;

    rmiregistry -J-d64 -J-Xbootclasspath/p:${JAVA_HOME}/lib/sa-jdi.jar
                    或
    rmiregistry -J-Xbootclasspath/p:${JAVA_HOME}/lib/sa-jdi.jar
    
  2. 启动debug server:

客户端通过机器名称连接到debug server进行调试,默认情况下,当只启动一个debug server时,机器名称为ip地址;

 ```bash
 java -d64 -classpath ${JAVA_HOME}/lib/sa-jdi.jar sun.jvm.hotspot.jdi.SADebugServer <pid>
                或
 java -d64 -classpath ${JAVA_HOME}/lib/sa-jdi.jar sun.jvm.hotspot.jdi.SADebugServer <java executable> <core file>
 ```

调试core dump

java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB $JAVA_HOME/bin/java core-file-name

注意:为了使Java程序能够产生core dump,必须设置ulimit -c unlimited,另外在Linux或Solaris下可以通过kill -6 <pid>手工产生core dump。

高级用法

关于使用SA了解JAVA内存布局和解决问题,可以参考如下的文章:

  1. 借HSDB来探索HotSpot VM的运行时数据
  2. 借助HotSpot SA来一窥PermGen上的对象
  3. 怎么查看运行时常量池的数据?
  4. 代码样例

Javascript支持

SA提供了jseval指令,可以执行js代码,其中提供部分内置的js函数和全局变量,具体可以参见sun.jvm.hotspot.utilities.soql.JSJavaScriptEngine的216~232行:

高级API

println("Function/Variable        Description");
        println("=================        ===========");
        println("address(jobject)         returns the address of the Java object");
        println("classof(jobject)         returns the class object of the Java object");
        println("dumpClass(jclass,[dir])  writes .class for the given Java Class");
        println("dumpHeap([file])         writes heap in hprof binary format");
        println("help()                   prints this help message");
        println("identityHash(jobject)    returns the hashCode of the Java object");
        println("mirror(jobject)          returns a local mirror of the Java object");
        println("load([file1, file2,...]) loads JavaScript file(s). With no files, reads <stdin>");
        println("object(string)           converts a string address into Java object");
        println("owner(jobject)           returns the owner thread of this monitor or null");
        println("sizeof(jobject)          returns the size of Java object in bytes");
        println("staticof(jclass, field)  returns a static field of the given Java class");
        println("read([prompt])           reads a single line from standard input");
        println("quit()                   quits the interactive load call");
        println("jvm                      the target jvm that is being debugged");

底层API

具体的实现代码参见sun.jvm.hotspot.utilities.soql包下面的sa.js,由于代码比较长,此处仅截取部分:

var sapkg = new Object();

sapkg.hotspot = Packages.sun.jvm.hotspot;
sapkg.asm = sapkg.hotspot.asm;
sapkg.bugspot = sapkg.hotspot.bugspot;
sapkg.c1 = sapkg.hotspot.c1;
sapkg.code = sapkg.hotspot.code;
sapkg.compiler = sapkg.hotspot.compiler;

// 'debugger' is a JavaScript keyword :-(
// sapkg.debugger = sapkg.hotspot.debugger;

sapkg.interpreter = sapkg.hotspot.interpreter;
sapkg.livejvm = sapkg.hotspot.livejvm;
sapkg.jdi = sapkg.hotspot.jdi;
sapkg.memory = sapkg.hotspot.memory;
sapkg.oops = sapkg.hotspot.oops;
sapkg.runtime = sapkg.hotspot.runtime;
sapkg.tools = sapkg.hotspot.tools;
sapkg.types = sapkg.hotspot.types;
sapkg.ui = sapkg.hotspot.ui;
sapkg.utilities = sapkg.hotspot.utilities;

// SA singletons are kept in 'sa' object
var sa = new Object();
sa.vm = sapkg.runtime.VM.getVM();
sa.dbg = sa.vm.getDebugger();
sa.cdbg = sa.dbg.CDebugger;
sa.heap = sa.vm.universe.heap();
sa.systemDictionary = sa.vm.systemDictionary;
sa.sysDict = sa.systemDictionary;
sa.symbolTable = sa.vm.symbolTable;
sa.symTbl = sa.symbolTable;
sa.threads = sa.vm.threads;
sa.interpreter = sa.vm.interpreter;
sa.typedb = sa.vm.typeDataBase;
sa.codeCache = sa.vm.codeCache;
// 'objHeap' is different from 'heap'!. 
// This is SA's Oop factory and heap-walker
sa.objHeap = sa.vm.objectHeap;

// few useful global variables
var OS = sa.vm.OS;
var CPU = sa.vm.CPU;
var LP64 = sa.vm.LP64;
var isClient = sa.vm.clientCompiler;
var isServer = sa.vm.serverCompiler;
var isCore = sa.vm.isCore();
var addressSize = sa.vm.addressSize;
var oopSize = sa.vm.oopSize;

常用命令

1.查看内建函数和变量

jseval "help()"

jvm全局变量

属性名称备注
addressSize32位还是64位
buildInfojdk构建新型,包括版本号,构建日期,编译器等
cpuCPU类型,例如:x86_64
flags类似-XX:+PrintFlagsFinal效果
heap
os操作系统信息,例如:solaris、linux、bsd、wind32等
sysProps返回jvm的系统属性,和jinfo命令看到的类似
threads线程列表
typeClient、Server、Core
versionJDK版本
classpathjava.class.path
bootClasspathsun.boot.class.path
userDiruser.dir

查看线程对象属性代码:

jseval "t=jvm.threads[jvm.threads.length-1]"
jseval "for(k in t){print(k);print(',');}"

代码样例

jseval "st.stringsDo(function (s) { if (sapkg.oops.OopUtilities.stringOopToString(s).matches('^(a|b).')) {print(s + ': '); s.printValueOn(java.lang.System.out); println('')}})"
jseval "java.lang.Long.toHexString(0x0000000193899df0+sapkg.oops.ConstantPool.headerSize*4)"
java -classpath .:$SAPATH/sa-jdi.jar sun.jvm.hotspot.tools.JMap -histo `pgrep java` > jmap_output.log
jstack -m $JAVA_HOME/bin/java core.11028 

jstack有时候看不到具体的错误信息,可以通过开关
export LIBSAPROC_DEBUG=1打开

    原文作者:allanYan
    原文地址: https://www.jianshu.com/p/7c40274441a4
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞