Java實(shí)戰(zhàn)之OutOfMemoryError異常問(wèn)題及解決方法
在Java虛擬機(jī)規(guī)范的描述中,除了程序計(jì)數(shù)器外,虛擬機(jī)內(nèi)存的其他幾個(gè)運(yùn)行時(shí)區(qū)域都有發(fā)生OutOfMemoryError (下文稱(chēng)OOM)異常的可能。本篇主要結(jié)合著【深入理解Java虛擬機(jī)】一書(shū)當(dāng)中整理了本篇博客,感興趣的跟著小編一塊來(lái)學(xué)習(xí)呀!
本篇文章和上一篇寫(xiě)到的 Java內(nèi)存區(qū)域劃分 息息相關(guān),如果您對(duì)Java內(nèi)存區(qū)域劃分不是很了解,建議了解一下,不然這篇文章讀起來(lái)會(huì)很痛苦。。。
一、簡(jiǎn)言
本節(jié)內(nèi)容的目的有兩個(gè):
- 第一,
通過(guò)代碼驗(yàn)證Java虛擬機(jī)規(guī)范中描述的各個(gè)運(yùn)行時(shí)區(qū)域儲(chǔ)存的內(nèi)容; - 第二,希望讀者在工作中遇到實(shí)際的內(nèi)存溢出異常時(shí),
能根據(jù)異常的信息快速判斷是哪個(gè)區(qū)域的內(nèi)存溢出,知道怎樣的代碼可能會(huì)導(dǎo)致這些區(qū)域的內(nèi)存溢出,以及出現(xiàn)這些異常后該如何處理。
下面代碼的開(kāi)頭都注釋了執(zhí)行時(shí)所需要設(shè)置的虛擬機(jī)啟動(dòng)參數(shù)(注釋中“VM Args”后面跟著的參數(shù)),這些參數(shù)對(duì)實(shí)驗(yàn)的結(jié)果有直接影響,請(qǐng)讀者調(diào)試代碼的時(shí)候不要忽略掉。(本篇文章所有案例都采用了JDK1.8版本進(jìn)行測(cè)試)
如果讀者使用控制臺(tái)命令來(lái)執(zhí)行程序,那直接跟在Java命令之后書(shū)寫(xiě)就可以。如果讀者使用Eclipse IDE,可以在Debug/Run頁(yè)簽中的設(shè)置。
二、代碼實(shí)戰(zhàn)
1、Java堆溢出
Java堆用于儲(chǔ)存對(duì)象實(shí)例,我們只要不斷地創(chuàng)建對(duì)象,并且保證GC Roots到對(duì)象之間有可達(dá)路徑來(lái)避免垃圾回收機(jī)制清除這些對(duì)象,就會(huì)在對(duì)象數(shù)量到達(dá)最大堆的容量限制后產(chǎn)生內(nèi)存溢出異常。
將Java堆設(shè)置大小為20MB,不可擴(kuò)展(將堆的最小值-Xms參數(shù)與最大值-Xmx參數(shù)設(shè)置為一樣即可避免堆自動(dòng)擴(kuò)展),通過(guò)參數(shù)-XX:+HeapDumpOnOutOfMemoryError 可以讓虛擬機(jī)在出現(xiàn)內(nèi)存溢出異常時(shí)Dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲(chǔ)快照以便事后進(jìn)行分析(內(nèi)存堆轉(zhuǎn)儲(chǔ)快照 指的是溢出后,內(nèi)存當(dāng)中的對(duì)象占用情況)。
我用的是ider:


設(shè)置啟動(dòng)參數(shù):
Xms:最小堆內(nèi)存 Xmx:最大可擴(kuò)展內(nèi)存
XX:+HeapDumpOnOutOfMemoryError:可以讓虛擬機(jī)在出現(xiàn)內(nèi)存溢出異常時(shí)Dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲(chǔ)快照以便事后進(jìn)行分析
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

import java.util.ArrayList;
import java.util.List;
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true){
list.add(new OOMObject());
}
}
運(yùn)行結(jié)果:

