JConsole: Java监视与管理控制台
代码清单1:
import java.util.*; public class JConsoleDemo { static class OOMObject { public byte[] placeholder = new byte[64 * 1024]; } public static void fillHeap(int num) throws InterruptedException { Listlist = new ArrayList (); for (int i = 0; i < num; i++) { Thread.sleep(50); list.add(new OOMObject()); } System.gc(); } public static void main(String[] args) throws Exception { fillHeap(1000); //System.gc(); Thread.sleep(10000); } }
内存监控:
编译运行JConsoleDemo类, 运行时设置的虚拟机参数为 -Xms100m -Xmx100m -XX:+UseSerialGC , 在%JAVA_HOME%\bin目录下, 启动jconsole.exe , 将自动搜索出本机运行的所有虚拟机进程, 这里我们选择JConsoleDemo对应的进程2464。
启动后主界面如下:
在"内存"页签, 查看堆内存Eden区的运行趋势如下:
从图中详细信息可以看出, Eden区的内存大小为27.328KB, 所以折线图中显示每次到27Mb左右时系统就会进行一次GC。当1000次循环结束后, 执行System.gc(), 柱状图中显示Eden区和Survivor区基本被清空, 但老年代的对应柱状图仍保持峰值状态, 这是因为System.gc()是在fillHeap()方法内执行, 所以list对象在System.gc()执行时仍然是存活的( 处于作用域之内、被引用)。如果将System.gc()移动到fillHeap()方法外执行, 如下柱状图所示, 则会回收包括老年代的所有内存。
代码清单2:
import java.io.*; public class JConsoleDemo2 { static class SynAddRunalbe implements Runnable { int a, b; public SynAddRunalbe(int a, int b) { this.a = a; this.b = b; } @Override public void run() { synchronized (Integer.valueOf(a)) { synchronized (Integer.valueOf(b)) { System.out.println(a + b); } } } } public static void main(String[] args) { new Thread(new SynAddRunalbe(1, 2)).start(); new Thread(new SynAddRunalbe(2, 1)).start(); } }
线程监控:
编译运行, 在"线程"页签可查看"死锁"描述。这是因为1、2两个数值在Integer类的缓存常量池[-128, 127]范围内, 这样当多次调用Integer.valueOf()方法时, 不会再每次都创建对象, 而是直接返回缓存常量池中的对象。所以上面两个线程的同步代码块中实际上只创建了两个锁对象, 且在某一时刻互相持有对方的锁, 即"死锁"现象。
VisualVM: 多合一故障处理工具
概述与插件安装
VisualVM基于NetBeans平台开发, 因此它一开始就具备了插件扩展的特性, 通过插件支持, VisualVM可以做许多事情, 例如:
- 显示虚拟机进程和进程的配置、环境信息(jps、jinfo)
- 监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)
- dump及分析堆转储快照(jmap、jhat)
- 方法级的程序运行性能分析, 找出被调用最多、运行时间最长的方法
- 离线程序快照: 收集程序的运行时配置、线程dump、内存dump等信息建立一个快照, 可以将快照发送开发者处进行bug反馈等等
在%JAVA_HOME%\bin目录下, 启动jvisualvm.exe进入主界面, 点击"工具"→"插件"→"可用插件"选项, 选择所需的插件安装。
安装好插件后, 选择一个正在运行的java程序就可以查看程序监控的主界面了
堆转储快照
两种方式生成堆dump文件:
- 在"应用程序"窗口中右键单击应用程序节点, 选择"堆 Dump"
- 在"监视"页签中选择"堆 Dump"
分析程序性能
在Profiler页签中, 可以对程序运行期间方法级的CPU和内存进行分析, 这个操作会对程序运行性能有很大影响, 所以一般不再生产环境使用。CPU分析将会统计每个方法的执行次数、执行耗时; 内存分析则会统计每个方法关联的对象数及对象所占空间。
BTrace动态日志跟踪
BTrace既可以作为visualVM的插件来使用, 也可以独立运行。它能在不停止目标程序运行的前提下, 通过热部署技术加入原本并不存在的调试代码, 以实现对程序的动态调试。
上面我们已经安装好了BTrace插件, 这里编译运行BTraceTest。
import java.io.*; public class BTraceTest { public int add(int a, int b) { return a + b; } public static void main(String[] args) throws IOException { BTraceTest test = new BTraceTest(); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); for (int i = 0; i < 10; i++) { reader.readLine(); int a = (int) Math.round(Math.random() * 1000); int b = (int) Math.round(Math.random() * 1000); System.out.println(test.add(a, b)); } } }
在"应用程序"页签中选择对应的java进程, 右键选择"Trace Application", 进入BTrace面板:
在BTrace页签内输入调试代码:
/* BTrace Script Template */ import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; @BTrace public class TracingScript { @OnMethod( clazz="BTraceTest", method="add", location=@Location(Kind.RETURN) ) public static void func(@Self BTraceTest instance,int a,int b,@Return int result) { println("调用堆栈:"); jstack(); println(strcat("方法参数A:",str(a))); println(strcat("方法参数B:",str(b))); println(strcat("方法结果:",str(result))); } }
点击BTrace面板的"Start"按钮, 对程序进行动态调试:
BTrace不仅可以用来打印调用堆栈、参数、返回值, 还可以进行性能监控、定位连接泄漏和内存泄漏、解决多线程竞争问题, 这里我们只是做一个入门的了解, 具体的拓展应用以后会专门用一篇文章来讲解。
参考资料
《深入理解Java虚拟机》