程序小白在寫代碼的過程中,經(jīng)常會不經(jīng)意間寫出發(fā)生內(nèi)存溢出異常的代碼。很多時候這類異常如何產(chǎn)生的都傻傻弄不清楚,如果能故意寫出讓jvm發(fā)生內(nèi)存溢出的代碼,有時候看來也并非一件容易的事。最近通過學(xué)習(xí)《深入理解java虛擬機-JVM高級特性與最佳實踐》這本書,終于初步了解了一下java虛擬機的內(nèi)存模型。本文通過寫出使jvm發(fā)生內(nèi)存溢出異常的代碼來對自己的學(xué)習(xí)結(jié)果進行總結(jié),同時也提醒自己以后寫代碼時候不要再跳進這個坑啦。
java的內(nèi)存管理是由java虛擬機自動進行管理的,并不需要程序員過多的手動干預(yù),這也就導(dǎo)致了初學(xué)java的人在不了解java內(nèi)存模型的情況下也能愉快的進行coding。不過一旦涉及了內(nèi)存泄露或者內(nèi)存溢出以及垃圾回收(GC)方面的問題,如果不了解虛擬機是怎么管理內(nèi)存的,那么排查問題,定位錯誤地點就顯得無從下手了。首先上圖看一下java虛擬機運行時數(shù)據(jù)區(qū)是什么樣子(圖片來源于網(wǎng)絡(luò)):
從上圖我們可以獲得以下信息:
jvm內(nèi)存分區(qū)可以分為所有線程共享數(shù)據(jù)區(qū)以及線程隔離數(shù)據(jù)區(qū)兩部分。這也就是說圖中的方法區(qū)和堆是由所有的線程共同使用的,而虛擬機棧、本地方法棧、程序計數(shù)器則是每個線程獨有各自的響應(yīng)數(shù)據(jù)區(qū),各線程之間是互不干擾的。
jvm運行時數(shù)據(jù)區(qū)按照功能可分為方法區(qū),堆,虛擬機棧,本地方法棧,程序計數(shù)器五個部分。各個部分存儲的數(shù)據(jù)類型不同。
本文主要講述如何讓JVM發(fā)生內(nèi)存溢出異常,有關(guān)JVM內(nèi)存模型將會在另一篇文章中詳細講解,這里簡單介紹各分區(qū)的作用:
堆:各線程共享;存放java對象實例,以及為數(shù)組分配的空間也在此處
虛擬機棧:線程私有;生命周期與線程相同,描述java方法執(zhí)行的內(nèi)存模型,每個方法在執(zhí)行時創(chuàng)建棧幀,棧幀存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、返回地址(方法出口)等信息
本地方法棧:和虛擬機棧功能類似,線程私有;為虛擬機使用的Native方法提供服務(wù)
方法區(qū):各線程共享;存儲已被虛擬機加載的類的信息、final聲明的常量,static修飾的靜態(tài)變量,以及編譯器編譯后的java代碼等數(shù)據(jù)(jdk7以后把常量池移到了堆中)
程序計數(shù)器:線程私有;可以看做是當(dāng)前線程所執(zhí)行程序的字節(jié)碼的行號指示器
在jvm規(guī)范中,除了程序計數(shù)器內(nèi)存區(qū)域沒有規(guī)定任何內(nèi)存溢出異常情形外,其他四個內(nèi)存區(qū)域都會有相應(yīng)的內(nèi)存溢出異常發(fā)生的可能,所以jvm內(nèi)存溢出異常發(fā)生在不同的內(nèi)存區(qū)域具有不同的異常發(fā)生原因,知道一內(nèi)存異常產(chǎn)生的位置,對于定位錯誤地點就很有指向性了。
下面就通過實例來展示,如何通過代碼指定讓不同的內(nèi)存區(qū)域發(fā)生內(nèi)存溢出異常。
一、java堆發(fā)生內(nèi)存溢出:
java堆是用來存儲對象實例以及數(shù)組的,使java堆發(fā)生內(nèi)存溢出的要旨是:
不斷創(chuàng)建對象
保證對象存活,不會被垃圾收集器回收
java虛擬機的內(nèi)存大小是可以人為設(shè)置的,通過設(shè)置限制內(nèi)存大小,可以很方便的實現(xiàn)內(nèi)存溢出,節(jié)約了時間。
設(shè)置java堆內(nèi)存大小的虛擬機參數(shù)為:-Xms堆的初始大小 -Xmx堆可擴展的最大值
延伸閱讀
學(xué)習(xí)是年輕人改變自己的最好方式