JVM相關(guān)(8)-- 內(nèi)存泄露和溢出場景及預(yù)防措施

8、內(nèi)存泄露和溢出場景及預(yù)防措施

內(nèi)存泄露(memory leak)袖肥,是指程序在申請內(nèi)存后障本,無法釋放已申請的內(nèi)存空間,即分配出去的內(nèi)存無法回收(不再使用的對象或者變量仍占內(nèi)存空間)橙困,在Java中內(nèi)存泄漏就是存在一些被分配的對象(可達(dá)的,卻是無用的)無法被gc回收耕餐。

內(nèi)存溢出(out of memory)凡傅,是指程序在申請內(nèi)存時(shí),沒有足夠的內(nèi)存空間供其使用肠缔,出現(xiàn)out of memory夏跷;比如申請了一個(gè)integer,但給它存了long才能存下的數(shù)明未,那就是內(nèi)存溢出槽华。可以看出內(nèi)存泄漏是內(nèi)存溢出的一種誘因趟妥,但不是唯一因素猫态。

memory leak會(huì)最終會(huì)導(dǎo)致out of memory!

Java判斷內(nèi)存空間是否符合垃圾回收標(biāo)準(zhǔn)有兩個(gè):給對象賦null且不再使用;給對象賦新值亲雪,重新分配內(nèi)存勇凭。

內(nèi)存泄漏的兩種情況:一是堆中申請的內(nèi)存沒釋放;二是對象已不再使用匆光,但還在內(nèi)存中保留著套像。

GC可以有效的解決第一種情況,但是無法保證情況二终息,所以Java存在的內(nèi)存泄漏主要是第二種夺巩。


以發(fā)生的方式來分類,內(nèi)存泄漏可以分為4類:

1. 常發(fā)性內(nèi)存泄漏周崭。發(fā)生內(nèi)存泄漏的代碼會(huì)被多次執(zhí)行到柳譬,每次被執(zhí)行的時(shí)候都會(huì)導(dǎo)致一塊內(nèi)存泄漏。

2. 偶發(fā)性內(nèi)存泄漏续镇。發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過程下才會(huì)發(fā)生美澳。常發(fā)性和偶發(fā)性是相對的。對于特定的環(huán)境摸航,偶發(fā)性的也許就變成了常發(fā)性的制跟。所以測試環(huán)境和測試方法對檢測內(nèi)存泄漏至關(guān)重要。

3. 一次性內(nèi)存泄漏酱虎。發(fā)生內(nèi)存泄漏的代碼只會(huì)被執(zhí)行一次雨膨,或者由于算法上的缺陷,導(dǎo)致總會(huì)有一塊僅且一塊內(nèi)存發(fā)生泄漏读串。比如聊记,在類的構(gòu)造函數(shù)中分配內(nèi)存,在析構(gòu)函數(shù)中卻沒有釋放該內(nèi)存恢暖,所以內(nèi)存泄漏只會(huì)發(fā)生一次排监。

4. 隱式內(nèi)存泄漏。程序在運(yùn)行過程中不停的分配內(nèi)存杰捂,但是直到結(jié)束的時(shí)候才釋放內(nèi)存舆床。嚴(yán)格的說這里并沒有發(fā)生內(nèi)存泄漏,因?yàn)樽罱K程序釋放了所有申請的內(nèi)存嫁佳。但是對于一個(gè)服務(wù)器程序挨队,需要運(yùn)行幾天,幾周甚至幾個(gè)月脱拼,不及時(shí)釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以坷备,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏熄浓。

從用戶使用程序的角度來看,內(nèi)存泄漏本身不會(huì)產(chǎn)生什么危害,作為一般的用戶赌蔑,根本感覺不到內(nèi)存泄漏的存在俯在。真正有危害的是內(nèi)存泄漏的堆積,這會(huì)最終消耗盡系統(tǒng)所有的內(nèi)存娃惯。從這個(gè)角度來說跷乐,一次性內(nèi)存泄漏并沒有什么危害,因?yàn)樗粫?huì)堆積趾浅,而隱式內(nèi)存泄漏危害性則非常大愕提,因?yàn)檩^之于常發(fā)性和偶發(fā)性內(nèi)存泄漏它更難被檢測到。

Java內(nèi)存泄漏的根本原因是什么呢皿哨?長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄漏浅侨,盡管短生命周期對象已經(jīng)不再需要,但是因?yàn)殚L生命周期持有它的引用而導(dǎo)致不能被回收证膨,這就是Java中內(nèi)存泄漏的發(fā)生場景如输。

具體主要有如下幾大類:

1、靜態(tài)集合類引起內(nèi)存泄露:

像HashMap央勒、Vector等的使用最容易出現(xiàn)內(nèi)存泄露不见,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對象Object也不能被釋放崔步,因?yàn)樗麄円矊⒁恢北籚ector等引用著稳吮。

Static Vector v = new Vector(10);

for (int i = 1; i<100; i++)

{

????? Object o = newObject();

????? v.add(o);

????? o = null;

}

在這個(gè)例子中,循環(huán)申請Object 對象刷晋,并將所申請的對象放入一個(gè)Vector 中盖高,如果僅僅釋放引用本身(o=null),那么Vector 仍然引用該對象眼虱,所以這個(gè)對象對GC 來說是不可回收的喻奥。因此,如果對象加入到Vector 后捏悬,還必須從Vector 中刪除撞蚕,最簡單的方法就是將Vector對象設(shè)置為null。

2过牙、當(dāng)集合里面的對象屬性被修改后甥厦,再調(diào)用remove()方法時(shí)不起作用。

public class MainTest

