写java线程导致电脑内存不足_如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码...
程序小白在寫代碼的過程中,經常會不經意間寫出發生內存溢出異常的代碼。很多時候這類異常如何產生的都傻傻弄不清楚,如果能故意寫出讓jvm發生內存溢出的代碼,有時候看來也并非一件容易的事。最近通過學習《深入理解java虛擬機-JVM高級特性與最佳實踐》這本書,終于初步了解了一下java虛擬機的內存模型。本文通過寫出使jvm發生內存溢出異常的代碼來對自己的學習結果進行總結,同時也提醒自己以后寫代碼時候不要再跳進這個坑啦。
java的內存管理是由java虛擬機自動進行管理的,并不需要程序員過多的手動干預,這也就導致了初學java的人在不了解java內存模型的情況下也能愉快的進行coding。不過一旦涉及了內存泄露或者內存溢出以及垃圾回收(GC)方面的問題,如果不了解虛擬機是怎么管理內存的,那么排查問題,定位錯誤地點就顯得無從下手了。首先上圖看一下java虛擬機運行時數據區是什么樣子(圖片來源于網絡):
從上圖我們可以獲得以下信息:
jvm內存分區可以分為所有線程共享數據區以及線程隔離數據區兩部分。這也就是說圖中的方法區和堆是由所有的線程共同使用的,而虛擬機棧、本地方法棧、程序計數器則是每個線程獨有各自的響應數據區,各線程之間是互不干擾的。
jvm運行時數據區按照功能可分為方法區,堆,虛擬機棧,本地方法棧,程序計數器五個部分。各個部分存儲的數據類型不同。
本文主要講述如何讓JVM發生內存溢出異常,有關JVM內存模型將會在另一篇文章中詳細講解,這里簡單介紹各分區的作用:
堆:各線程共享;存放java對象實例,以及為數組分配的空間也在此處
虛擬機棧:線程私有;生命周期與線程相同,描述java方法執行的內存模型,每個方法在執行時創建棧幀,棧幀存儲局部變量表、操作數棧、動態鏈接、返回地址(方法出口)等信息
本地方法棧:和虛擬機棧功能類似,線程私有;為虛擬機使用的Native方法提供服務
方法區:各線程共享;存儲已被虛擬機加載的類的信息、final聲明的常量,static修飾的靜態變量,以及編譯器編譯后的java代碼等數據(jdk7以后把常量池移到了堆中)
程序計數器:線程私有;可以看做是當前線程所執行程序的字節碼的行號指示器
在jvm規范中,除了程序計數器內存區域沒有規定任何內存溢出異常情形外,其他四個內存區域都會有相應的內存溢出異常發生的可能,所以jvm內存溢出異常發生在不同的內存區域具有不同的異常發生原因,知道一內存異常產生的位置,對于定位錯誤地點就很有指向性了。
下面就通過實例來展示,如何通過代碼指定讓不同的內存區域發生內存溢出異常。
一、java堆發生內存溢出:
java堆是用來存儲對象實例以及數組的,使java堆發生內存溢出的要旨是:
不斷創建對象
保證對象存活,不會被垃圾收集器回收
java虛擬機的內存大小是可以人為設置的,通過設置限制內存大小,可以很方便的實現內存溢出,節約了時間。
設置java堆內存大小的虛擬機參數為:-Xms堆的初始大小 -Xmx堆可擴展的最大值
1 packageText.JVM;2
3 importjava.util.ArrayList;4 importjava.util.List;5
6 /**7 * JVM堆中存儲java對象實例和數組
8 * VM Args: -Xms20m -Xmx20m (限制堆的大小不可擴展)
9 *@authorAdministrator
10 *
11*/
12 public classHeapOutOfMemoryError {13
14 public static classOOMObject {15}16 public static voidmain(String[] args) {17 List list=new ArrayList<>();18 //不斷創建對象,并保證GC Roots到對象之間有可達路徑,避免垃圾回收清除創建的對象
19 while (true) {20 list.add(newOOMObject());21System.out.println(System.currentTimeMillis());22}23}24
25 }
View Code
jvm虛擬機啟動參數設置:
運行結果:
Exception in thread "main"java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2245)
at java.util.Arrays.copyOf(Arrays.java:2219)
at java.util.ArrayList.grow(ArrayList.java:242)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
at java.util.ArrayList.add(ArrayList.java:440)
at Text.JVM.HeapOutOfMemoryError.main(HeapOutOfMemoryError.java:20)
View Code
注意運行結果第一行末尾:java.lang.OutOfMemoryError:Java heap space 。java heap space明確的指出了異常發生的區域:堆。
二、虛擬機棧發生內存溢出異常:
能夠使虛擬機棧發生內存溢出異常的情形有兩種:
線程請求的棧深度超過虛擬機所允許的最大深度,將拋出StackOverflowError異常。
虛擬機在擴展棧時無法申請到足夠的內存空間,將拋出OutOfMemoryError異常。
使java虛擬機棧發生內存溢出異常的要旨:
對應情形1,使用不合理的遞歸
對應情形2,不斷創建活躍的線程
使用遞歸導致虛擬機棧內存溢出異常的示例:
1 packageText.JVM;2
3
4 /*5 * 線程請求的棧深度超過虛擬機所允許的最大深度,將拋出StackOverflowError異常
6 * 最常見引起此類異常的情形時使用不合理的遞歸調用
7 * VM Args:-Xss256k
8 *
9 * @author Administrator
10 *
11*/
12 public classStackOverflowError {13
14 //記錄內存溢出時的棧深度
15 private int stackLength = 1;16
17 //遞歸調用的方法
18 public voidstackLeak() {19 stackLength++;20stackLeak();21}22
23 public static voidmain(String[] args) {24
25 StackOverflowError oomError = newStackOverflowError();26 try{27oomError.stackLeak();28 } catch(Throwable e) {29 System.out.println("棧深度為:" +oomError.stackLength);30 throwe;31}32}33
34 }
View Code
程序運行結果:
棧深度為:2491Exception in thread"main"java.lang.StackOverflowError
at Text.JVM.StackOverflowError.stackLeak(StackOverflowError.java:19)
at Text.JVM.StackOverflowError.stackLeak(StackOverflowError.java:20)
at Text.JVM.StackOverflowError.stackLeak(StackOverflowError.java:20)
at Text.JVM.StackOverflowError.stackLeak(StackOverflowError.java:20)
......
View Code
異常信息中的:java.lang.StackOverflowError表明了內存溢出區域為虛擬機棧。
通過創建線程導致虛擬機棧內存溢出異常的示例:
1 packageText.JVM;2
3 /*4 * 通過不斷創建活躍線程,消耗虛擬機棧資源
5 * VM Args:-Xss256k
6*/
7 public classStackOutOfMemoryError {8
9 //線程任務,每個線程任務一直在運行
10 private voidwontStop() {11 while (true) {12System.out.println(System.currentTimeMillis());13}14}15
16 //不斷地創建線程
17 public voidstackLeadByThread() {18 while (true) {19 Thread thread = new Thread(newRunnable() {20
21@Override22 public voidrun() {23wontStop();24}25});26thread.start();27}28}29
30 public static voidmain(String[] args) {31 StackOutOfMemoryError oomError=newStackOutOfMemoryError();32oomError.stackLeadByThread();33}34
35 }
View Code
理論上本段代碼的運行結果應該是:?Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread?,但是由于windows平臺虛擬機java線程映射到操作系統的內核線程上,執行上述代碼有較大風險可能會導致操作系統假死(我運行完是真的死了),所以運行需謹慎!!!(寫了這么長的博客有一半沒保存,重啟后丟了這我會亂說???都是淚啊)。
三、方法區和運行時常量池內存溢出異常
方法區的作用是存儲 Java 類的結構信息,當我們創建對象實例后,對象的類型信息存儲在方法區之中,實例數據存放在堆中;實例數據指的是在 Java 中創建的各種實例對象以及它們的值,類型信息指的是定義在 Java 代碼中的常量、靜態變量、以及在類中聲明的各種方法、方法字段等等;同時可能包括即時編譯器編譯后產生的代碼數據。通過在運行時產生大量的類,或者工程本身具有大量的類,而方法區分配的空間不足以容納如此多的類信息的時候就會產生方法區內存溢出異常。
運行時常量池是方法取得一部分,程序中使用到的String類型字面量以及基本數據類型的一部分數據會存儲在常量池中。我們使用String.intern()方法來測試,是運行時常量池發生內存溢出。此方法的作用是:如果字符串常量池中不包含一個等于此String對象的字符串,則將此對象包含的字符串添加到常量池中,并返回此對象的引用。
使方法區發生內存溢出的要旨:
程序運行時動態創建的大量類,導致方法區內存空間不足
程序中存有大量字面量等數據導致常量區內存不足
常量池內存溢出示例代碼(僅在jdk6之前的版本中有效):
1 packageText.JVM;2
3 importjava.util.ArrayList;4 importjava.util.List;5
6 /*7 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M(限制常量池容量)
8 *
9*/
10 public classRunTimeConstantPoolOutOfMemoryError {11
12 public static voidmain(String[] args) {13 //使用list保持常量池引用,避免常量池內的數據被垃圾回收清除
14 List list = new ArrayList<>();15 long i = 0;16 while (true) {17 String string = (i++) + "";18list.add(string.intern());19}20}21
22 }
View Code
據說此段代碼在jdk6之前的版本中運行時會產生:Exception in thread "main" java.lang.OutOfMemoryError: PermGen space 其中PermGen space指示內存溢出發生在運行時常量池中。
但是,我在jdk7的環境中運行得到的結果卻是:?Exception in thread "main" java.lang.OutOfMemoryError: Java heap space? 指示內存溢出發生在堆中而不是方法區中的常量池!!!都說實踐是檢驗真理的唯一標準,果真是沒錯的。因為在 JDK1.2 ~ JDK6 的實現中,HotSpot 使用永久代實現方法區,而從 JDK7 開始 Oracle HotSpot 開始移除永久代,JDK7中符號表被移動到 Native Heap中,字符串常量和類引用被移動到 Java Heap中。在 JDK8 中,永久代已完全被元空間(Meatspace)所取代。關于常量池的存放位置還有待進一步研究,不過上段代碼是可以引起常量池的內存溢出的。
通過運行時動態產生大量的類產生方法區內存溢出示例這里就不提供了,書中提供了使用CGLib使方法區出現內存異常的示例:
1 /**2 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
3 *@authorzzm
4*/
5 public classJavaMethodAreaOOM {6
7 public static voidmain(String[] args) {8 while (true) {9 Enhancer enhancer = newEnhancer();10 enhancer.setSuperclass(OOMObject.class);11 enhancer.setUseCache(false);12 enhancer.setCallback(newMethodInterceptor() {13 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throwsThrowable {14 returnproxy.invokeSuper(obj, args);15}16});17enhancer.create();18}19}20
21 static classOOMObject {22
23}24 }
View Code
運行結果:
Caused by: java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
...8 more
View Code
我并沒有運行過這段代碼,因為我本人對于CGLib并不了解,這里只要知道發生內存溢出時顯示:PermGen space那就是方法區內存溢出準沒錯啦。
四、直接內存不足導致內存溢出異常
直接內存不是虛擬機運行時的數據區的一部分,也不是在java虛擬機規范中定義的區域,但是這部分區域也被頻繁使用,也會導致OutOfMemoryError
直接內存應用于NIO,直接內存區域默認為對內存的最大值,通過-XX:MaxDirectMemorySize可以顯式的指定直接內存大小,如果忽略直接內存,容易使各個內存區域總和大于物理內存限制,從而導致動態擴展時出現內存溢出現象。
具體的代碼示例也就不貼了,因為平時的學習過程中還沒有使用過或者還沒有意識到自己使用過直接內存區。
(注:以上內容完全是為了記錄學習結果,所有內容皆為原創,如果覺得對您有用歡迎轉載,但請注明出處,尊重原創,如果文內有內容不對的地方還請多多指教)
總結
以上是生活随笔為你收集整理的写java线程导致电脑内存不足_如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 没看错?问界汽车海报惊现“HUAWEI”
- 下一篇: 不花钱每天能跑12公里 印度首款太阳能汽