摘要:問題分析之死鎖產(chǎn)生死鎖必須同時滿足以下四個條件互斥條件一段時間內(nèi)某資源只能被一個線程進程占有,若有其他請求線程只能等待。問題分析之內(nèi)存泄露內(nèi)存溢出堆內(nèi)存溢出內(nèi)存泄露指的是申請內(nèi)存后無法釋放該內(nèi)存。
問題分析之死鎖
產(chǎn)生死鎖必須同時滿足以下四個條件:
互斥條件:一段時間內(nèi)某資源只能被一個線程(進程)占有,若有其他請求線程只能等待。
不剝奪條件:一個線程占用某資源后只能該線程自己釋放資源,不能被其他線程奪走。
請求和保持條件:一個線程去申請另外一個資源的時候,繼續(xù)占有已分配的資源。
循環(huán)等待條件:存在一個處于等待狀態(tài)的線程集合{p1,...,pi,..},pi等待的資源被p(i+1)占有。
簡單點說,對于兩個線程A,B而言,先有線程A占有鎖X,線程B占有鎖Y,然后A繼續(xù)申請鎖Y,B繼續(xù)申請鎖X,但由于此時鎖Y已經(jīng)被B占有,A只能等待B釋放鎖Y,同理B也在等待A釋放鎖X。此時形成了一個線程分別等待對方釋放鎖的狀況,即產(chǎn)生了死鎖。
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"); } } */ } } }
上面代碼是一個簡單的例子,產(chǎn)生死鎖一般可以用jstack命令生成線程快照來分析,當然更好用的有jdk自帶的visualVM圖形化工具。在java_home目錄下的bin文件夾里面,可以找到j(luò)visualVM。在linux下可以使用命令行:
cd $JAVA_HOME/bin
./jvisualvm&
當然JAVA_HOME一般都export到PATH下了,可以直接命令行輸入
jvisualvm&
在visualVM中進入對應(yīng)的進程,可以看到visualVM直接幫助我們檢測到了死鎖:
點擊線程dump按鈕,查看dump堆文件:
由于這里的死鎖程序使用的Lock鎖,可以看到兩個線程Thread-0,Thread-1的狀態(tài)為WAITING(如果使用上面程序注釋掉的synchronized鎖,線程狀態(tài)為阻塞)。Thread-1已擁有鎖的id為<...71bc8>,等待鎖id為<...73008>,相反Thread-0擁有鎖<...73008>,正在等待鎖<...71bc8>。
內(nèi)存泄露memory leak:指的是申請內(nèi)存后無法釋放該內(nèi)存。在java當中指的是存在無用,而且是可達的(導致jvm無法回收)的對象。
內(nèi)存溢出out of memory:指的是申請內(nèi)存時,已沒有足夠的內(nèi)存空間供使用。
內(nèi)存泄露如果大量的堆積,消耗足夠多的內(nèi)存,最后會產(chǎn)生內(nèi)存溢出。
下面是一個內(nèi)存泄露最終導致內(nèi)存溢出的例子:
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啟動參數(shù),將最小和最大堆大小均設(shè)為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。我們假設(shè)這些Object已是無用對象,雖然我們將o置為null,但其實Vector里面仍然保存每個Object對象的引用,所以O(shè)bject對jvm來說是可達的,jvm無法對其進行回收。
2. 方法區(qū)內(nèi)存溢出(outOfMemoryError:permgem space)在jvm規(guī)范中,方法區(qū)主要存放的是類信息、常量、靜態(tài)變量等。
所以如果程序加載的類過多,或者使用反射、gclib等這種動態(tài)代理生成類的技術(shù),就可能導致該區(qū)發(fā)生內(nèi)存溢出,一般該區(qū)發(fā)生內(nèi)存溢出時的錯誤信息為:
outOfMemoryError:permgem space
可以使用jvm參數(shù)調(diào)整方法區(qū)的大小分配:
3. 線程棧溢出(StackOverflowError)-XX:PermSize -XX:MaxPermSize
一般線程棧溢出是由于遞歸太深或方法調(diào)用層級過多導致的。
可以使用以下參數(shù)來調(diào)整棧大小的分配,線程棧越大遞歸調(diào)用的深度越大,但同時由于總的內(nèi)存大小限制,會使總體能夠啟動的線程數(shù)目減小。
4. 直接內(nèi)存溢出(OutOfMemoryError: Direct buffer memory)-Xss
試運行以下代碼,直接分配大量堆外內(nèi)存:
public static void main(String args[]){ for(int i=0; i<3024; i++){ ByteBuffer.allocateDirect(1024*1024*1024); System.out.println(i); //System.gc(); } }
最后導致結(jié)果:
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)
需要注意的是,直接內(nèi)存是無法觸發(fā)jvm的內(nèi)存回收機制的,直接內(nèi)存可以被垃圾收集器回收,但是只能是由堆中內(nèi)存觸發(fā)gc,同時順便對直接內(nèi)存進行垃圾收集清理。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/64823.html
摘要:然而偶爾的一次聚會,你聽說和自己一起出道的同學早已經(jīng)年薪萬,而自己卻囊中羞澀。這個時候,你可能會懷疑自己的能力,也痛恨為什么當初自己沒有好好復(fù)習。 作為一個 Java 程序員,我們深知水平的深淺決定你的收入高低,月工資下到七八千,上到十幾萬都是很正常的事情。許多人的現(xiàn)狀是平時總是陷在業(yè)務(wù)開發(fā)...
摘要:接私活對程序員這個圈子來說是一個既公開又隱私的話題,不說全部,應(yīng)該大多數(shù)程序員都有過想要接私活的想法,當然,也有部分得道成仙的不主張接私活。 接私活 對程序員這個圈子來說是一個既公開又隱私的話題,不說全部,應(yīng)該大多數(shù)程序員都有過想要接私活的想法,當然,也有部分得道成仙的不主張接私活。但是很少...
摘要:可現(xiàn)在五年過去了,他想跳槽卻鮮有人問津。最可氣的是比他晚一年畢業(yè)的學弟,勤勤懇懇在一家中型互聯(lián)網(wǎng)企業(yè)干了年,現(xiàn)在已經(jīng)跳槽到了阿里,月薪是我這個同學的倍。 我有個同學大學畢業(yè),因為卻少工作經(jīng)驗,又不愿意去正經(jīng)的互聯(lián)網(wǎng)企業(yè)做實習生,他嫌工資太低,于是進了家外包公司,那時候感覺待遇還可以。可現(xiàn)在五...
摘要:同時也會關(guān)注市場上同崗位薪資,以便對企業(yè)內(nèi)部薪資結(jié)構(gòu)做出相應(yīng)調(diào)整。一般來說,相同崗位和職責的員工,薪資低于市場不超過,都屬于合理范疇,因為一個員工不會為了的薪酬而跳槽。同時,還能激勵員工自我提升,以獲得相應(yīng)技能市場所給予的報酬。 各位職場人都聽說過薪資倒掛這詞兒吧,這個情況在行業(yè)內(nèi)早就不是什...
閱讀 3231·2021-11-11 16:54
閱讀 2395·2021-09-04 16:48
閱讀 3387·2019-08-29 16:08
閱讀 728·2019-08-29 15:13
閱讀 1455·2019-08-29 15:09
閱讀 2747·2019-08-29 12:45
閱讀 2010·2019-08-29 12:12
閱讀 531·2019-08-26 18:27