{

??? public static void main(String[] args)

??? {

??????? MainTestmain = new MainTest();

??????? Setset = newHashSet<Person>();

??????? Personp1 = main.new Person("唐僧", 25);

??????? Personp2 = main.new Person("孫悟空", 26);

??????? Personp3 = main.new Person("豬八戒", 27);

??????? set.add(p1);

??????? set.add(p2);

??????? set.add(p3);

??????? System.out.println("總共有:" + set.size() + " 個(gè)元素!"); // 結(jié)果:總共有:3 個(gè)元素!

??????? p3.setAge(2); // 修改p3的年齡,此時(shí)p3元素對應(yīng)的hashcode值發(fā)生改變

??????? set.remove(p3); // 此時(shí)remove不掉寇钉,造成內(nèi)存泄漏

??????? set.add(p3); // 重新添加刀疙,居然添加成功

??????? System.out.println("總共有:" + set.size() + " 個(gè)元素!"); // 結(jié)果:總共有:4 個(gè)元素!

??? }

??? class Person

??? {

??????? private String name;

??????? private int age;

??????? publicPerson(String name, int age)

??????? {

??????????? super();

??????????? this.name = name;

??????????? this.age = age;

??????? }

??????? public String getName()

??????? {

??????????? return name;

??????? }

??????? public void setName(Stringname)

??????? {

??????????? this.name = name;

??????? }

??????? public int getAge()

??????? {

??????????? return age;

??????? }

??????? public void setAge(int age)

??????? {

???????? ???this.age = age;

??????? }

??????? @Override

??????? public String toString()

??????? {

??????????? return "Person [name=" + name + ", age=" + age + "]";

??????? }

??????? @Override

??????? public int hashCode()

??????? {

??????????? final int prime = 31;

??????????? int result = 1;

??????????? result = prime * result + getOuterType().hashCode();

??????????? result = prime * result + age;

??????????? result = prime * result + ((name == null) ? 0 : name.hashCode());

??????????? return result;

??????? }

??????? @Override

??????? public booleanequals(Object obj)

??????? {

??????????? if (this == obj)

??????????????? return true;

??????????? if (obj == null)

??????????????? return false;

??????????? if (getClass() !=obj.getClass())

??????????????? return false;

??????????? Personother = (Person) obj;

??????????? if(!getOuterType().equals(other.getOuterType()))

??????????????? return false;

??????????? if (age != other.age)

??????????????? return false;

??????????? if (name == null)

??????????? {

??????????????? if (other.name != null)

??????????????????? return false;

??????????? }

??????????? else if (!name.equals(other.name))

??????????????? return false;

??????????? return true;

??????? }

??????? privateMainTest getOuterType()

??????? {

??????????? returnMainTest.this;

??????? }

??? }

}

3、變量不合理的作用域

如果變量的定義范圍大于使用范圍扫倡,并且在使用完后沒有賦值為null的話谦秧,會(huì)出現(xiàn)內(nèi)存泄露。定義變量的時(shí)候,能定義為局部變量就不要定義為成員變量疚鲤,或者定義為成員變量的話锥累,在使用完變量后,把變量賦值為null集歇。

4桶略、使用非靜態(tài)內(nèi)部類

非靜態(tài)內(nèi)部類對象的構(gòu)建依賴于其外部類,內(nèi)部類對象會(huì)持有外部類對象的this引用诲宇,即時(shí)外部類對象不再被使用了际歼,其占用的內(nèi)存可能不會(huì)被GC回收,因?yàn)閮?nèi)部類的生命周期可能比外部類的生命周期要長焕窝,從而造成外部類對象不能被及時(shí)回收蹬挺。解決辦法是盡量使用靜態(tài)內(nèi)部類,靜態(tài)內(nèi)部類只是形式上在外部類的里面它掂,靜態(tài)內(nèi)部類不會(huì)持有外部類的引用巴帮,可以把靜態(tài)內(nèi)部類理解成是一個(gè)獨(dú)立的類,和外部類沒什么關(guān)系虐秋。

5榕茧、單例模式可能會(huì)造成內(nèi)存泄露

單例模式只允許應(yīng)用程序存在一個(gè)實(shí)例對象,并且這個(gè)實(shí)例對象的生命周期和應(yīng)用程序的生命周期一樣長客给,如果單例對象中擁有另一個(gè)對象的引用的話用押,這個(gè)被引用的對象就不能被及時(shí)回收。解決辦法是單例對象中持有的其他對象使用弱引用靶剑,弱引用對象在GC線程工作時(shí)蜻拨,其占用的內(nèi)存會(huì)被回收掉,如下示例:

public class SingleTon1 {???

??? private static finalSingleTon1 mInstance = null;???

??? privateWeakReference mContext;?

??? privateSingleTon1(WeakReference context) {???

??? mContext = context;?

??? }? ??

??? public static SingleTon1getInstance(WeakReference context) {???

??????? if (mInstance ==null) {???

??????????? synchronized(SingleTon1.class) {???

??????????????? if(mInstance == null) {???

???????????????????mInstance = new SingleTon1(context);???

??????????????? }???

??????????? }???

??????? }???

??????? returnmInstance;???

??? }???

}? ?

public class MyActivity extents Activity {?

??? public void onCreate(Bundle savedInstanceState){?

??????super.onCreate(savedInstanceState);?

??????setContentView(R.layout.main);?

?????? SingleTon1 singleTon1= SingleTon1.getInstance(new WeakReference(this));?

?? }?

}?

6桩引、監(jiān)聽器

在java 編程中缎讼,我們都需要和監(jiān)聽器打交道,通常一個(gè)應(yīng)用當(dāng)中會(huì)用到很多監(jiān)聽器坑匠,我們會(huì)調(diào)用一個(gè)控件的諸如addXXXListener()等方法來增加監(jiān)聽器血崭,但往往在釋放對象的時(shí)候卻沒有記住去刪除這些監(jiān)聽器,從而增加了內(nèi)存泄漏的機(jī)會(huì)厘灼。

7夹纫、各種連接

比如數(shù)據(jù)庫連接(dataSourse.getConnection()),網(wǎng)絡(luò)連接(socket)和io連接设凹,除非其顯式的調(diào)用了其close()方法將其連接關(guān)閉舰讹,否則是不會(huì)自動(dòng)被GC 回收的。對于Resultset 和Statement 對象可以不進(jìn)行顯式回收闪朱,但Connection 一定要顯式回收月匣,因?yàn)镃onnection 在任何時(shí)候都無法自動(dòng)回收匈睁,而Connection一旦回收,Resultset 和Statement 對象就會(huì)立即為NULL桶错。但是如果使用連接池,情況就不一樣了胀蛮,除了要顯式地關(guān)閉連接院刁,還必須顯式地關(guān)閉Resultset Statement 對象(關(guān)閉其中一個(gè),另外一個(gè)也會(huì)關(guān)閉)粪狼,否則就會(huì)造成大量的Statement 對象無法釋放退腥,從而引起內(nèi)存泄漏。這種情況下一般都會(huì)在try里面去的連接再榄,在finally里面釋放連接狡刘。


如何排查

(1)通過jps查找java進(jìn)程id。

