天天看點

深入學習JVM(中)

JVM的位置

深入學習JVM(中)

JVM的體系架構圖

深入學習JVM(中)
深入學習JVM(中)
深入學習JVM(中)

類加載器ClassLoader

深入學習JVM(中)

類的加載、連結和初始化(了解)

加載:查找并加載類的二進制資料

連接配接:

驗證:保證被加載的類的正确性;

準備:給類靜态變量配置設定記憶體空間,指派一個預設的初始值;-

解析:把類中的符号引用轉換為直接引用

在把java編譯為class檔案的時候,虛拟機并不知道所引用的位址;助記符:符号引用!

轉為真正的直接引用,找到對應的直接位址!

初始化:給類的靜态變量指派正确的值;

public class Test{
    public static int a = 1;
}
// 1、加載   編譯檔案為 .class 檔案,通過類加載,加載到JVM

// 2、連接配接   
	  驗證(1)  保證Class類檔案沒有問題
      準備(2)  給int類型配置設定記憶體空間,a = 0;
      解析(3)  符号引用轉換為直接引用

// 3、初始化
      經過這個階段的解析,把1 指派給 變量 a;
           

類的加載 static

package com.coding.classloader;

// JVM 參數:
//     -XX:+TraceClassLoading // 用于追蹤類的加載資訊并列印出來
//     分析項目啟動為什麼這麼慢,快速定位自己的類有沒有被加載!
// rt.jar jdk 出廠自帶的,最進階别的類加載器要加載的!
public class Demo02 {
    public static void main(String[] args) {
        System.out.println(MyChild1.str2);
        // 運作的結果
        /**
         * MyParent1 static
         * MyChild1 static
         * hello,str2
         */
    }
}

class MyParent1{
    public static String str = "hello,world";
    static {
        System.out.println("MyParent1 static");
    }
}

class MyChild1 extends MyParent1{
    public static String str2 = "hello,str2";
    static {
        System.out.println("MyChild1 static");
    }
}
           

常量 final

final 常量在編譯階段的時候是放在常量池;

這個代碼中将常量放到了 Demo03 的常量池中。之後 Demo03與MyParent02 就沒有關系了

package com.coding.classloader;

// 常量
public class Demo03 {
    public static void main(String[] args) {
        System.out.println(MyParent02.str);
    }
}

class MyParent02{
    public static final String str = "hello world";

    static {
        System.out.println("MyParent02 static"); // 這句話會輸出嗎?
    }
    /**
     * final 常量在編譯階段的時候 常量池;
     * 這個代碼中将常量放到了 Demo03 的常量池中。之後 Demo03與MyParent02 就沒有關系了
     */
}
           

UUID對象不在常量池内,final不會直接取值

package com.coding.classloader;

import java.util.UUID;

/**
 * 當一個常量的值并非編譯期間可以确定的,那這個值就不會被方法調用類的常量池中!
 * 程式運作期間的時候,回主動使用常用所在的類
 */
public class Demo04 {
    public static void main(String[] args) {
        System.out.println(MyParent04.str);
    }
}

class MyParent04{

    public static final String str = UUID.randomUUID().toString();

    static {
        System.out.println("MyParent04 static"); // 這句話會輸出嗎?
    }

}
           

ClassLoader 分類

