學習JVM---入門

来源:https://www.cnblogs.com/yx-study/archive/2023/10/04/17742373.html
-Advertisement-
Play Games

1.JVM體繫結構 JVM的位置 JVM體繫結構 2.類載入器 雙親委派機制 package java.lang; /** * 測試自定義java.lang.String類能否運行成功 * 體會雙親委派機制 * * 類載入器逐級向上檢查:app->ext->boot * 發現boot類載入器中也有S ...


1.JVM體繫結構

JVM的位置

JVM的位置

JVM體繫結構

JVM體繫結構

2.類載入器

雙親委派機制

package java.lang;

/**
 * 測試自定義java.lang.String類能否運行成功
 * 體會雙親委派機制
 *
 * 類載入器逐級向上檢查:app->ext->boot
 * 發現boot類載入器中也有String類,但是沒有main方法,於是報錯
 * app:應用程式載入器
 * ext:擴展類載入器
 * boot:啟動類(根)載入器
 *
 * 檢查什麼?每一級類載入器能夠載入的類是固定的,不能越級載入。
 * boot能載入的類,app,ext就不能載入;同理,exit能載入的,app就不能載入。
 * 一個形象的比喻:類,app,ext,boot分別對應平民,村長,鄉長,縣長。
 * 村長會向鄉長彙報,鄉長向縣長彙報。如果這個案子很特殊,應該有縣長來處理,那麼村長和鄉長當然不能管了。
 *
 * 通過驗證“hello”是否會輸出,可以知道:先載入類,再去執行main方法。
 * 類是方法的載體,包括main方法,想要用方法,就得先載入類
 */
public class String {
    public static void main(String[] args) {
        System.out.println("hello");        //這一行會輸出嗎?
        String s = "";
        System.out.println(s.getClass().getClassLoader());
    }
}

運行結果:

錯誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義為:
   public static void main(String[] args)
否則 JavaFX 應用程式類必須擴展javafx.application.Application
public class Cat {
    /**
     * 測試自定義普通類使用哪個類載入器
     *
     * 從app到boot檢查是否有Cat類,發現ext和boot中都沒有Cat類
     * 所有直接還是用app載入器
     */
    public static void main(String[] args) {
        System.out.println("Hello");
        System.out.println(Cat.class.getClassLoader());     //運行結果:sun.misc.Launcher$AppClassLoader@18b4aac2
    }
}

3.沙箱安全機制

4.native

native是Java的一個關鍵字,使用它可以調用本地方法,訪問本地資源。

Thread類中的一個用法:

private native void start0();

執行到start0()時,start0()進入本地方法棧,然後調用本地方法介面JNI(Java Native Interface),然後調用本地方法庫。

5.方法區

存哪些東西

  • static變數
  • final變數
  • Class對象
  • 常量池

實例變數存放在堆中。

面試題:一個實例的創建過程?

  1. 類載入,Class對象存放在方法區中。
    1. 父類靜態成員
    2. 子類靜態成員
    3. 父類代碼塊
    4. 父類構造器
    5. 子類代碼塊
    6. 子類構造器
  2. 實例的聲明 Object obj,存放在棧中。
  3. 實例的創建 new Object(),存放在堆中。
  4. 將對象實例的地址賦給對象的引用(棧中的變數名指向堆中具體的對象)。obj = new Object();
  5. 對對象的屬性賦值。
  6. 調用方法。
    創建對象

6.棧

特點:先進後出,後進先出

為什麼main方法先執行,後結束?

棧與main方法

進棧順序:main(),test1(),test2()

出棧順序:test2(),test1(),main()

為什麼遞歸會引起棧溢出?

棧溢出

當調用遞歸出現死迴圈的情況時,棧溢出也就出現了。

測試代碼:

package stack_;

public class TestStack {
    public static void main(String[] args) {
        test1();
    }
    static void test1(){
        test2();
    }

    static void test2(){
        test1();
    }
}

運行結果:

Exception in thread "main" java.lang.StackOverflowError
	at stack_.TestStack.test2(TestStack.java:12)
	at stack_.TestStack.test1(TestStack.java:8)
	……
	此處省略1000多行(馬德,typora軟體都乾卡死了)
	……
	at stack_.TestStack.test2(TestStack.java:12)
	at stack_.TestStack.test1(TestStack.java:8)

Process finished with exit code 1

7.堆

堆:heap

堆中的三個區域

  • 新生區
    • 伊甸園
    • 幸存區0
    • 幸存區1
  • 養老區
  • 永久區

新生區

新生區

伊甸園:類的創建,應用,甚至消亡。

伊甸園滿了之後,觸發GC,有一部分被銷毀,有一部分進入幸存區。幸存區0,1又會發生數據交換。

老年區

新生區滿了之後,觸發Full GC,進入老年區。

老年區和新生區都滿了,就會發生OOM。

一般不會出現OOM,因為99%的數據都是臨時的,用完就不再使用,在伊甸園或幸存區就被回收了。

永久區