(2)通過top -p [pid]發(fā)現(xiàn)內(nèi)存占用達(dá)到了最大值

(3)jstat -gccause pid 20000 每隔20秒輸出Full GC結(jié)果

(4)發(fā)現(xiàn)Full GC次數(shù)太多困鸥,基本就是內(nèi)存泄露了嗅蔬。生成dump文件,借助工具分析是哪個(gè)對象太多了疾就±绞酰基本能定位到問題在哪。


Full GC的原因

我們知道Full GC的觸發(fā)條件大致情況有以下幾種情況:

1. 程序執(zhí)行了System.gc() //建議jvm執(zhí)行fullgc猬腰,并不一定會(huì)執(zhí)行

2. 執(zhí)行了jmap -histo:live pid命令 //這個(gè)會(huì)立即觸發(fā)fullgc

3. 在執(zhí)行minor gc的時(shí)候進(jìn)行的一系列檢查

??? *執(zhí)行Minor GC的時(shí)候鸟废,JVM會(huì)檢查老年代中最大連續(xù)可用空間是否大于了當(dāng)前新生代所有對象的總大小。

??? *如果大于姑荷,則直接執(zhí)行Minor GC(這個(gè)時(shí)候執(zhí)行是沒有風(fēng)險(xiǎn)的)盒延。

??? *如果小于了,JVM會(huì)檢查是否開啟了空間分配擔(dān)保機(jī)制鼠冕,如果沒有開啟則直接改為執(zhí)行Full GC添寺。

??? *如果開啟了,則JVM會(huì)檢查老年代中最大連續(xù)可用空間是否大于了歷次晉升到老年代中的平均大小供鸠,如果小于則執(zhí)行改為執(zhí)行Full GC畦贸。

??? *如果大于則會(huì)執(zhí)行Minor GC,如果Minor GC執(zhí)行失敗則會(huì)執(zhí)行Full GC

對于我們的情況楞捂,可以初步排除1薄坏,2兩種情況,最有可能是4和5這兩種情況寨闹。為了進(jìn)一步排查原因胶坠,我們在線上開啟了 -XX:+HeapDumpBeforeFullGC。


注意:

JVM在執(zhí)行dump操作的時(shí)候是會(huì)發(fā)生stop the word事件的繁堡,也就是說此時(shí)所有的用戶線程都會(huì)暫停運(yùn)行沈善。

為了在此期間也能對外正常提供服務(wù)乡数,建議采用分布式部署,并采用合適的負(fù)載均衡算法闻牡。


內(nèi)存溢出種類:

引起內(nèi)存溢出的原因有很多種净赴,常見的有以下幾種:

1.內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù)罩润;

2.集合類中有對對象的引用玖翅,使用完后未清空,使得JVM不能回收割以;

3.代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對象實(shí)體金度;

4.使用的第三方軟件中的BUG;

5.啟動(dòng)參數(shù)內(nèi)存值設(shè)定的過小


內(nèi)存溢出的解決方案:

第一步严沥,修改JVM啟動(dòng)參數(shù)猜极,直接增加內(nèi)存。(-Xms消玄,-Xmx參數(shù)一定不要忘記加跟伏。)

第二步,檢查錯(cuò)誤日志翩瓜,查看“OutOfMemory”錯(cuò)誤前是否有其它異吵昴罚或錯(cuò)誤。

第三步奥溺,對代碼進(jìn)行走查和分析辞色,找出可能發(fā)生內(nèi)存溢出的位置。

重點(diǎn)排查以下幾點(diǎn):

1.檢查對數(shù)據(jù)庫查詢中浮定,是否有一次獲得全部數(shù)據(jù)的查詢相满。一般來說,如果一次取十萬條記錄到內(nèi)存桦卒,就可能引起內(nèi)存溢出立美。這個(gè)問題比較隱蔽,在上線前方灾,數(shù)據(jù)庫中數(shù)據(jù)較少建蹄,不容易出問題,上線后裕偿,數(shù)據(jù)庫中數(shù)據(jù)多了洞慎,一次查詢就有可能引起內(nèi)存溢出。因此對于數(shù)據(jù)庫查詢盡量采用分頁的方式查詢嘿棘。

2.檢查代碼中是否有死循環(huán)或遞歸調(diào)用劲腿。

3.檢查是否有大循環(huán)重復(fù)產(chǎn)生新對象實(shí)體。

4.檢查對數(shù)據(jù)庫查詢中鸟妙,是否有一次獲得全部數(shù)據(jù)的查詢焦人。一般來說挥吵,如果一次取十萬條記錄到內(nèi)存,就可能引起內(nèi)存溢出花椭。這個(gè)問題比較隱蔽忽匈,在上線前,數(shù)據(jù)庫中數(shù)據(jù)較少矿辽,不容易出問題脉幢,上線后,數(shù)據(jù)庫中數(shù)據(jù)多了嗦锐,一次查詢就有可能引起內(nèi)存溢出。因此對于數(shù)據(jù)庫查詢盡量采用分頁的方式查詢沪曙。

5.檢查List奕污、MAP等集合對象是否有使用完后,未清除的問題液走。List碳默、MAP等集合對象會(huì)始終存有對對象的引用,使得這些對象不能被GC回收缘眶。

第四步嘱根,使用內(nèi)存查看工具動(dòng)態(tài)查看內(nèi)存使用情況

?

內(nèi)存不同區(qū)域溢出情況:

1,堆內(nèi)存溢出

場景:

1)設(shè)置的jvm內(nèi)存太小巷懈,對象所需內(nèi)存太大该抒,創(chuàng)建對象時(shí)分配空間,就會(huì)拋出這個(gè)異常顶燕。

堆內(nèi)存中主要存放對象凑保、數(shù)組等,只要不斷地創(chuàng)建這些對象涌攻,并且保證GC Roots到對象之間有可達(dá)路徑來避免垃圾收集回收機(jī)制清除這些對象欧引,當(dāng)這些對象所占空間超過最大堆容量時(shí),就會(huì)產(chǎn)生java.lang.OutOfMemoryError:Java heap space的異常恳谎。

2)流量/數(shù)據(jù)峰值芝此,應(yīng)用程序自身的處理存在一定的限額,比如一定數(shù)量的用戶或一定數(shù)量的數(shù)據(jù)因痛。而當(dāng)用戶數(shù)量或數(shù)據(jù)量突然激增并超過預(yù)期的閾值時(shí)婚苹,那么就會(huì)在峰值停止前正常運(yùn)行的操作將停止并觸發(fā)java . lang.OutOfMemoryError:Java heap space錯(cuò)誤。


