Java8虛擬機器(JVM)記憶體溢位實戰
前言
相信很多JAVA中高階的同學在面試的時候會經常碰到一個面試題
你是如何在工作中對JVM調優和排查定位問題的?
事實上,如果使用者量不大的情況下,在你的程式碼還算正常的情況下,在工作中除非真正碰到與JVM相關的問題是少之又少,就算碰到了也是由公司的一些大牛去排查解決,那麼我們又如何積累這方面的經驗呢?下面由衝鍋帶大家一起來實踐JVM的調優吧
注意我們平常所說的JVM調優一般指Java堆,Java虛擬機器棧引數調優
Java堆溢位
先來一段程式碼示例,注意筆者用的是IDEA工具,需要配置一下VM options 為-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError,如果不清楚的百度一下如何配置idea的JVM執行引數
package com.example.demo.jvm;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 9:37
* @Version: V1.0
*/
public class HeapOutMemoryTest {
static class ChongGuo {
}
public static void main(String[] args) {
List<ChongGuo> chongGuos = new ArrayList<>();
while (true) {
chongGuos.add(new ChongGuo());
}
}
}
執行結果如下:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9352.hprof ...
Heap dump file created [28701160 bytes in 0.122 secs]
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.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at com.example.demo.jvm.HeapOutMemoryTest.main(HeapOutMemoryTest.java:18)
Disconnected from the target VM, address: '127.0.0.1:54599', transport: 'socket'
可以看到控制檯出現java.lang.OutOfMemoryError: Java heap space的錯誤,這是為什麼呢,首先先解釋一下上面的執行引數
- -Xms20m:設定JVM最小記憶體為20m。此值可以設定與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配記憶體
- -Xmx20m:設定JVM最大可用記憶體20M
- -XX:+HeapDumpOnOutOfMemoryError 表示當JVM發生OOM時,自動生成DUMP檔案
下面我們分析一下出錯的原因,用JProfiler分析一下,開啟剛才生成的名為java_pid9352.hprof的dump檔案。可以看到根據(InstanceXcount和Size)基本可以確定哪個類的物件出現問題,在上面示例中,可以是ChongGuo這個例項生在數量的大小已經超過12M,但沒有超過20M,那麼新問題又來了?沒到20M為啥會報堆記憶體溢位呢?
答案就是JDK8中堆記憶體中還包括Metaspace,即元記憶體空間,在元空間出現前JDK1.7之前在JDK7以及其前期的JDK版本號中。堆記憶體通常被分為三塊區域Nursery記憶體(young generation)、長時記憶體(old generation)、永久記憶體(Permanent Generation for VM Matedata),如下圖
當中最上一層是年輕代,一個物件被建立以後首先被放到年輕代中的Eden記憶體中,假設存活期超兩個Survivor之後就會被轉移到長時記憶體中(Old Generation)中永久記憶體中存放著物件的方法、變數等後設資料資訊。透過假設永久記憶體不夠。我們就會得到例如以下錯誤:java.lang.OutOfMemoryError: PermGen
而在JDK8中情況發生了明顯的變化,就是普通情況下你都不會得到這個錯誤,原因
在於JDK8中把存放後設資料中的永久記憶體從堆記憶體中移到了本地記憶體(native memory)
中,JDK8中JVM堆記憶體結構就變成了例如以下:
如果我啟動VM引數加上:-XX:MaxMetaspaceSize=1m,重新執行一下上面的程式,
Connected to the target VM, address: '127.0.0.1:56433', transport: 'socket'
java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid9232.hprof ...
Heap dump file created [1604635 bytes in 0.024 secs]
FATAL ERROR in native method: processing of -javaagent failed
Exception in thread "main" Disconnected from the target VM, address: '127.0.0.1:56433', transport: 'socket'
Process finished with exit code 1
可以發現報錯資訊變成了java.lang.OutOfMemoryError: Metaspace,說明元空間不夠,我改成到大概4m左右才能滿足啟動條件。
虛擬機器棧和本地方法棧棧溢位
在Java虛擬機器規範中描述了兩種異常:
- 如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError異常
- 如果虛擬機器在擴充套件棧無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常
StackOverflowError比較好測試,測試程式碼如下:
package com.example.demo.jvm;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 19:09
* @Version: V1.0
*/
public class StackOverflowTest {
/**
* 棧大小
*/
private int stackLength = 1;
/**
* 遞迴壓棧
*/
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
StackOverflowTest stackOverflowTest = new StackOverflowTest();
try {
stackOverflowTest.stackLeak();
} catch (Throwable e) {
System.out.println("stack length is :" + stackOverflowTest.stackLength);
throw e;
}
}
}
執行結果如下:
Exception in thread "main" stack length is :20739
java.lang.StackOverflowError
at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)
at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)
在VM引數-Xss引數未設定的情況下,該執行緒的記憶體支援的棧深度為20739,該測試結果與機器的記憶體大小有關,不過上面的第二點如何測試呢?正常來說如果是單執行緒,則難以測試記憶體洩露的情況,那麼多執行緒呢?我們看一下以下測試程式碼:
package com.example.demo.jvm;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 19:09
* @Version: V1.0
*/
public class StackOOMTest implements Runnable{
/**
* 棧大小
*/
private int stackLength = 1;
/**
* 遞迴壓棧
*/
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
while (true){
StackOOMTest stackOverflowTest = new StackOOMTest();
new Thread(stackOverflowTest).start();
}
}
@Override
public void run() {
stackLeak();
}
}
如果系統不假死的情況下,會出現Exception in thread “main” java.lang.OutOfMemoryError:unable to create new native thread
執行時常量池溢位
- 字元型常量池溢位,在JAVA8中也是堆溢位,測試程式碼如下:
package com.example.demo.jvm;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 19:44
* @Version: V1.0
*/
public class RuntimePoolOOMTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i).intern());
}
}
}
結果如下:
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.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at com.example.demo.jvm.RuntimePoolOOMTest.main(RuntimePoolOOMTest.java:17)
Disconnected from the target VM, address: '127.0.0.1:50253', transport: 'socket'
證明字元常量池已經在Java8中是在堆中分配的。
方法區溢位
在Java7之前,方法區位於永久代(PermGen),永久代和堆相互隔離,永久代的大小在啟動JVM時可以設定一個固定值,不可變;Java8仍然保留方法區的概念,只不過實現方式不同。取消永久代,方法存放於元空間(Metaspace),元空間仍然與堆不相連,但與堆共享實體記憶體,邏輯上可認為在堆中
測試程式碼如下,為快速看出結果,請加入VM引數-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=10m:
package com.example.demo.jvm;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 19:56
* @Version: V1.0
*/
public class MethodAreaOOMTest {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o,
objects));
enhancer.create();
}
}
static class OOMObject {
}
}
執行結果如下:
java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid8816.hprof ...
Heap dump file created [6445908 bytes in 0.039 secs]
Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:492)
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:305)
at com.example.demo.jvm.MethodAreaOOMTest.main(MethodAreaOOMTest.java:19)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)
... 6 more
Caused by: java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
... 11 more
Process finished with exit code 1
元空間記憶體報錯,證明方法區的溢位與元空間相關。
總結如下:
- 正常JVM調優都是針對堆記憶體和棧記憶體、元空間的引數做相應的改變
- 元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制,但可以透過以下引數來指定元空間的大小:
- -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
- -XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。
- 字串池常量池在每個VM中只有一份,存放的是字串常量的引用值,存放在堆中
有更多的文章,請關注檢視,更有面試寶典相送
本文由部落格一文多發平臺 釋出!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1868/viewspace-2825465/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 對jvm虛擬機器 記憶體溢位的思考JVM虛擬機記憶體溢位
- 深入理解JVM虛擬機器-JVM記憶體區域與記憶體溢位JVM虛擬機記憶體溢位
- Java虛擬機器4:記憶體溢位Java虛擬機記憶體溢位
- JVM系列之實戰記憶體溢位異常JVM記憶體溢位
- 【深入Java虛擬機器】之一:Java記憶體區域與記憶體溢位Java虛擬機記憶體溢位
- jvm 之 記憶體溢位場景模擬JVM記憶體溢位
- Java虛擬機器01——Java記憶體資料區域和記憶體溢位異常Java虛擬機記憶體溢位
- 深入理解Java虛擬機器-Java記憶體區域與記憶體溢位異常Java虛擬機記憶體溢位
- JVM——記憶體洩漏與記憶體溢位JVM記憶體溢位
- jdk8:jvm虛擬機器記憶體模型JDKJVM虛擬機記憶體模型
- JVM虛擬機器記憶體結構簡析JVM虛擬機記憶體
- jvm記憶體區域之虛擬機器棧JVM記憶體虛擬機
- Java虛擬機器:JVM記憶體分代策略Java虛擬機JVM記憶體
- JVM 深入筆記(2)記憶體溢位場景模擬JVM筆記記憶體溢位
- JVM記憶體溢位及合理配置JVM記憶體溢位
- JVM 深入筆記(2)記憶體區溢位場景模擬JVM筆記記憶體
- 虛擬記憶體到實體記憶體(32位)記憶體
- 關於虛擬機器記憶體和JVM記憶體設定的思考虛擬機記憶體JVM
- 《深入理解java虛擬機器》學習筆記2——Java記憶體溢位例項Java虛擬機筆記記憶體溢位
- 【JVM之記憶體與垃圾回收篇】虛擬機器棧JVM記憶體虛擬機
- jvm記憶體設定及記憶體溢位、解決方案JVM記憶體溢位
- Java虛擬機器(JVM)中的記憶體設定詳解Java虛擬機JVM記憶體
- Java棧溢位|記憶體洩漏|記憶體溢位Java記憶體溢位
- JAVA 虛擬機器可用記憶體Java虛擬機記憶體
- JVM(2)-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- 深入理解JVM之記憶體區域與記憶體溢位JVM記憶體溢位
- 記憶體溢位記憶體溢位
- Java虛擬機器記憶體優化實踐Java虛擬機記憶體優化
- JVM掃盲-3:虛擬機器記憶體模型與高效併發JVM虛擬機記憶體模型
- 深入理解Java虛擬機器之JVM記憶體佈局篇Java虛擬機JVM記憶體
- java虛擬機器 jvm 出入java棧 棧空間記憶體分配Java虛擬機JVM記憶體
- 深入理解JVM虛擬機器11:Java記憶體異常原理與實踐JVM虛擬機Java記憶體
- Tomcat中JVM記憶體溢位及合理配置TomcatJVM記憶體溢位
- JVM面試問題系列:深入詳解JVM 記憶體區域及記憶體溢位分析JVM面試記憶體溢位
- Java記憶體溢位Java記憶體溢位
- JBOSS記憶體溢位記憶體溢位
- 實體記憶體和虛擬記憶體記憶體
- 深入理解JVM虛擬機器-物件引用,GC與記憶體分配回收JVM虛擬機物件GC記憶體