JDK1.6及以前:永久代,常量池位於方法區

JDK1.7:永久代,常量池位於堆

JDK1.8及以後,永久區更名為:元空間,常量池位於元空間

記憶體調優

//虛擬機需要的最大記憶體
        long max = Runtime.getRuntime().maxMemory();
        //虛擬機初始化時的總記憶體
        long original = Runtime.getRuntime().totalMemory();

        System.out.println("max:" + max + "Byte," + max / 1024 / 1024 + "MB");
        System.out.println("original:" + original + "Byte," + original / 1024 / 1024 + "MB");
/**
 * 調優之前:
 * max / 電腦記憶體 ≈ 1 / 4
 * original / 電腦記憶體 ≈ 1 / 64
 
 *記憶體調優:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
 */

運行結果:

max:1029177344Byte,981MB
original:1029177344Byte,981MB
Heap
 PSYoungGen      total 305664K, used 20971K
  eden space 262144K, 8% used
  from space 43520K, 0% used
  to   space 43520K, 0% used
 ParOldGen       total 699392K, used 0K
  object space 699392K, 0% used
 Metaspace       used 3282K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 356K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

(305664K+699392K)/ 1024 = 981M

元空間的記憶體邏輯上存在,物理上不存在。

OOM 記憶體溢出

案例演示

package heap_;

import java.util.Random;

/**
 * 測試堆記憶體溢出
 */
public class TestOOM {
    public static void main(String[] args) {
        String s = "hello";
        while (true){
            s +=  s + s + new Random().nextInt(999999999);
        }
    }
}

運行結果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at heap_.TestOOM.main(TestOOM.java:9)

Process finished with exit code 1

Java中的字元串可以不停的增加長度,但是JVM中的堆記憶體空間是有限的。

對虛擬機參數進行調整(-Xms8m -Xmx8m -XX:+PrintGCDetails),並觀察運行結果。

可以看到很快就會運行結束,並報OOM錯誤。

出現OOM,如何解決?

  • 記憶體調大一點
  • 如果還是有問題,就要研究代碼,是否有bug

使用Jprofiler分析記憶體

Jprofiler安裝教程:https://www.cnblogs.com/zhangxl1016/articles/16220183.html

測試代碼:

package heap_;

import java.util.ArrayList;

public class TestDump {
    byte[] arr = new byte[1024 * 1024];     //共1MB的空間

    public static void main(String[] args) {
        ArrayList<TestDump> list = new ArrayList<>();
        int count = 0;
        try {
            while (true) {
                list.add(new TestDump());
                count++;
            }
        } 
        //OOM要用Error來捕獲
        catch (Error error) {
            System.out.println("count=" + count);
            error.printStackTrace();
        }

    }
}

運行結果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3996.hprof ...
Heap dump file created [7778880 bytes in 0.024 secs]
count=6
java.lang.OutOfMemoryError: Java heap space
	at heap_.TestDump.<init>(TestDump.java:6)
	at heap_.TestDump.main(TestDump.java:13)

Process finished with exit code 0

併在當前項目的根目錄下生成了Jprofiler文件:
生成Jprofiler文件

雙擊打開:
BiggestObjs

可以看到最上邊的列表項(ArrayList)占用的記憶體是最高的,說明是它出了問題。

那麼具體是代碼中的哪一行有問題呢?
ThreadDump

因為這個案例只有main方法一個線程,所以直接點main,然後在下方可以看到是源代碼第13行出了問題。

為什麼到count=6時就報錯了呢?因為在第13行每加一個對象,就會增加1MB的空間,我們分配的最大空間是8MB,所以加到6的時候,就會發生記憶體溢出。

記得刪除生成的Jprofiler文件,因為比較占用空間。
記得刪除生成的Jprofiler文件

虛擬機參數

-Xms 設置初始化記憶體大小 預設1/64

-Xmx 設置最大分配記憶體 預設1/4

-XX:PrintGCDetails 列印GC垃圾回收信息

-XX:HeapDumpOnOutOfMemoryError 轉儲OOM異常信息

上一個案例設置為:-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
編輯配置項

VMOptions

垃圾回收

哪些東西是垃圾?

不需要的,而且占了空間的對象。

在哪裡回收?

為什麼不在棧回收?因為棧里沒有垃圾,棧不存對象,只存儲變數的引用和方法。

舉個例子:假設堆中存的是具體的人,那麼棧中存的是人的姓名。人去世之後,這個具體的對象依然存在堆中,而且占用空間,所以它需要被回收。棧里存的名字,用完了就自動出棧了,所以棧中不存在垃圾,亦無需回收。

如何回收?

引用計數法

引用計數法

每一個對象都有一個計數器,某個對象被使用一次,相應的計數器就會加1。垃圾回收時,計數器為0的對象被回收。

缺點:當對象很多時,計數器也會占用大量的資源。

複製演算法

主要針對新生區