堆內(nèi)存異常示例如下:

/**

?*設(shè)置最大堆最小堆:-Xms20m -Xmx20m

?*運(yùn)行時(shí)鸵膏,不斷在堆中創(chuàng)建OOMObject類的實(shí)例對象租副,且while執(zhí)行結(jié)束之前,GC Roots(代碼中的oomObjectList)到對象(每一個(gè)OOMObject對象)之間有可達(dá)路徑较性,垃圾收集器就無法回收它們用僧,最終導(dǎo)致內(nèi)存溢出结胀。

?*/

public class HeapOOM {

??? static class OOMObject {

??? }

??? public static voidmain(String[] args) {

???????List oomObjectList = new ArrayList<>();

??????? while (true) {

???????????oomObjectList.add(new OOMObject());

??????? }

??? }

}

運(yùn)行后會(huì)報(bào)異常,在堆棧信息中可以看到 java.lang.OutOfMemoryError: Java heap space 的信息责循,說明在堆內(nèi)存空間產(chǎn)生內(nèi)存溢出的異常糟港。


常見的原因 :

內(nèi)存加載的數(shù)據(jù)量太大:一次性從數(shù)據(jù)庫取太多數(shù)據(jù)

集合類中有對對象的引用,使用后未清空院仿,GC不能進(jìn)行回收秸抚。

代碼中存在循環(huán)產(chǎn)生過多的重復(fù)對象

啟動(dòng)參數(shù)堆內(nèi)存值小

解決方法:

首先,如果代碼沒有什么問題的情況下歹垫,可以適當(dāng)調(diào)整-Xms和-Xmx兩個(gè)jvm參數(shù)剥汤,使用壓力測試來調(diào)整這兩個(gè)參數(shù)達(dá)到最優(yōu)值。

其次排惨,盡量避免大的對象的申請吭敢,像文件上傳,大批量從數(shù)據(jù)庫中獲取暮芭,這是需要避免的鹿驼,盡量分塊或者分批處理,有助于系統(tǒng)的正常穩(wěn)定的執(zhí)行辕宏。

最后畜晰,盡量提高一次請求的執(zhí)行速度,垃圾回收越早越好瑞筐,否則凄鼻,大量的并發(fā)來了的時(shí)候,再來新的請求就無法分配內(nèi)存了聚假,就容易造成系統(tǒng)的雪崩野宜。


2,虛擬機(jī)棧/本地方法棧溢出

(1)StackOverflowError:當(dāng)線程請求的棧的深度大于虛擬機(jī)所允許的最大深度魔策,則拋出StackOverflowError匈子,簡單理解就是虛擬機(jī)棧中的棧幀數(shù)量過多(一個(gè)線程嵌套調(diào)用的方法數(shù)量過多)時(shí),就會(huì)拋出StackOverflowError異常闯袒。最常見的場景就是方法無限遞歸調(diào)用逛尚,

如下:

/**

?*設(shè)置每個(gè)線程的棧大型靶:-Xss256k

?*運(yùn)行時(shí)明郭,不斷調(diào)用doSomething()方法嬉挡,main線程不斷創(chuàng)建棧幀并入棧,導(dǎo)致棧的深度越來越大喷户,最終導(dǎo)致棧溢出唾那。

?*/

public class StackSOF {

??? private intstackLength=1;

??? public void doSomething(){

??????????? stackLength++;

??????????? doSomething();

??? }

??? public static voidmain(String[] args) {

??????? StackSOFstackSOF=new StackSOF();

??????? try {

???????????stackSOF.doSomething();

??????? }catch (Throwablee){//注意捕獲的是Throwable

???????????System.out.println("棧深度:"+stackSOF.stackLength);

??????????? throw e;

??????? }

??? }

}

上述代碼執(zhí)行后拋出:Exception in thread “Thread-0” java.lang.StackOverflowError的異常。

常見原因:

棧內(nèi)存溢出褪尝,一般由棧內(nèi)存的局部變量過爆了闹获,導(dǎo)致內(nèi)存溢出期犬,出現(xiàn)在遞歸方法,參數(shù)個(gè)數(shù)過多避诽,遞歸過深龟虎,遞歸沒有出口。

(2)OutOfMemoryError:如果虛擬機(jī)在擴(kuò)展棧時(shí)無法申請到足夠的內(nèi)存空間沙庐,則拋出OutOfMemoryError鲤妥。我們可以這樣理解,虛擬機(jī)中可以供棧占用的空間≈可用物理內(nèi)存 - 最大堆內(nèi)存 - 最大方法區(qū)內(nèi)存拱雏,比如一臺機(jī)器內(nèi)存為4G棉安,系統(tǒng)和其他應(yīng)用占用2G,虛擬機(jī)可用的物理內(nèi)存為2G铸抑,最大堆內(nèi)存為1G贡耽,最大方法區(qū)內(nèi)存為512M,那可供棧占有的內(nèi)存大約就是512M羡滑,假如我們設(shè)置每個(gè)線程棧的大小為1M,那虛擬機(jī)中最多可以創(chuàng)建512個(gè)線程算芯,超過512個(gè)線程再創(chuàng)建就沒有空間可以給棧了,就報(bào)OutOfMemoryError異常了熙揍。

棧上能夠產(chǎn)生OutOfMemoryError的示例如下:

/**

?*設(shè)置每個(gè)線程的棧大兄暗弧:-Xss2m

?*運(yùn)行時(shí),不斷創(chuàng)建新的線程(且每個(gè)線程持續(xù)執(zhí)行)届囚,每個(gè)線程對一個(gè)一個(gè)棧有梆,最終沒有多余的空間來為新的線程分配,導(dǎo)致OutOfMemoryError

?*/

public class StackOOM {

??? private static intthreadNum = 0;

??? public voiddoSomething() {

??????? try {

??????????? Thread.sleep(100000000);

??????? } catch(InterruptedException e) {

???????????e.printStackTrace();

??????? }

??? }

??? public static voidmain(String[] args) {

??????? final StackOOMstackOOM = new StackOOM();

??????? try {

??????????? while (true) {

??????????????? threadNum++;

??????????????? Threadthread = new Thread(new Runnable() {

???????????????????@Override

??????????????????? publicvoid run() {

???????????????????????stackOOM.doSomething();

??????????????????? }

??????????????? });

?????? ?????????thread.start();

??????????? }

??????? } catch (Throwablee) {

???????????System.out.println("目前活動(dòng)線程數(shù)量:" + threadNum);

??????????? throw e;

??????? }

??? }

}

