jvm分析工具

jvisualvm

给一个heap out of memory的例子:

public class TestHeapSpace {
    public static void main(String[] args) {
        List<TestHeapSpace> list = new ArrayList<>();

        for(;;){
            list.add(new TestHeapSpace());
        }
    }

}

list中一直放入对象,终有一天会堆溢出。

为了让效果明显,我们减少堆内存的容量:


最大和最小都配成5m,这样就不会有伸缩空间了。同时,要求将堆溢出错误的文件导出。

在cmd窗口输入jvisualvm,把导出的hprof文件装入。


在这上面我们很容易看到为何会堆溢出。

 for(;;){
            list.add(new TestHeapSpace());
            System.gc();
        }

若我们催促gc进行回收,可以看到一直没有报堆溢出的错误。

打开抽样器:


我们看到,TestHeapSpace的实例还是在一直增加,因为它是无法被回收的(list一直存在着),但是jvm创建对象所使用的其他对象就可以被回收(比如字节数组)。

jconsole

写一个栈溢出的例子:

package com.ocean.jvmtools;

import java.util.concurrent.TimeUnit;

public class TestStackMemory {
    private int length;

    public int getLength() {
        return length;
    }

    public void test() {
        length++;

        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        test();
    }

    public static void main(String[] args) {
        TestStackMemory testStackMemory = new TestStackMemory();

        try {
            testStackMemory.test();
        } catch (Throwable throwable) {
            System.out.println(testStackMemory.getLength());
           throwable.printStackTrace();
        }finally {

        }
    }
}

这是一个递归调用的例子,一个方法就会对应一个栈帧,所以栈一定会被占满。

我们给栈100k的大小,以此更快溢出。

为了能够让程序活得久一点,我们在test方法中加入睡眠,这就给我们争取时间来打开分析工具了。

报错后,我们会打印length的值。

这次我们在cmd控制台输入jconsole,用它来连接程序。

看一下线程的标签:

这里有我们的main线程,有jvm的reference handler线程以及finalizer线程,看名字就知道,前者用于处理引用,后者是垃圾回收器的线程。

还有jconsole自己的tcp线程。

main线程的状态是TIMED_WAITING

线程一共有六种状态,TIMED_WAITING是因为我们调用了sleep方法。

  /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

死锁

比如有两个熊孩子吵架,A对B说:“你先把笔还给我我再还你的尺子”。B对A说:“你先还我的尺子我再还你的笔”。于是他们就这样僵持着。这就是死锁。

给一段代码:

package com.ocean.jvmtools;

import sun.security.jgss.LoginConfigImpl;

import java.util.concurrent.TimeUnit;

public class DeadLock {
    private static final Object LOCK1 = new Object();
    private static final Object LOCK2 = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (LOCK1){
                System.out.println(Thread.currentThread().getName()+" starts...");
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (LOCK2){
                    System.out.println(Thread.currentThread().getName()+" ends...");
                }
            }

        },"A").start();

        new Thread(()->{
            synchronized (LOCK2){
                System.out.println(Thread.currentThread().getName()+" starts...");

                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (LOCK1){
                    System.out.println(Thread.currentThread().getName()+" ends...");
                }
            }
        },"B").start();
    }
}

线程A和线程B分别拿到了LOCK1和LOCK2。它们同时睡了500毫秒,然后都向对方索要锁,但是自己又不释放锁。

所以,程序卡住了。

打开jconsole观察:


线程A和B的状态都是BLOCKED,并且都试图获取对方已经持有的锁。

jconsole专门有死锁检测工具:

一点击就能检测到。

当然,你也可以用命令行的工具,比如jstack

jstack -l pid

这是也有详细的堆栈信息。

元空间溢出

jdk1.8元空间的默认大小是21m,到达这个大小后会进行垃圾回收,如果类的持续产生还是超过了21m,那他就会自动扩容。元空间的内存使用的是计算机的内存,会一直扩容下去。

因此我们要限制元空间的大小,来看到内存溢出。

为了一直产生类,我们使用cgilib:


```java
package com.ocean.jvmtools;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

public class TestMetaSpace {
    public static void main(String[] args) {
        while(true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(TestMetaSpace.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor)(obj, method, arg1, proxy)->
                    proxy.invokeSuper(obj,arg1));

            enhancer.create();
            System.out.println("class is created!");

        }

    }
}

我们在运行过程中动态地产生TestMetaSpace这个类的子类,子类的信息会存入元空间。

我们限定元空间最大为100m,然后用TraceClassLoading来跟踪类的创建。

用jconsole可以看到类的数量一直在增加:


最后控制台报的错:

jcmd

我们还是拿TestMetaSpace这个例子,并且介绍jcmd这个工具。

通过jps命令我们拿到TestMetaSpace的进程id(7860),然后输入jcmd 7860 help


它这里面有很多选项,可以一个个玩。

比如用jcmd 7860 VM.flags可以看到虚拟机的启动参数:


使用jcmd 7860 GC.class_histogram可以看到当前的类信息。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值