JVM問題情景分析

問題分析之死鎖

產生死鎖必須同時知足如下四個條件:java

  • 互斥條件:一段時間內某資源只能被一個線程(進程)佔有,如有其餘請求線程只能等待。linux

  • 不剝奪條件:一個線程佔用某資源後只能該線程本身釋放資源,不能被其餘線程奪走。jvm

  • 請求和保持條件:一個線程去申請另一個資源的時候,繼續佔有已分配的資源。ide

  • 循環等待條件:存在一個處於等待狀態的線程集合{p1,...,pi,..},pi等待的資源被p(i+1)佔有。工具

簡單點說,對於兩個線程A,B而言,先有線程A佔有鎖X,線程B佔有鎖Y,而後A繼續申請鎖Y,B繼續申請鎖X,但因爲此時鎖Y已經被B佔有,A只能等待B釋放鎖Y,同理B也在等待A釋放鎖X。此時造成了一個線程分別等待對方釋放鎖的情況,即產生了死鎖。spa

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&code

在visualVM中進入對應的進程,能夠看到visualVM直接幫助咱們檢測到了死鎖:
deadLock.png
點擊線程dump按鈕,查看dump堆文件:
clipboard.png
因爲這裏的死鎖程序使用的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)

clipboard.png

代碼當中咱們不停的往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.<init>(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,同時順便對直接內存進行垃圾收集清理。

相關文章
相關標籤/搜索