上述代碼運(yùn)行后會(huì)報(bào)異常意系,在堆棧信息中可以看到 java.lang.OutOfMemoryError: unable to create new native thread的信息泥耀,無法創(chuàng)建新的線程,說明是在擴(kuò)展棧的時(shí)候產(chǎn)生的內(nèi)存溢出異常蛔添。

總結(jié):在線程較少的時(shí)候痰催,某個(gè)線程請求深度過大,會(huì)報(bào)StackOverflow異常迎瞧,解決這種問題可以適當(dāng)加大棧的深度(增加椏淙埽空間大小)凶硅,也就是把-Xss的值設(shè)置大一些缝裁,但一般情況下是代碼問題的可能性較大;在虛擬機(jī)產(chǎn)生線程時(shí)足绅,無法為該線程申請椊莅螅空間了韩脑,會(huì)報(bào)OutOfMemoryError異常,解決這種問題可以適當(dāng)減小棧的深度胎食,也就是把-Xss的值設(shè)置小一些扰才,每個(gè)線程占用的空間小了,總空間一定就能容納更多的線程厕怜,但是操作系統(tǒng)對一個(gè)進(jìn)程的線程數(shù)有限制衩匣,經(jīng)驗(yàn)值在3000~5000左右。在jdk1.5之前-Xss默認(rèn)是256k粥航,jdk1.5之后默認(rèn)是1M琅捏,這個(gè)選項(xiàng)對系統(tǒng)硬性還是蠻大的,設(shè)置時(shí)要根據(jù)實(shí)際情況递雀,謹(jǐn)慎操作柄延。


3,方法區(qū)溢出

前面說到缀程,方法區(qū)主要用于存儲(chǔ)虛擬機(jī)加載的類信息搜吧、常量、靜態(tài)變量杨凑,以及編譯器編譯后的代碼等數(shù)據(jù)滤奈,所以方法區(qū)溢出的原因就是沒有足夠的內(nèi)存來存放這些數(shù)據(jù)。

由于在jdk1.6之前字符串常量池是存在于方法區(qū)中的撩满,所以基于jdk1.6之前的虛擬機(jī)蜒程,可以通過不斷產(chǎn)生不一致的字符串(同時(shí)要保證和GC Roots之間保證有可達(dá)路徑)來模擬方法區(qū)的OutOfMemoryError異常;但方法區(qū)還存儲(chǔ)加載的類信息伺帘,所以基于jdk1.7的虛擬機(jī)昭躺,可以通過動(dòng)態(tài)不斷創(chuàng)建大量的類來模擬方法區(qū)溢出。

/**

?*設(shè)置方法區(qū)最大伪嫁、最小空間:-XX:PermSize=10m -XX:MaxPermSize=10m

?*運(yùn)行時(shí)领炫,通過cglib不斷創(chuàng)建JavaMethodAreaOOM的子類,方法區(qū)中類信息越來越多张咳,最終沒有可以為新的類分配的內(nèi)存導(dǎo)致內(nèi)存溢出

?*/

public class JavaMethodAreaOOM {

??? public static voidmain(final String[] args){

?????? try {

?????????? while (true){

?????????????? Enhancerenhancer=new Enhancer();

??????????????enhancer.setSuperclass(JavaMethodAreaOOM.class);

??????????????enhancer.setUseCache(false);

?????????????? enhancer.setCallback(newMethodInterceptor() {

?????????????????? @Override

?????????????????? publicObject intercept(Object o, Method method, Object[] objects, MethodProxymethodProxy) throws Throwable {

??????????????????????return methodProxy.invokeSuper(o,objects);

?????????????????? }

?????????????? });

??????????????enhancer.create();

?????????? }

?????? }catch (Throwable t){

??????????t.printStackTrace();

?????? }

??? }

}

上述代碼運(yùn)行后會(huì)報(bào)“java.lang.OutOfMemoryError: PermGen space”的異常驹吮,說明是在方法區(qū)出現(xiàn)了內(nèi)存溢出的錯(cuò)誤。


4晶伦,Metaspace內(nèi)存溢出

問題描述:

元空間的溢出碟狞,系統(tǒng)會(huì)拋出java.lang.OutOfMemoryError: Metaspace。出現(xiàn)這個(gè)異常的問題的原因是系統(tǒng)的代碼非常多或引用的第三方包非常多或者通過動(dòng)態(tài)代碼生成類加載等方法婚陪,導(dǎo)致元空間的內(nèi)存占用很大族沃。

以下是用循環(huán)動(dòng)態(tài)生成class的方式來模擬元空間的內(nèi)存溢出的。

解決方法:???

默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制脆淹。但是為了整機(jī)的性能常空,盡量還是要對該項(xiàng)進(jìn)行設(shè)置,以免造成整機(jī)的服務(wù)停機(jī)盖溺。

? 1)優(yōu)化參數(shù)配置漓糙,避免影響其他JVM進(jìn)程

-XX:MetaspaceSize,初始空間大小烘嘱,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載昆禽,同時(shí)GC會(huì)對該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值蝇庭;如果釋放了很少的空間醉鳖,那么在不超過MaxMetaspaceSize時(shí),適當(dāng)提高該值哮内。

-XX:MaxMetaspaceSize盗棵,最大空間,默認(rèn)是沒有限制的北发。

除了上面兩個(gè)指定大小的選項(xiàng)以外纹因,還有兩個(gè)與 GC 相關(guān)的屬性:

-XX:MinMetaspaceFreeRatio,在GC之后琳拨,最小的Metaspace剩余空間容量的百分比瞭恰,減少為分配空間所導(dǎo)致的垃圾收集 。

-XX:MaxMetaspaceFreeRatio从绘,在GC之后寄疏,最大的Metaspace剩余空間容量的百分比是牢,減少為釋放空間所導(dǎo)致的垃圾收集僵井。

2)慎重引用第三方包

對第三方包,一定要慎重選擇驳棱,不需要的包就去掉批什。這樣既有助于提高編譯打包的速度,也有助于提高遠(yuǎn)程部署的速度社搅。

3)關(guān)注動(dòng)態(tài)生成類的框架

