前言
工作確定,房子也找好了,昨天電腦桌子也到位了,從今天開始博客進入正常更新狀態。計劃四月份之前將 JVM 相關的東西寫完。
JVM 系列主要寫一些類加載、java內存區域,垃圾回收,jvm 參數配置,使用 java 工具、arthas和 linux 命令定位解決問題
JAVA內存區域
JAVA內存區域(JAVA運行時數據區)不要和 JAVA 內存模型(JMM)混淆。
JAVA內存模型(JMM)定義了Java虛擬機(JVM)在計算機內存(RAM)中的工作方式,java內存模型指的是一套規範,規範線程如何訪問內存。
圖片引用 https://luoyoubao.gitbooks.io/jvm/content/javanei-cun-mo-xing/javanei-cun-mo-xing.html
圖片引用 https://luoyoubao.gitbooks.io/jvm/content/chapter1.html
JVM被分爲三個主要的子系統:
-
類加載器子系統 -
運行時數據區 -
執行引擎
類加載子系統負責從文件系統或者網絡中加載 Class 信息,Class 信息放在方法區中。
PC 寄存器(線程私有)
java 虛擬機中每個線程都有自己的 pc 寄存器。在任意時刻,一條線程只會執行一個方法代碼。如果執行的方法不是 native,那麼 pc 寄存器就保存正在執行的字節碼指令的地址。如果是執行的是 native 方法,pc 寄存器的值是 null。PC 寄存區也是唯一一個不會拋出 OOM 異常的區域。
JAVA 虛擬機棧(線程私有)
每條線程都有自己的虛擬機棧,這個棧和線程同時創建,用於儲存局部變量或者指向堆的指針。在 Java 虛擬機規範中,如果方法遞歸調用太深會拋出 StackOverflowError 異常;當無法申請足夠的內存時也會拋出 OOM 異常。
-Xss 用於調節棧的大小。
native 方法棧(線程私有)
調用 native 方法時使用的棧,瞭解即可。當棧溢出時,拋出 StackOverflowError 異常 ;當申請內存失敗時,拋出 OOM 異常。
堆(線程共享)
堆(heap)是線程共享的區域,垃圾回收也主要回收它,我們主要也是堆打交道。-Xms 和 -Xmx 用於調整堆的大小。
方法區(線程共享)
方法區是線程共享的內存區域,它存儲 Class 的結構信息。例如,運行時常量池、字段、方法、構造函數。方法區使用的是本地內存(堆外內存),相當於在系統上申請的內存。方法區會拋出 OOM。方法區使用元數據空間來調整。
-XX:MaxMetaspaceSize: 設置,默認 -1 不限制。
-XX:MetaspaceSize:指定元空間初始空間大小。字節爲單位。
內存區域異常拋出演示
圖片引用 https://mp.weixin.qq.com/s/hj2GcW5nHS6U8wVZM7YBFg
實際中我們主要關注的是堆、直接內存、棧、元數據區。
堆拋出 OOM
在 idea 啓動程序的時候,傳入虛擬機參數 -Xms100m -Xmx100m -XX:+HeapDumpOnOutOfMemoryError。並使用 jvisualvm 觀察堆的使用情況。
運行結果會拋出 java.lang.OutOfMemoryError: Java heap space。
HeapDumpOnOutOfMemoryError
指定了拋出異常時 dump 內存到文件中。我們可以通過分析這個文件,看那些對象佔用比較多,從而分析問題。
/**
* @author 張攀欽
* @date 2021-03-23-15:23
*/
public class HeapOOM {
static class Obj {
private byte[] a = new byte[1024 * 1024 * 10];
}
public static void main(String[] args) {
final ArrayList<Object> objects = Lists.newArrayList();
int count = 0;
while (true) {
try {
objects.add(new Obj());
System.out.println("添加了多少次" + ++count);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
}
棧溢出
-Xss 用於設置棧的大小。當棧調用深度過深,會拋出 StackOverflowError 異常。
-Xss512k 時,打印結果 調用深度4868
。
-Xss256k 時,打印結果 調用深度1889
。
public class StackOverflowErrorDemo {
private int count = 0;
public void add() {
count++;
add();
}
public int getCount() {
return count;
}
public static void main(String[] args) {
final StackOverflowErrorDemo stackOverflowErrorDemo = new StackOverflowErrorDemo();
try {
stackOverflowErrorDemo.add();
} catch (Error e) {
System.out.println("調用深度" + stackOverflowErrorDemo.getCount());
}
}
}
方法區溢出
方法區主要是儲存類加載的信息,我們可以通過動態代理來模擬出來。
-XX:MaxMetaspaceSize=20m 設置元空間大小。
public class MetaOOM {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
while (true) {
try {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(IMetaService.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invoke(obj, args);
}
});
enhancer.create();
} catch (Throwable e) {
System.err.println(e.getMessage());
}
}
}
}
interface IMetaService {
void add();
}
直接內存溢出
直接內存(Direct Memory,也是堆外內存)的容量可以通過 -XX:MaxDirectMemorySize
設置。默認值是 64m。
一般 Nio 使用了直接內存。-XX:MaxDirectMemorySize=50m
public static void main(String[] args) {
final ArrayList<Object> objects = new ArrayList<>();
while (true) {
try {
objects.add(ByteBuffer.allocateDirect(1024 * 1024 * 10));
} catch (Throwable e) {
System.out.println(e.getMessage());
}
}
}
本文分享自微信公衆號 - Mflyyou(Mflyyou)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。