1、java虛拟機自帶的加載器
  • BootStrap 根加載器 (加載系統的包,JDK 核心庫中的類 rt.jar)
  • Ext 擴充類加載器 (加載一些擴充jar包中的類)
  • Sys/App 系統(應用類)加載器 (我們自己編寫的類

2、使用者自己定義的加載器

ClassLoader,隻需要繼承這個抽象類即可,自定義自己的類加載器

雙親委派機制

雙親委派機制 可以保護java的核心類不會被自己定義的類所替代

一層一層的讓父類去加載,如果頂層的加載器不能加載,然後再向下類推

// Demo 01

// AppClassLoader 03

// ExtClassLoader 02

// BootStrap (最頂層) 01 java.lang.String rt.jar

```java
package com.coding.classloader;

// Demo01
public class Demo01 {

    public static void main(String[] args) {
        Object o = new Object(); // jdk 自帶的
        Demo01 demo01 = new Demo01();  // 執行個體化一個自己定義的對象

        // null 在這裡并不代表沒有,隻是Java觸及不到!
        System.out.println(o.getClass().getClassLoader()); // null
        System.out.println(demo01.getClass().getClassLoader()); // AppClassLoader
        System.out.println(demo01.getClass().getClassLoader().getParent()); // ExtClassLoader
        System.out.println(demo01.getClass().getClassLoader().getParent().getParent()); // null

        // 思考:為什麼我們剛才自己定義的 java.lang.String 沒有生效?

        // jvm 中有機制可以保護自己的安全;
        // 雙親委派機制 : 一層一層的讓父類去加載,如果頂層的加載器不能加載,然後再向下類推
        // Demo01
        // AppClassLoader       03
        // ExtClassLoader       02
        // BootStrap (最頂層)   01  java.lang.String  rt.jar

        // 雙親委派機制 可以保護java的核心類不會被自己定義的類所替代
    }
}
           

Native方法

native : 隻要是帶了這個關鍵字的,說明 java的作用範圍達不到,隻能去調用底層 C 語言的庫!

public class Test {
    public static void main(String[] args) {
        // java 真的可以開啟線程嗎?
        // private native void start0();
        new Thread().start();
    }
}
           

程式計數器

每個線程都有一個程式計數器,是線程私有的。

程式計數器就是一塊十分小的記憶體空間;幾乎可以不計

作用: 看做目前位元組碼執行的行号訓示器;

深入學習JVM(中)

分支、循環、跳轉、異常處理!都需要依賴于程式計數器來完成!

bipush

将 int、float、String、常量值推送值棧頂;

istore

将一個數值從操作數棧存儲到局部變量表;

iadd

imul

方法區淵源

Method Area 方法區 是 Java虛拟機規範中定義的運作是資料區域之一,和堆(heap)一樣可以線上程之間共享!

JDK1.7之前

永久代:用于存儲一些虛拟機加載類資訊,常量,字元串、靜态變量等等。。。。這些東西都會放到永久代中;

永久代大小空間是有限的:如果滿了

OutOfMemoryError:PermGen

JDK1.8之後

徹底将永久代移除 HotSpot jvm ,Java Heap 中或者 Metaspcace(Native Heap)元空間;

元空間就是方法區在 HotSpot jvm 的實作;

方法區重要就是來存:類資訊,常量,字元串、靜态變量、符号引用、方法代碼。。。。。。

元空間和永久代,都是對JVM規範中方法區的實作。

元空間和永久代最大的差別:元空間并不在Java虛拟機中,使用的是本地記憶體!

-XX:MetasapceSize10m

棧Stack

棧和隊列都是基本的資料結構;

隊列:FIFO(First Input First OutPut)

深入學習JVM(中)
棧的優勢:存取速度比堆快!僅次于寄存器,棧的資料是不可以共享的;
public class Demo01 {
    public static void main(String[] args) {
        a();
    }
    // main  a  a  a   a a  a a a  a a  a  滿
    // Exception in thread "main" java.lang.StackOverflowError
    private static void a() {
        a();
    }
}
           

是以說,棧裡面是一定不會存在垃圾回收的問題的,隻要線程一旦結束,該棧就Over了。生命周期和線程一緻.

Stack原理

java棧的組成元素–`棧幀

深入學習JVM(中)

棧(存什麼)+ 堆 + 方法區的互動圖:

深入學習JVM(中)

棧主要是 HotSpot (指針)

堆(heap)

Java7之前:

Heap 堆,一個JVM執行個體中隻存在一個堆,堆的記憶體大小是可以調節的。

可以存的内容:類、方法、常量、儲存了類型引用的真實資訊;

分為三個部分:

  • 新生區:Young (Eden-s0-s1)
  • 養老區:Old Tenure
  • 永久區:Perm

堆記憶體在邏輯上分為三個部分:新生、養老、永久(JDK1.8以後,叫元空間)

實體上隻有 新生、養老;元空間在本地記憶體中,不在JVM中!

GC 垃圾回收主要是在 新生區和養老區,又分為 普通的GC 和 Full GC,如果堆滿了,就會爆出 OutOfMemory;

新生區

新生區 就是一個類誕生、成長、消亡的地方!

新生區細分: Eden、s(from to),所有的類Eden被 new 出來的,慢慢的當 Eden 滿了,程式還需要建立對象的時候,就會觸發一次輕量級GC;清理完一次垃圾之後,會将活下來的對象,會放入幸存者區(),… 清理了 20次之後,出現了一些極其頑強的對象,有些對象突破了15次的垃圾回收!這時候就會将這個對象送入養老區!運作了幾個月之後,養老區滿了,就會觸發一次 Full GC;假設項目1年後,整個空間徹徹底底的滿了,突然有一天系統 OOM,排除OOM問題,或者重新開機;

Sun HotSpot 虛拟機中,記憶體管理(分代管理機制:不同的區域使用不同的算法!)

99% 的對象在 Eden 都是臨時對象;

養老區

15次都幸存下來的對象進入養老區,養老區滿了之後,觸發 Full GC

預設是15次,可以修改!

永久區(Perm)

放一些 JDK 自身攜帶的 Class、Interface的中繼資料;

幾乎不會被垃圾回收的;

OutOfMemoryError:PermGen

在項目啟動的時候永久代不夠用了?加載大量的第三方包!

JDK1.6之前: 有永久代、常量池在方法區;

JDK1.7:有永久代、但是開始嘗試去永久代,常量池在堆中;

JDK1.8 之後:永久代沒有了,取而代之的是元空間;常量池在元空間中;

堆記憶體調優

環境:HotSpot、JDK1.8;

測試一

package com.coding.oom;


/**
 * 預設情況:
 * maxMemory : 1808.0MB (虛拟機試圖使用的最大的記憶體量  一般是實體記憶體的 1/4)
 * totalMemory : 123.0MB (虛拟機試圖預設的記憶體總量 一般是實體記憶體的 1/64)
 */
// 我們可以自定堆記憶體的總量
// -XX:+PrintGCDetails; // 輸出詳細的垃圾回收資訊
// -Xmx: 最大配置設定記憶體; 1/4
// -Xms: 初始配置設定的記憶體大小; 1/64

// -Xmx1024m -Xms1024m -XX:+PrintGCDetails
public class Demo01 {
    public static void main(String[] args) {
        // 擷取堆記憶體的初始大小和最大大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();

        System.out.println("maxMemory="+maxMemory+"(位元組)、"+(maxMemory/1024/(double)1024)+"MB");
        System.out.println("totalMemory="+totalMemory+"(位元組)、"+(totalMemory/1024/(double)1024)+"MB");

    }
}
           
深入學習JVM(中)

測試二

package com.coding.oom;

import java.util.Random;

/*
 * -Xmx8m -Xms8m -XX:+PrintGCDetails
 *
 * 分析GC日志:
 *
 *  [Times: user=0.00 sys=0.00, real=0.00 secs]
 * 1、GC 類型  GC:普通的GC,Full GC :重GC
 * 2、1536K 執行 GC之前的大小
 * 3、504K  執行 GC之後的大小
 * 4、(2048K) young 的total大小
 * 5、0.0012643 secs 清理的時間
 * 6、user 總計GC所占用CPU的時間   sys OS調用等待的時間   real 應用暫停的時間
 *
 * GC :串行執行 STW(Stop The World)  并行執行   G1
*/

public class Demo02 {
    public static void main(String[] args) {
        System.gc(); // 手動喚醒GC(),等待cpu的調用
        String str = "ilovecoding";
        while (true){
            str += str
                    + new Random().nextInt(999999999)
                    + new Random().nextInt(999999999);
        }
        // 出現問題:java.lang.OutOfMemoryError: Java heap space
    }
}
           

Dump記憶體快照

在java程式運作的時候,想測試運作的情況!

使用一些工具來檢視;

1、Jconsole

2、idea debug

3、Eclipse(MAT插件)

4、IDEA(Jprofiler插件)

Jprofiler 插件

一款性能瓶頸分析插件

安裝 Jprofiler

1、IDEA安裝 JProfiler 插件

深入學習JVM(中)

2、window上安裝 JProfiler (無腦下一步即可:注意路徑中不能有中文和空格)

3、激活

注冊碼僅供大家參考

[email protected]#23874-hrwpdp1sh1wrn#0620

[email protected]#36573-fdkscp15axjj6#25257

[email protected]#5481-ucjn4a16rvd98#6038

[email protected]#99016-hli5ay1ylizjj#27215

[email protected]#40775-3wle0g1uin5c1#0674

4、在IDEA 中綁定 JProfiler

深入學習JVM(中)
package com.coding.oom;

import java.util.ArrayList;
import java.util.List;

// -Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError
public class Demo03 {

    byte[] bytes = new byte[1*1024*1024]; // 1M

    public static void main(String[] args) throws InterruptedException {
        // 泛型:限制!
        List<Demo03> list = new ArrayList<Demo03>();

        int count = 0;

        try {
            // Error
            while (true){
                list.add(new Demo03());
                count = count + 1;
            }
        } catch (Throwable e) { // Throwable 或者 Error
            System.out.println("count="+count);
            e.printStackTrace();
        }
    }
}
           

分析dump出來的快照,檢視異常對象;分析定位到具體的類和代碼問題!