對于使用大量動(dòng)態(tài)生成類的框架驻债,要做好壓力測試,驗(yàn)證動(dòng)態(tài)生成的類是否超出內(nèi)存的需求會(huì)拋出異常形葬。


5合呐,本機(jī)直接內(nèi)存溢出

本機(jī)直接內(nèi)存(DirectMemory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域笙以,但Java中用到NIO相關(guān)操作時(shí)(比如ByteBuffer的allocteDirect方法申請的是本機(jī)直接內(nèi)存)淌实,也可能會(huì)出現(xiàn)java.lang.OutOfMemoryError:Direct buffer memory異常。

如果你在直接或間接使用了ByteBuffer中的allocateDirect方法的時(shí)候,而不做clear的時(shí)候就會(huì)出現(xiàn)類似的問題拆祈。

解決方法:如果經(jīng)常有類似的操作恨闪,可以考慮設(shè)置參數(shù):-XX:MaxDirectMemorySize,并及時(shí)clear內(nèi)存放坏。


6咙咽,棧內(nèi)存溢出

問題描述

當(dāng)一個(gè)線程執(zhí)行一個(gè)Java方法時(shí),JVM將創(chuàng)建一個(gè)新的棧幀并且把它push到棧頂淤年。此時(shí)新的棧幀就變成了當(dāng)前棧幀钧敞,方法執(zhí)行時(shí),使用棧幀來存儲(chǔ)參數(shù)互亮、局部變量犁享、中間指令以及其他數(shù)據(jù)。

當(dāng)一個(gè)方法遞歸調(diào)用自己時(shí)豹休,新的方法所產(chǎn)生的數(shù)據(jù)(也可以理解為新的棧幀)將會(huì)被push到棧頂炊昆,方法每次調(diào)用自己時(shí),會(huì)拷貝一份當(dāng)前方法的數(shù)據(jù)并push到棧中威根。因此凤巨,遞歸的每層調(diào)用都需要?jiǎng)?chuàng)建一個(gè)新的棧幀。這樣的結(jié)果是洛搀,棧中越來越多的內(nèi)存將隨著遞歸調(diào)用而被消耗敢茁,如果遞歸調(diào)用自己一百萬次,那么將會(huì)產(chǎn)生一百萬個(gè)棧幀留美。這樣就會(huì)造成棧的內(nèi)存溢出StackOverflowError彰檬。

解決方法:

如果程序中確實(shí)有遞歸調(diào)用,出現(xiàn)棧溢出時(shí)谎砾,可以調(diào)高-Xss大小逢倍,就可以解決棧內(nèi)存溢出的問題了。遞歸調(diào)用防止形成死循環(huán)景图,否則就會(huì)出現(xiàn)棧內(nèi)存溢出较雕。


Full GC分析定位過程:

1,如何發(fā)現(xiàn)是否發(fā)生FULL GC和FULL GC是否頻繁

使用JDK自帶的輕量級小工具jstat

???? 語法結(jié)構(gòu):

Usage: jstat -help|-options

???????????? jstat-? [-t] [-h] [[]]

參數(shù)解釋:

Options — 選項(xiàng)挚币,我們一般使用 -gcutil 查看gc情況

vmid??? — VM的進(jìn)程號亮蒋,即當(dāng)前運(yùn)行的java進(jìn)程號

interval– 間隔時(shí)間,單位為秒或者毫秒

count?? —打印次數(shù)妆毕,如果缺省則打印無數(shù)次

比如/opt/taobao/java/bin/jstat –gcutil pid 5000

輸出結(jié)果:

S0??? S1???????? E????????? O????????? P??????? YGC?????YGCT??? FGC???? FGCT????GCT

0.00? 90.63????? 100.00???58.82????? 3.51???? 183?????2.059???? 0???? 0.000???2.059

0.00? 15.48????? 7.80?????60.99????? 3.51???? 185?????2.092???? 1???? 0.305???2.397

0.00? 15.48????? 18.10????47.90????? 3.51???? 185?????2.092???? 2???? 0.348???2.440

S0? — Heap上的 Survivor space 0 區(qū)已使用空間的百分比

S1? — Heap上的 Survivor space 1 區(qū)已使用空間的百分比

E?? — Heap上的 Eden space 區(qū)已使用空間的百分比

O?? — Heap上的 Old space 區(qū)已使用空間的百分比

P?? — Perm space區(qū)已使用空間的百分比

YGC — 從應(yīng)用程序啟動(dòng)到采樣時(shí)發(fā)生 Young GC 的次數(shù)

YGCT– 從應(yīng)用程序啟動(dòng)到采樣時(shí) Young GC 所用的時(shí)間(單位秒)

FGC — 從應(yīng)用程序啟動(dòng)到采樣時(shí)發(fā)生 Full GC 的次數(shù)

FGCT– 從應(yīng)用程序啟動(dòng)到采樣時(shí) Full GC 所用的時(shí)間(單位秒)

GCT — 從應(yīng)用程序啟動(dòng)到采樣時(shí)用于垃圾回收的總時(shí)間(單位秒)

通過FGC我們可以發(fā)現(xiàn)系統(tǒng)是否發(fā)生FULL GC和FULL GC的頻率

2慎玖,F(xiàn)ULL GC分析和問題定位

a. GC log收集和分析

(1)在JVM啟動(dòng)參數(shù)增加:"-verbose:gc-Xloggc:?-XX:+PrintGCDetails -XX:+PrintGCDateStamps"

PrintGCTimeStamp只能獲得相對時(shí)間,建議使用PrintGCDateStamps獲得full gc 發(fā)生的絕對時(shí)間

(2)如果采用CMS GC,仔細(xì)分析jstat FGC輸出和GC 日志會(huì)發(fā)現(xiàn)笛粘, CMS的每個(gè)并發(fā)GC周期則有兩個(gè)stop-the-world階段——initial mark與final re-mark趁怔,使得CMS的每個(gè)并發(fā)GC周期總共會(huì)更新full GC計(jì)數(shù)器兩次远舅,initial mark與final re-mark各一次

b. Dump JVM 內(nèi)存快照

/opt/taobao/java/bin/jmap -dump:format=b,file=dump.bin pid

這里有一個(gè)問題是什么時(shí)候進(jìn)行dump?