假設當前伊甸園有兩個對象:o1,o2,GC之後,o1被銷毀,o2進入幸存區。幸存區有兩個,要去哪個呢?哪個是空的,就去哪個。另外一個幸存區中,如果有對象,也會進入to區。然後當前的to區又變成from區,from區又變成to區。一定要保證有一個幸存區是空的,這個區就是to區。

”誰空誰是to“

複製演算法

一個對象在新生區經歷15次(預設次數)還沒有消亡,那麼它會進入老年區,類似於久經沙場的老兵,活到最後就可以養老了。

這裡涉及到一個JVM參數:-XX:MaxTenuringThreshold=15,預設值是15,如果這個值調到很大,那麼新生區的對象會很難進入到老年區中。

  • 優點:沒有記憶體碎片。
  • 缺點:浪費空間。
    • 總是要保證to區是空的。
    • 假設對象100%存活,to區就要面臨無法容納所有對象的情況。to區同時要面臨伊甸園和form區兩個方向的對象。
  • 最佳使用場景:對象存活度較低的區域---新生區。
標記壓縮清除演算法

第一次掃描:標記存活的對象,沒被標記的就是不需要的

第二次掃描:清除沒用的對象,會產生碎片

最後一次掃描:被標記的對象向一側移動,另一側就是被清除掉的

標記壓縮清除演算法

優點:不會增加額外的空間

缺點:掃描會浪費時間,會有記憶體碎片產生

優化:先進行幾次標記清除,最後再統一壓縮

演算法比較
  1. 記憶體效率:複製演算法>標記清除>標記壓縮(時間複雜度)

    • 複製是一個動作,清除是兩個動作,壓縮有三個動作
  2. 記憶體整齊度:複製演算法=標記壓縮>標記清除

    • 複製和壓縮都沒有記憶體碎片
  3. 記憶體利用率:標記清除=標記壓縮>複製

    • 清除和壓縮都在原有空間中操作,複製演算法總是要保證to區是空的

有沒有最優演算法呢?

沒有,只有最合適的:分代收集演算法

新生代:存活率低,適合複製演算法。不用擔心to區的空間不夠。

老年代:區域大,存活率高,適合標記清除+標記壓縮混合實現。碎片不是很多的時候用標記清除,碎片積累到一定程度,就需要壓縮,這其中就要涉及到記憶體調優。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 示例,將新列表中的所有值設置為 'hello': newlist = ['hello' for x in fruits] 表達式還可以包含條件,不像篩選器那樣,而是作為操縱結果的一種方式: 示例,返回 "orange" 而不是 "banana": newlist = [x if x != "bana ...
  • 【中秋國慶不斷更】OpenHarmony組件內狀態變數使用:@State裝飾器 @State裝飾的變數,或稱為狀態變數,一旦變數擁有了狀態屬性,就和自定義組件的渲染綁定起來。當狀態改變時,UI會發生對應的渲染改變。 在狀態變數相關裝飾器中,@State是最基礎的,使變數擁有狀態屬性的裝飾器,它也是大 ...
  • 【中秋國慶不斷更】HarmonyOS對通知類消息的管理與發佈通知(下) 一、發佈進度條類型通知 進度條通知也是常見的通知類型,主要應用於文件下載、事務處理進度顯示。HarmonyOS提供了進度條模板,發佈通知應用設置好進度條模板的屬性值,如模板名、模板數據,通過通知子系統發送到通知欄顯示。 目前系統 ...
  • Python中的變數 變數的定義 程式中,數據都臨時存儲在記憶體中。每一個被存儲在記憶體的數據都有一個記憶體地址。其中特定的數據被我們所使用,因此我們為那些記憶體地址定義了名稱。這一名稱被稱作 標識符,又稱變數名。而與變數名對應記憶體地址中的數據被稱為變數值。 總結:變數為記憶體中特定的數據。它的記憶體地址的名稱 ...
  • 在這,您將學習瞭解 Spring Boot Starter Parent, 它是 Spring Boot 提供的父級 Pom 文件,旨在提供自動版本依賴管理,幫助我們輕鬆快速地進行 Spring Boot 開發。 什麼是 Spring Boot Starter Parent ? 通過 Spring ...
  • 用Rust手把手編寫一個wmproxy(代理,內網穿透等), HTTP及TCP內網穿透原理及運行篇 項目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy github: https://github.com/tickbh/wmproxy 內網、公 ...
  • Dart 3.0在語法層面共發佈了3個高級特性,第一個特性Record記錄我們在前面已經學習和探究。今天我們來學習第二個高級類型Pattern模式,由於內容較多,共分2篇文章進行介紹,本文首先介紹模式的概覽和用法,包括匹配、解構、在變數申明、賦值、迴圈、表達式等應用場景…… ...
  • 背包問題-01背包 首先我們要明白什麼是01背包,在下述例題中,由於每個物體只有兩種可能的狀態(取與不取),對應二進位中的 \(0\) 和 \(1\),這類問題便被稱為\(\text{「0-1 背包問題」}\)。 題目描述 有 \(N\) 件物品和一個容量為 \(M\) 的背包。第 \(i\) 件物 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...