因?yàn)樵O(shè)置了-XX:+HeapDumpOnOutOfMemoryError參數(shù),所以生成了 這個(gè)報(bào)告。可以查看對(duì)象占用內(nèi)存。

Java堆內(nèi)存的OOM異常是實(shí)際應(yīng)用中最常見(jiàn)的內(nèi)存溢出異常情況。出現(xiàn)Java堆內(nèi)存溢出時(shí),異常堆棧信息"java.lang.OutOfMemoryError”會(huì)跟著進(jìn)一步提示“Java heapspace"。
要解決這個(gè)區(qū)域的異常,一般的手段是首先通過(guò)內(nèi)存映像分析工具(如EclipseMemory Analyzer、Dier的jprofiler)對(duì)dump出來(lái)的堆轉(zhuǎn)儲(chǔ)快照進(jìn)行分析,重點(diǎn)是確認(rèn)內(nèi)存中的對(duì)象是否是必要的,也就是要先分清楚到底是出現(xiàn)了內(nèi)存泄漏(Memory Leak)還是內(nèi)存溢出(Memory Overflow)。
如果是內(nèi)存泄漏,可進(jìn)一步通過(guò)工具查看泄漏對(duì)象到GC Roots的引用鏈。于是就能找到泄漏對(duì)象是通過(guò)怎樣的路徑與GC Roots相關(guān)聯(lián)并導(dǎo)致垃圾收集器無(wú)法自動(dòng)回收它們的。掌握了泄漏對(duì)象的類(lèi)型信息,以及GC Roots引用鏈的信息,就可以比較準(zhǔn)確地定位出泄漏代碼的位置。
如果不存在泄漏,換句話(huà)說(shuō)就是內(nèi)存中的對(duì)象確實(shí)都還必須存活著,那就應(yīng)當(dāng)檢查虛擬機(jī)的堆參數(shù)(-Xmx與-Xms),與機(jī)器物理內(nèi)存對(duì)比看是否還可以調(diào)大,從代碼上檢查是否存在某些對(duì)象生命周期過(guò)長(zhǎng)、持有狀態(tài)時(shí)間過(guò)長(zhǎng)的情況,嘗試減少程序運(yùn)行期的內(nèi)存消耗。
后面我會(huì)專(zhuān)門(mén)寫(xiě)一篇關(guān)于內(nèi)存分析工具的博客,XX:+HeapDumpOnOutOfMemoryError這個(gè)只是有內(nèi)存占用情況,工具可以幫我們看到對(duì)象的引用鏈情況。
2、虛擬機(jī)棧和本地方法棧溢出
由于在HotSpot虛擬機(jī)中并不區(qū)分虛擬機(jī)棧和本地方法棧,因此對(duì)于HotSpot來(lái)說(shuō),-Xoss參數(shù)(設(shè)置本地方法棧大小)雖然存在,但實(shí)際上是無(wú)效的,棧容量只由-Xss參數(shù)設(shè)定。關(guān)于虛擬機(jī)棧和本地方法棧,在Java虛擬機(jī)規(guī)范中描述了兩種異常。
注意:HotSpot虛擬機(jī)的棧容量是不可以動(dòng)態(tài)擴(kuò)展的。
- 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常
- 允許??臻g動(dòng)態(tài)擴(kuò)展時(shí),當(dāng)
擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存時(shí)會(huì)拋出OutOfMemoryError異常
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
- 當(dāng)聲明是基本類(lèi)型的變量的時(shí),其變量名及值(變量名及值是兩個(gè)概念)是放在JAVA虛擬機(jī)棧中
- 當(dāng)聲明的是引用變量時(shí),所聲明的變量(該變量實(shí)際上是在方法中存儲(chǔ)的是內(nèi)存地址值)是放在JAVA虛擬機(jī)的棧中,該變量所指向的對(duì)象是放在堆類(lèi)存中的。
實(shí)驗(yàn)結(jié)果表明:在單個(gè)線程下,無(wú)論是由于棧幀太大,還是虛擬機(jī)棧容量太小,當(dāng)內(nèi)存無(wú)法分配的時(shí)候,虛擬機(jī)拋出的都是StackOverflowError異常。換成遠(yuǎn)古時(shí)代的Classic虛擬機(jī),這款虛擬機(jī)可以支持動(dòng)態(tài)擴(kuò)展 棧內(nèi)存的容量,這時(shí)候就會(huì)報(bào)StackOverflowError異常了。
也就是當(dāng)我設(shè)置-Xss128k和不設(shè)置都是報(bào)同樣的錯(cuò)誤,并沒(méi)有出現(xiàn)內(nèi)存溢出異常,原因就是 HotSpot虛擬機(jī)的棧容量是不可以動(dòng)態(tài)擴(kuò)展的,但是值得注意的是我的電腦是16G運(yùn)行內(nèi)存的,當(dāng)我設(shè)置-Xss128k的時(shí)候輸出的長(zhǎng)度是將近1000,當(dāng)我不限制-Xss128k大小的時(shí)候輸出的長(zhǎng)度是20000左右,也就意味著每個(gè)線程的棧幀大小默認(rèn)最大是2MB。
如果測(cè)試時(shí)不限于單線程,通過(guò)不斷地建立線程的方式倒是可以產(chǎn)生內(nèi)存溢出異常,在這種情況下,給每個(gè)線程的棧分配的內(nèi)存越大,反而越容易產(chǎn)生內(nèi)存溢出異常。
原因其實(shí)不難理解,操作系統(tǒng)分配給每個(gè)進(jìn)程的內(nèi)存是有限制的,譬如32位Windows的單個(gè)進(jìn)程 最大內(nèi)存限制為2GB。HotSpot虛擬機(jī)提供了參數(shù)可以控制Java堆和方法區(qū)這兩部分的內(nèi)存的最大值。那么虛擬機(jī)棧和本地方法棧內(nèi)存如下:
虛擬機(jī)棧和本地方法棧內(nèi)存=2GB-最大堆容量-最大方法區(qū)容量-程序計(jì)數(shù)器容量
因此為每個(gè)線程分配到的棧內(nèi)存越大,可以建立的線程數(shù)量自 然就越少,建立線程時(shí)就越容易把剩下的內(nèi)存耗盡。
通過(guò)上面了解到,出現(xiàn)StackOverflowError異常時(shí)有錯(cuò)誤堆??梢蚤喿x,相對(duì)來(lái)說(shuō),比較容易找到問(wèn)題的所在(一般出現(xiàn)死循環(huán)可能會(huì)導(dǎo)致)。
如果是建立過(guò)多線程導(dǎo)致的內(nèi)存溢出,而不是棧溢出,在不能減少線程數(shù)或者更換64位虛擬機(jī)的情況下,就只能通過(guò)減少最大堆和減少棧容量來(lái)?yè)Q取更多的線程。如果沒(méi)有這方面的經(jīng)驗(yàn),這種通過(guò)“減少內(nèi)存”的手段來(lái)解決內(nèi)存溢出的方式會(huì)比較難以想到。
public class JavaVMStackOOM {
private void dontStop(){
while (true){
}
}
public void stackLeakByThread(){
while (true){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
注意 重點(diǎn)提示一下,如果讀者要嘗試運(yùn)行上面這段代碼,記得要先保存當(dāng)前的工作,由于在 Windows平臺(tái)的虛擬機(jī)中,Java的線程是映射到操作系統(tǒng)的內(nèi)核線程上,無(wú)限制地創(chuàng)建線程會(huì)對(duì)操 作系統(tǒng)帶來(lái)很大壓力,上述代碼執(zhí)行時(shí)有很高的風(fēng)險(xiǎn),可能會(huì)由于創(chuàng)建線程數(shù)量過(guò)多而導(dǎo)致操作系統(tǒng) 假死(電腦可能直接死機(jī))。
在32位操作系統(tǒng)下的運(yùn)行結(jié)果:
原因:32位有進(jìn)程大小內(nèi)存限制。
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread
注意:如果要測(cè)試上面內(nèi)存溢出代碼,記住先保存當(dāng)前的工作,避免電腦卡死帶來(lái)的麻煩。
3、運(yùn)行時(shí)常量池溢出
由于運(yùn)行時(shí)常量池是方法區(qū)的一部分,所以這兩個(gè)區(qū)域的溢出測(cè)試可以放到一起進(jìn)行。前面曾經(jīng) 提到HotSpot從JDK 7開(kāi)始逐步“去永久代”的計(jì)劃,并在JDK 8中完全使用元空間來(lái)代替永久代,在此我們就以測(cè)試代碼來(lái)觀察一下,使用“永久代”還是“元空間”來(lái)實(shí)現(xiàn)方法區(qū),對(duì)程序有什么 實(shí)際的影響。
String::intern()是一個(gè)本地方法,它的作用是如果字符串常量池中已經(jīng)包含一個(gè)等于此String對(duì)象的 字符串,則返回代表池中這個(gè)字符串的String對(duì)象的引用;否則,會(huì)將此String對(duì)象包含的字符串添加 到常量池中,并且返回此String對(duì)象的引用。
import java.util.ArrayList;
import java.util.List;
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
// 使用List保持著常量池引用,避免Full GC回收常量池行為
List<String> list = new ArrayList<>();
// 10MB的PerSize在integer范圍內(nèi)足夠產(chǎn)生00M
int i = 0;
while (true){
list.add(String.valueOf(i++).intern());
}
}
}
- JDK7及以前(了解):-XX:PermSize設(shè)置永久代初始大小。-XX:MaxPermSize設(shè)置永久代最大可分配空間。(JDK7目前已經(jīng)很少用了,這兩個(gè)參數(shù)在JDK8及以后已經(jīng)沒(méi)有了,所以不必掌握,了解一下)
- JDK8及以后:可以使用-XX:MetaspaceSize和-XX:MaxMetaspaceSize設(shè)置元空間初始大小以及最大可分配大小。
使用JDK 7或更高版本的JDK來(lái)運(yùn)行這段程序并不會(huì)得到相同的結(jié)果,無(wú)論是在JDK 7中繼續(xù)使 用-XX:MaxPermSize參數(shù)或者在JDK 8及以上版本使用-XX:MaxMeta-spaceSize參數(shù)把方法區(qū)容量同 樣限制在6MB,都不會(huì)出現(xiàn)溢出異常,循環(huán)將一直進(jìn)行下去,永不停歇。出現(xiàn)這種變 化,是因?yàn)樽訨DK 7起,原本存放在永久代的字符串常量池被移至Java堆之中,所以在JDK 7及以上版 本,限制方法區(qū)的容量對(duì)該測(cè)試用例來(lái)說(shuō)是毫無(wú)意義的。
在JDK1.7中(包括1.7以上)常量池存儲(chǔ)的不再是對(duì)象,而是對(duì)象引用,真正的對(duì)象是存儲(chǔ)在堆中的。把RuntimeConstantPoolOOM.java運(yùn)行時(shí)的VM參數(shù)改為如下(設(shè)置堆大?。┧荆?/p>
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
運(yùn)行結(jié)果:

查看生成的堆內(nèi)存快照:

4、方法區(qū)溢出
方法區(qū)用于存放Class的相關(guān)信息,如類(lèi)名、訪問(wèn)修飾符、常量池、字段描述、方法描述等。對(duì)于這個(gè)區(qū)域的測(cè)試,基本的思路是運(yùn)行時(shí)產(chǎn)生大量的類(lèi)去填滿(mǎn)方法區(qū),直到溢出。雖然直接使用Java SE API也可以動(dòng)態(tài)產(chǎn)生類(lèi)(如反射時(shí)的GeneratedConstructorAccessor 和動(dòng)態(tài)代理等),但在本次實(shí)驗(yàn)中操作起來(lái)比較麻煩。借助CGLib直接操作字節(jié)碼運(yùn)行時(shí),生成了大量的動(dòng)態(tài)類(lèi)。
值得特別注意的是,我們?cè)谶@個(gè)例子中模擬的場(chǎng)景并非純粹是一個(gè)實(shí)驗(yàn),這樣的應(yīng)用經(jīng)常會(huì)出現(xiàn)在實(shí)際應(yīng)用中:當(dāng)前的很多主流框架,如Spring和Hibernate對(duì)類(lèi)進(jìn)行增強(qiáng)時(shí),都會(huì)使用到CGLib這類(lèi)字節(jié)碼技術(shù),增強(qiáng)的類(lèi)越多,就需要越大的方法區(qū)來(lái)保證動(dòng)態(tài)生成的Class可以加載人內(nèi)存。
測(cè)試示例:
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class JavaMethodAreaOOM {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
static class OOMObject {
}
}
設(shè)置元空間最大空間,和初始化空間參數(shù):
類(lèi)信息是都存在方法區(qū)的,方法區(qū)在jdk1.8將永久區(qū)改為了元空間。自此以后,常量池在元空間都是存儲(chǔ)的引用。實(shí)際對(duì)象是在堆中。
-XX:MaxMetaspaceSize=10m -XX:MetaspaceSize=10m
運(yùn)行結(jié)果:

方法區(qū)溢出也是一種常見(jiàn)的內(nèi)存溢出異常,一個(gè)類(lèi)如果要被垃圾收集器回收,要達(dá)成的條件是比 較苛刻的。在經(jīng)常運(yùn)行時(shí)生成大量動(dòng)態(tài)類(lèi)的應(yīng)用場(chǎng)景里,就應(yīng)該特別關(guān)注這些類(lèi)的回收狀況。這類(lèi)場(chǎng) 景除了之前提到的程序使用了CGLib字節(jié)碼增強(qiáng)和動(dòng)態(tài)語(yǔ)言外,常見(jiàn)的還有:大量JSP或動(dòng)態(tài)產(chǎn)生JSP 文件的應(yīng)用(JSP第一次運(yùn)行時(shí)需要編譯為Java類(lèi))、基于OSGi的應(yīng)用(即使是同一個(gè)類(lèi)文件,被不同 的加載器加載也會(huì)視為不同的類(lèi))等。
5、本機(jī)直接內(nèi)存溢出
直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是《Java虛擬機(jī)規(guī)范》中 定義的內(nèi)存區(qū)域。但是這部分內(nèi)存也被頻繁地使用,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。
直接內(nèi)存:可以使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在Java堆里面的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用`進(jìn)行操作。這樣能在一些場(chǎng)景中顯著提高性能,因?yàn)楸苊饬嗽贘ava堆和Native堆中來(lái)回復(fù)制數(shù)據(jù)(但是有一點(diǎn)注意,雖然不占用堆內(nèi)存,但是他占用了服務(wù)器內(nèi)存)。
直接內(nèi)存(Direct Memory)的容量大小可通過(guò)-XX:MaxDirectMemorySize參數(shù)來(lái)指定,如果不 去指定,則默認(rèn)與Java堆最大值(由-Xmx指定)一致。
代碼示例:
越過(guò)了DirectByteBuffer類(lèi)直接通 過(guò)反射獲取Unsafe實(shí)例進(jìn)行內(nèi)存分配(Unsafe類(lèi)的getUnsafe()方法指定只有引導(dǎo)類(lèi)加載器才會(huì)返回實(shí) 例,體現(xiàn)了設(shè)計(jì)者希望只有虛擬機(jī)標(biāo)準(zhǔn)類(lèi)庫(kù)里面的類(lèi)才能使用Unsafe的功能,在JDK 10時(shí)才將Unsafe 的部分功能通過(guò)VarHandle開(kāi)放給外部使用),因?yàn)殡m然使用DirectByteBuffer分配內(nèi)存也會(huì)拋出內(nèi)存溢 出異常,但它拋出異常時(shí)并沒(méi)有真正向操作系統(tǒng)申請(qǐng)分配內(nèi)存,而是通過(guò)計(jì)算得知內(nèi)存無(wú)法分配就會(huì) 在代碼里手動(dòng)拋出溢出異常,真正申請(qǐng)分配內(nèi)存的方法是Unsafe::allocateMemory()。
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
運(yùn)行參數(shù):
-Xmx20M -XX:MaxDirectMemorySize=10M -XX:+HeapDumpOnOutOfMemoryError
運(yùn)行結(jié)果:

我設(shè)置了-XX:+HeapDumpOnOutOfMemoryError發(fā)現(xiàn)運(yùn)行完成之后并沒(méi)有發(fā)現(xiàn)有內(nèi)存快照。
由直接內(nèi)存導(dǎo)致的內(nèi)存溢出,一個(gè)明顯的特征是在Heap Dump文件中不會(huì)看見(jiàn)有什么明顯的異常 情況,如果讀者發(fā)現(xiàn)內(nèi)存溢出之后產(chǎn)生的Dump文件很小,而程序中又直接或間接使用了 DirectMemory(典型的間接使用就是NIO),那就可以考慮重點(diǎn)檢查一下直接內(nèi)存方面的原因了。
三、JVM常用的啟動(dòng)參數(shù)
堆:
-Xms3550m:設(shè)置JVM初始內(nèi)存為3550M。表示初始化JAVA堆的大小及該進(jìn)程剛創(chuàng)建出來(lái)的時(shí)候,他的專(zhuān)屬JAVA堆的大小,一旦對(duì)象容量超過(guò)了JAVA堆的初始容量,JAVA堆將會(huì)自動(dòng)擴(kuò)容到-Xmx大小。-Xmx3550m:設(shè)置JVM最大可用內(nèi)存為3550M。表示java堆可以擴(kuò)展到的最大值,在很多情況下,通常將-Xms和-Xmx設(shè)置成一樣的,因?yàn)楫?dāng)堆不夠用而發(fā)生擴(kuò)容時(shí),會(huì)發(fā)生內(nèi)存抖動(dòng)影響程序運(yùn)行時(shí)的穩(wěn)定性。
棧:
-Xss128k:規(guī)定了每個(gè)線程虛擬機(jī)棧及堆棧的大小,一般情況下,256k是足夠的,此配置將會(huì)影響此進(jìn)程中并發(fā)線程數(shù)的大?。ê投咽遣灰粯拥?,不支持動(dòng)態(tài)擴(kuò)展)。
方法區(qū):
- JDK7及以前(了解):
-XX:PermSize設(shè)置永久代初始大小。 -XX:MaxPermSize設(shè)置永久代最大可分配空間。(JDK7目前已經(jīng)很少用了,這兩個(gè)參數(shù)在JDK8及以后已經(jīng)沒(méi)有了,所以不必掌握,了解一下)-XX:MaxMetaspaceSize=10m:設(shè)置元空間最大值,默認(rèn)是-1,即不限制,或者說(shuō)只受限于本地內(nèi)存 大小。-XX:MetaspaceSize=10m:指定元空間的初始空間大小,以字節(jié)為單位,達(dá)到該值就會(huì)觸發(fā)垃圾收集 進(jìn)行類(lèi)型卸載,同時(shí)收集器會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放 了很少的空間,那么在不超過(guò)-XX:MaxMetaspaceSize(如果設(shè)置了的話(huà))的情況下,適當(dāng)提高該值。-XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空間剩余容量的百分比,可 減少因?yàn)樵臻g不足導(dǎo)致的垃圾收集的頻率。類(lèi)似的還有-XX:Max-MetaspaceFreeRatio,用于控制最 大的元空間剩余容量的百分比。
內(nèi)存:
-XX:+HeapDumpOnOutOfMemoryError可以讓虛擬機(jī)在出現(xiàn)內(nèi)存溢出異常時(shí)Dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲(chǔ)快照以便事后進(jìn)行分析(內(nèi)存堆轉(zhuǎn)儲(chǔ)快照 指的是溢出后,內(nèi)存當(dāng)中的對(duì)象占用情況)
GC:
-XX:-PrintGCDetails:每次GC時(shí)打印詳細(xì)信息。
四、面試題
public static void main(String[] args) {
String str1 = new StringBuilder("計(jì)算機(jī)").append("軟件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
}
這段代碼在JDK 6中運(yùn)行,會(huì)得到兩個(gè)false,而在JDK 7中運(yùn)行,會(huì)得到一個(gè)true和一個(gè)false。在jdk1.8運(yùn)行也是,true、false。
產(chǎn) 生差異的原因是,在JDK 6中,intern()方法會(huì)把首次遇到的字符串實(shí)例復(fù)制到永久代的字符串常量池 中存儲(chǔ),
返回的也是永久代里面這個(gè)字符串實(shí)例的引用,而由StringBuilder創(chuàng)建的字符串對(duì)象實(shí)例在 Java堆上,所以必然不可能是同一個(gè)引用,結(jié)果將返回false。
而JDK 7(以及部分其他虛擬機(jī),例如JRockit)的intern()方法實(shí)現(xiàn)就不需要再拷貝字符串的實(shí)例 到永久代了,既然字符串常量池已經(jīng)移到Java堆中,那只需要在常量池里記錄一下首次出現(xiàn)的實(shí)例引 用即可,因此intern()返回的引用和由StringBuilder創(chuàng)建的那個(gè)字符串實(shí)例就是同一個(gè)。
而對(duì)str2比較返 回false,這是因?yàn)?ldquo;java”這個(gè)字符串在執(zhí)行String-Builder.toString()之前就已經(jīng)出現(xiàn)過(guò)了,字符串常量 池中已經(jīng)有它的引用,不符合intern()方法要求“首次遇到”的原則,“計(jì)算機(jī)軟件”這個(gè)字符串則是首次 出現(xiàn)的,因此結(jié)果返回true。(這塊說(shuō)實(shí)話(huà)不好理解,說(shuō)白了就是java是個(gè)特殊的字符串,他在常量池里面就一直存在)
總結(jié):在1.8之后通過(guò)intern()添加到常量池,只有字符串在常量池不存在的時(shí)候才會(huì)返回字符串的引用。
五、總結(jié)
到此為止,我們明白了虛擬機(jī)里面的內(nèi)存是如何劃分的,哪部分區(qū)域、什么樣的代碼和操作可能 導(dǎo)致內(nèi)存溢出異常。雖然Java有垃圾收集機(jī)制,但內(nèi)存溢出異常離我們并不遙遠(yuǎn),本章只是講解了各 個(gè)區(qū)域出現(xiàn)內(nèi)存溢出異常的原因,下一章將詳細(xì)講解Java垃圾收集機(jī)制為了避免出現(xiàn)內(nèi)存溢出異常都 做了哪些努力。
到此這篇關(guān)于Java實(shí)戰(zhàn)之OutOfMemoryError異常的文章就介紹到這了,更多相關(guān)java OutOfMemoryError異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)讀取html文本內(nèi)容并按照格式導(dǎo)出到excel中
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)讀取html文本提取相應(yīng)內(nèi)容按照格式導(dǎo)出到excel中,文中的示例代碼講解詳細(xì),需要的可以參考下2024-02-02
Java 字符串轉(zhuǎn)float運(yùn)算 float轉(zhuǎn)字符串的方法
今天小編就為大家分享一篇Java 字符串轉(zhuǎn)float運(yùn)算 float轉(zhuǎn)字符串的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
JavaScript實(shí)現(xiàn)鼠標(biāo)移動(dòng)粒子跟隨效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)鼠標(biāo)移動(dòng)粒子跟隨效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
Springcloud Config支持本地配置文件的方法示例
這篇文章主要介紹了Springcloud Config支持本地配置文件的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Springboot傳輸數(shù)據(jù)時(shí)日期格式化問(wèn)題
這篇文章主要介紹了Springboot傳輸數(shù)據(jù)時(shí)日期格式化問(wèn)題,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09