一種方法是前面提到的用jstat工具觀察,當(dāng)OLD區(qū)到達(dá)比較高的比例如60%痕钢,一般會(huì)很快觸發(fā)一次FULL GC,可以進(jìn)行一次DUMP,在FULL GC發(fā)生以后再DUMP一次图柏,這樣比較就可以發(fā)現(xiàn)到底是哪些對象導(dǎo)致不停的FULL GC

另外一種方法是通過配置JVM參數(shù)

-XX:+HeapDumpBeforeFullGC -XX:+HeapDumpAfterFullGC分別用于指定在full GC之前與之后生成heap dump

c. 利用MAT((Memory

Analyzer Tool)工具分析dump文件

關(guān)于MAT具體使用方法網(wǎng)上有很多介紹,這里不做詳細(xì)展開任连,這里需要注意的是:

(1)?? MAT缺省只分析reachable的對象蚤吹,unreachable的對象(將被收集掉的對象)被忽略,而分析FULL GC頻繁原因時(shí)unreachable

object也應(yīng)該同時(shí)被重點(diǎn)關(guān)注随抠。如果要顯示unreachable的對象細(xì)節(jié)必須用mat 1.1以上版本并且打開選項(xiàng)“keep unreachable object”

(2)?? 通常dump文件會(huì)好幾個(gè)G裁着,無法在windows上直接進(jìn)行分析,我們可以先把dump文件在linux上進(jìn)行分析拱她,再把分析好的文件拷貝到windows上二驰,在windows上用MAT打開分析文件。


內(nèi)存泄露主要有如下幾大類

1)靜態(tài)集合類引起內(nèi)存泄漏:

像HashMap秉沼、Vector等的使用最容易出現(xiàn)內(nèi)存泄露桶雀,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對象Object也不能被釋放唬复,因?yàn)樗麄円矊⒁恢北籚ector等引用著矗积。

static Vector v = new Vector();

for (int i = 1; i<100; i++)

{

??? Object o = new Object();

??? v.add(o);

??? o = null;

}

在這個(gè)例子中,代碼棧中存在Vector 對象的引用 v 和 Object 對象的引用 o 敞咧。在 For 循環(huán)棘捣,我們不斷的生成新的對象,然后將其添加到 Vector 對象中休建,之后將 o 引用置空乍恐。問題是當(dāng) o 引用被置空后,如果發(fā)生 GC测砂,我們創(chuàng)建的 Object 對象是否能夠被 GC 回收呢茵烈?答案是否定的。因?yàn)椋?GC 在跟蹤代碼棧中的引用時(shí)邑彪,會(huì)發(fā)現(xiàn) v 引用瞧毙,而繼續(xù)往下跟蹤胧华,就會(huì)發(fā)現(xiàn) v 引用指向的內(nèi)存空間中又存在指向 Object 對象的引用寄症。也就是說盡管o 引用已經(jīng)被置空,但是 Object 對象仍然存在其他的引用矩动,是可以被訪問到的有巧,所以 GC 無法將其釋放掉。如果在此循環(huán)之后悲没, Object 對象對程序已經(jīng)沒有任何作用篮迎,那么我們就認(rèn)為此 Java 程序發(fā)生了內(nèi)存泄漏。


2)當(dāng)集合里面的對象屬性被修改后,再調(diào)用remove()方法時(shí)不起作用甜橱。

public static void main(String[] args)

{

??? Set set =new HashSet();

??? Person p1 = newPerson("唐僧","pwd1",25);

??? Person p2 = newPerson("孫悟空","pwd2",26);

??? Person p3 = newPerson("豬八戒","pwd3",27);

??? set.add(p1);

??? set.add(p2);

??? set.add(p3);

???System.out.println("總共有:"+set.size()+"個(gè)元素!"); //結(jié)果:總共有:3 個(gè)元素!

??? p3.setAge(2); //修改p3的年齡,此時(shí)p3元素對應(yīng)的hashcode值發(fā)生改變

??? set.remove(p3); //此時(shí)remove不掉逊笆,造成內(nèi)存泄漏

??? set.add(p3); //重新添加,居然添加成功

???System.out.println("總共有:"+set.size()+"個(gè)元素!"); //結(jié)果:總共有:4 個(gè)元素!

??? for (Person person :set)

??? {

???????System.out.println(person);

??? }

}


3)各種連接

比如數(shù)據(jù)庫連接(dataSourse.getConnection())岂傲,網(wǎng)絡(luò)連接(socket)和io連接难裆,除非其顯式的調(diào)用了其close()方法將其連接關(guān)閉,否則是不會(huì)自動(dòng)被GC回收的镊掖。對于Resultset和Statement 對象可以不進(jìn)行顯式回收乃戈,但Connection一定要顯式回收,因?yàn)镃onnection 在任何時(shí)候都無法自動(dòng)回收亩进,而Connection一旦回收症虑,Resultset和Statement 對象就會(huì)立即為null。但是如果使用連接池归薛,情況就不一樣了谍憔,除了要顯式地關(guān)閉連接,還必須顯式地關(guān)閉Resultset Statement 對象(關(guān)閉其中一個(gè)主籍,另外一個(gè)也會(huì)關(guān)閉)韵卤,否則就會(huì)造成大量的Statement 對象無法釋放,從而引起內(nèi)存泄漏崇猫。這種情況下一般都會(huì)在try里面去的連接沈条,在finally里面釋放連接。


4)內(nèi)部類和外部模塊的引用

內(nèi)部類的引用是比較容易遺忘的一種诅炉,而且一旦沒釋放可能導(dǎo)致一系列的后繼類對象沒有釋放蜡歹。此外程序員還要小心外部模塊不經(jīng)意的引用,例如程序員A負(fù)責(zé)A模塊涕烧,調(diào)用了B模塊的一個(gè)方法如:

public void registerMsg(Object b);

這種調(diào)用就要非常小心了月而,傳入了一個(gè)對象,很可能模塊B就保持了對該對象的引用议纯,這時(shí)候就需要注意模塊B 是否提供相應(yīng)的操作去除引用父款。


5)單例模式

不正確使用單例模式是引起內(nèi)存泄漏的一個(gè)常見問題,單例對象在初始化后將在JVM的整個(gè)生命周期中存在(以靜態(tài)變量的方式)瞻凤,如果單例對象持有外部的引用憨攒,那么這個(gè)對象將不能被JVM正常回收阀参,導(dǎo)致內(nèi)存泄漏肝集,考慮下面的例子:

class A{

??? public A(){

??? ???????B.getInstance().setA(this);

??? }

??? ....

}

