资讯专栏INFORMATION COLUMN

JVM问题情景分析

SnaiLiu / 2855人阅读

摘要:问题分析之死锁产生死锁必须同时满足以下四个条件互斥条件一段时间内某资源只能被一个线程进程占有,若有其他请求线程只能等待。问题分析之内存泄露内存溢出堆内存溢出内存泄露指的是申请内存后无法释放该内存。

问题分析之死锁

产生死锁必须同时满足以下四个条件:

互斥条件:一段时间内某资源只能被一个线程(进程)占有,若有其他请求线程只能等待。

不剥夺条件:一个线程占用某资源后只能该线程自己释放资源,不能被其他线程夺走。

请求和保持条件:一个线程去申请另外一个资源的时候,继续占有已分配的资源。

循环等待条件:存在一个处于等待状态的线程集合{p1,...,pi,..},pi等待的资源被p(i+1)占有。

简单点说,对于两个线程A,B而言,先有线程A占有锁X,线程B占有锁Y,然后A继续申请锁Y,B继续申请锁X,但由于此时锁Y已经被B占有,A只能等待B释放锁Y,同理B也在等待A释放锁X。此时形成了一个线程分别等待对方释放锁的状况,即产生了死锁。

public class DeadLock {
    private static Lock lockA = new ReentrantLock();
    private static Lock lockB = new ReentrantLock();
    
    /*private static Object monitor1 = new Object();
    private static Object monitor2 = new Object();*/

    public static void main(String[] args)  {
        
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        new ThreadA().start();
        new ThreadB().start();
        
    }
    
    static class ThreadA extends Thread{
        
        @Override
        public void run() {
            lockA.lock();
            try {
                Thread.sleep(2000);
                
                lockB.lock();
                System.out.println("in lockB");
                lockB.unlock();
                
            } catch (Exception e) {
                // TODO: handle exception
            }finally{
                lockA.unlock();
            }
            
        /*    synchronized (monitor1) {
            
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                synchronized (monitor2) {
                    System.out.println("in monitor2");
                }
                
                
            }*/
            
        }
        
        
    }
    
    
    static class ThreadB extends Thread{
        
        @Override
        public void run() {
            lockB.lock();
            
            try{
                
                Thread.sleep(4000);
                
                lockA.lock();
                System.out.println("in lockA");
                lockA.unlock();
                
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                lockB.unlock();
            }
            
        /*    synchronized (monitor2) {
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                synchronized(monitor1){
                    System.out.println("in monitor2");
                }
            }
            */
            
        }
        
    }
}

上面代码是一个简单的例子,产生死锁一般可以用jstack命令生成线程快照来分析,当然更好用的有jdk自带的visualVM图形化工具。在java_home目录下的bin文件夹里面,可以找到jvisualVM。在linux下可以使用命令行:

cd $JAVA_HOME/bin
./jvisualvm&

当然JAVA_HOME一般都export到PATH下了,可以直接命令行输入

jvisualvm&

在visualVM中进入对应的进程,可以看到visualVM直接帮助我们检测到了死锁:

点击线程dump按钮,查看dump堆文件:

由于这里的死锁程序使用的Lock锁,可以看到两个线程Thread-0,Thread-1的状态为WAITING(如果使用上面程序注释掉的synchronized锁,线程状态为阻塞)。Thread-1已拥有锁的id为<...71bc8>,等待锁id为<...73008>,相反Thread-0拥有锁<...73008>,正在等待锁<...71bc8>。

问题分析之内存泄露/内存溢出 1. 堆内存溢出(outOfMemoryError:java heap space)

内存泄露memory leak:指的是申请内存后无法释放该内存。在java当中指的是存在无用,而且是可达的(导致jvm无法回收)的对象。
内存溢出out of memory:指的是申请内存时,已没有足够的内存空间供使用。
内存泄露如果大量的堆积,消耗足够多的内存,最后会产生内存溢出。

下面是一个内存泄露最终导致内存溢出的例子:

public class MemoryLeak {
    public static void main(String[] args) {
        sleep(9000);
        Vector v = new Vector();
        long count = 0;
        while (true) {
            Object o = new Object();
            v.add(o);
            o = null;
            count++;
            if (count % 100 == 0) {
                System.out.println("vector size: " + v.size());
                long freeMem = Runtime.getRuntime().freeMemory()
                        / (1024 * 1024);
                System.out.println("freeMemory is " + freeMem + "M in count->"
                        + count);
            }
        }
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

我们加上jvm启动参数,将最小和最大堆大小均设为20M:

-Xms20m -Xmx20m

最后得到溢出异常:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.Vector.grow(Vector.java:266)
at java.util.Vector.ensureCapacityHelper(Vector.java:246)
at java.util.Vector.add(Vector.java:782)
at com.ethfoo.jvm.MemoryLeak.main(MemoryLeak.java:17)

代码当中我们不停的往Vector里面加Object对象,并且每个对象的引用o置为null。我们假设这些Object已是无用对象,虽然我们将o置为null,但其实Vector里面仍然保存每个Object对象的引用,所以Object对jvm来说是可达的,jvm无法对其进行回收。

2. 方法区内存溢出(outOfMemoryError:permgem space)

在jvm规范中,方法区主要存放的是类信息、常量、静态变量等。
所以如果程序加载的类过多,或者使用反射、gclib等这种动态代理生成类的技术,就可能导致该区发生内存溢出,一般该区发生内存溢出时的错误信息为:

outOfMemoryError:permgem space

可以使用jvm参数调整方法区的大小分配:

-XX:PermSize -XX:MaxPermSize

3. 线程栈溢出(StackOverflowError)

一般线程栈溢出是由于递归太深或方法调用层级过多导致的。
可以使用以下参数来调整栈大小的分配,线程栈越大递归调用的深度越大,但同时由于总的内存大小限制,会使总体能够启动的线程数目减小。

-Xss

4. 直接内存溢出(OutOfMemoryError: Direct buffer memory)

试运行以下代码,直接分配大量堆外内存:

public static void main(String args[]){
    for(int i=0; i<3024; i++){
            ByteBuffer.allocateDirect(1024*1024*1024);
            System.out.println(i);
            //System.gc();
    }
}

最后导致结果:

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:658)
at java.nio.DirectByteBuffer.(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at com.ethfoo.jvm.OutOfMemory.directMemeory(OutOfMemory.java:28)
at com.ethfoo.jvm.OutOfMemory.main(OutOfMemory.java:10)

需要注意的是,直接内存是无法触发jvm的内存回收机制的,直接内存可以被垃圾收集器回收,但是只能是由堆中内存触发gc,同时顺便对直接内存进行垃圾收集清理。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64823.html

相关文章

  • jvm原理

    摘要:在之前,它是一个备受争议的关键字,因为在程序中使用它往往收集器理解和原理分析简称,是后提供的面向大内存区数到数多核系统的收集器,能够实现软停顿目标收集并且具有高吞吐量具有更可预测的停顿时间。 35 个 Java 代码性能优化总结 优化代码可以减小代码的体积,提高代码运行的效率。 从 JVM 内存模型谈线程安全 小白哥带你打通任督二脉 Java使用读写锁替代同步锁 应用情景 前一阵有个做...

    lufficc 评论0 收藏0
  • 同样是做后端的,为什么你同学年薪80万,你20多万?

    摘要:然而偶尔的一次聚会,你听说和自己一起出道的同学早已经年薪万,而自己却囊中羞涩。这个时候,你可能会怀疑自己的能力,也痛恨为什么当初自己没有好好复习。 作为一个 Java 程序员,我们深知水平的深浅决定你的收入高低,月工资下到七八千,上到十几万都是很正常的事情。许多人的现状是平时总是陷在业务开发...

    邹强 评论0 收藏0
  • 作为一名Java程序员,因为偷偷接私活被...

    摘要:接私活对程序员这个圈子来说是一个既公开又隐私的话题,不说全部,应该大多数程序员都有过想要接私活的想法,当然,也有部分得道成仙的不主张接私活。 接私活 对程序员这个圈子来说是一个既公开又隐私的话题,不说全部,应该大多数程序员都有过想要接私活的想法,当然,也有部分得道成仙的不主张接私活。但是很少...

    Aomine 评论0 收藏0
  • 麻了,都是科班出身的,学弟月薪却是我的3倍。

    摘要:可现在五年过去了,他想跳槽却鲜有人问津。最可气的是比他晚一年毕业的学弟,勤勤恳恳在一家中型互联网企业干了年,现在已经跳槽到了阿里,月薪是我这个同学的倍。 我有个同学大学毕业,因为却少工作经验,又不愿意去正经的互联网企业做实习生,他嫌工资太低,于是进了家外包公司,那时候感觉待遇还可以。可现在五...

    wangzy2019 评论0 收藏0
  • Java程序员工作3年,薪资为何会被应届生倒挂?

    摘要:同时也会关注市场上同岗位薪资,以便对企业内部薪资结构做出相应调整。一般来说,相同岗位和职责的员工,薪资低于市场不超过,都属于合理范畴,因为一个员工不会为了的薪酬而跳槽。同时,还能激励员工自我提升,以获得相应技能市场所给予的报酬。 各位职场人都听说过薪资倒挂这词儿吧,这个情况在行业内早就不是什...

    szysky 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<