//B類采用單例模式

class B{

??? private A a;

??? private static Binstance=new B();

??? public B(){}

??? public static BgetInstance(){

??? ???????return instance;

??? }

??? public void setA(A a){

?????? ????this.a=a;

??? }

??? //getter...

}

顯然B采用singleton模式,它持有一個(gè)A對象的引用蛛壳,而這個(gè)A類的對象將不能被回收杏瞻。


6)監(jiān)聽器

在java編程中所刀,我們都需要和監(jiān)聽器打交道,通常一個(gè)應(yīng)用當(dāng)中會(huì)用到很多監(jiān)聽器捞挥,我們會(huì)調(diào)用一個(gè)控件的諸如addXXXListener()等方法來增加監(jiān)聽器浮创,但往往在釋放對象的時(shí)候卻沒有記住去刪除這些監(jiān)聽器,從而增加了內(nèi)存泄漏的機(jī)會(huì)砌函。


ThreadLocal 內(nèi)存泄漏問題

ThreadLocal的實(shí)現(xiàn)是這樣的:每個(gè)Thread 維護(hù)一個(gè) ThreadLocalMap映射表蒸矛,這個(gè)映射表的 key 是 ThreadLocal實(shí)例本身,value 是真正需要存儲(chǔ)的 Object胸嘴。

也就是說 ThreadLocal 本身并不存儲(chǔ)值雏掠,它只是作為一個(gè) key 來讓線程從 ThreadLocalMap獲取 value。值得注意的是圖中的虛線劣像,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作為 Key 的乡话,弱引用的對象在 GC 時(shí)會(huì)被回收。

ThreadLocal為什么會(huì)內(nèi)存泄漏

ThreadLocalMap使用ThreadLocal的弱引用作為key耳奕,通常弱引用都會(huì)和引用隊(duì)列配合清理機(jī)制使用绑青,但是ThreadLocalMap是個(gè)例外,它并沒有這么做屋群。這意味著闸婴,廢棄項(xiàng)目的回收依賴于顯式的觸發(fā),否則就要等線程結(jié)束芍躏,今兒回收相應(yīng)的ThreadLocalMap邪乍。如果一個(gè)ThreadLocal沒有外部強(qiáng)引用來引用它,那么系統(tǒng) GC 的時(shí)候对竣,這個(gè)ThreadLocal勢必會(huì)被回收庇楞,這樣一來,ThreadLocalMap中就會(huì)出現(xiàn)key為null的Entry否纬,就沒有辦法訪問這些key為null的Entry的value吕晌,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠(yuǎn)無法回收临燃,造成內(nèi)存泄漏睛驳。

其實(shí),ThreadLocalMap的設(shè)計(jì)中已經(jīng)考慮到這種情況膜廊,也加上了一些防護(hù)措施:在ThreadLocal的get(),set(),remove()的時(shí)候都會(huì)清除線程ThreadLocalMap里所有key為null的value乏沸。

但是這些被動(dòng)的預(yù)防措施并不能保證不會(huì)內(nèi)存泄漏:

使用static的ThreadLocal,延長了ThreadLocal的生命周期溃论,可能導(dǎo)致的內(nèi)存泄漏(參考ThreadLocal 內(nèi)存泄露的實(shí)例分析)屎蜓。

分配使用了ThreadLocal又不再調(diào)用get(),set(),remove()方法痘昌,那么就會(huì)導(dǎo)致內(nèi)存泄漏钥勋。

綜合上面的分析炬转,我們可以理解ThreadLocal內(nèi)存泄漏的前因后果,那么怎么避免內(nèi)存泄漏呢算灸?

每次使用完ThreadLocal扼劈,都調(diào)用它的remove()方法,清除數(shù)據(jù)菲驴。

在使用線程池的情況下荐吵,沒有及時(shí)清理ThreadLocal,不僅是內(nèi)存泄漏的問題赊瞬,更嚴(yán)重的是可能導(dǎo)致業(yè)務(wù)邏輯出現(xiàn)問題先煎。所以,使用ThreadLocal就跟加鎖完要解鎖一樣巧涧,用完就清理薯蝎。


參考書目:《深入理解JVM虛擬機(jī)》、《Java性能調(diào)優(yōu)指南》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谤绳,一起剝皮案震驚了整個(gè)濱河市占锯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缩筛,老刑警劉巖消略,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瞎抛,居然都是意外死亡艺演,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門桐臊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钞艇,“玉大人,你說我怎么就攤上這事豪硅×ㄕ眨” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵懒浮,是天一觀的道長飘弧。 經(jīng)常有香客問我,道長砚著,這世上最難降的妖魔是什么次伶? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮稽穆,結(jié)果婚禮上冠王,老公的妹妹穿的比我還像新娘。我一直安慰自己舌镶,他們只是感情好柱彻,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布豪娜。 她就那樣靜靜地躺著,像睡著了一般哟楷。 火紅的嫁衣襯著肌膚如雪瘤载。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天卖擅,我揣著相機(jī)與錄音鸣奔,去河邊找鬼。 笑死惩阶,一個(gè)胖子當(dāng)著我的面吹牛挎狸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播断楷,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤坯台,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冕象,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年墓律,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了针肥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片即纲。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掘猿,死狀恐怖轻绞,靈堂內(nèi)的尸體忽然破棺而出奸远,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站猎莲,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏益眉。R本人自食惡果不足惜晌柬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望郭脂。 院中可真熱鬧年碘,春花似錦、人聲如沸展鸡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莹弊。三九已至涤久,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忍弛,已是汗流浹背响迂。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留细疚,地道東北人蔗彤。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親然遏。 傳聞我的和親對象是個(gè)殘疾皇子贫途,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方待侵,同時(shí)不同JDK版本的...
    高廣超閱讀 15,545評論 3 83
  • 介紹JVM中7個(gè)區(qū)域丢早,然后把每個(gè)區(qū)域可能造成內(nèi)存的溢出的情況說明 程序計(jì)數(shù)器:看做當(dāng)前線程所執(zhí)行的字節(jié)碼行號指示器...
    jemmm閱讀 2,223評論 0 9
  • 第二部分 自動(dòng)內(nèi)存管理機(jī)制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運(yùn)行數(shù)據(jù)區(qū)域 程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,130評論 0 2
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    _痞子閱讀 1,625評論 0 8
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight閱讀 1,412評論 1 0