国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

03-JVM虛擬機(jī)-課堂筆記

這篇具有很好參考價(jià)值的文章主要介紹了03-JVM虛擬機(jī)-課堂筆記。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

3-JVM虛擬機(jī)

靈魂三問(wèn):

JVM是什么?

  • JVM廣義上指的是一種規(guī)范。狹義上的是JDK中的JVM虛擬機(jī)。

為什么要學(xué)習(xí)JVM?

  • 面試過(guò)程中,經(jīng)常會(huì)被問(wèn)到JVM。

  • 研發(fā)過(guò)程中,肯定會(huì)面臨一些重難點(diǎn)問(wèn)題與JVM有關(guān)系。例如:線程死鎖、內(nèi)存溢出、項(xiàng)目性能優(yōu)化等等。

  • 基礎(chǔ)不牢,地動(dòng)山搖。想深入掌握J(rèn)ava這門(mén)語(yǔ)言,JVM始終是繞不過(guò)去的那座大山,早晚得攀。

怎么學(xué)習(xí)JVM?

JVM虛擬機(jī)部分,我們是這么安排的:

  1. JVM基本常識(shí)

  2. 類(lèi)加載子系統(tǒng)

  3. 運(yùn)行時(shí)數(shù)據(jù)區(qū)

  4. 一個(gè)對(duì)象的一生(出生、死亡與內(nèi)涵)

  5. GC垃圾收集器

  6. JVM調(diào)優(yōu)相關(guān)工具與可調(diào)參數(shù)

  7. 調(diào)優(yōu)實(shí)戰(zhàn)案例

1.JVM虛擬機(jī)概述

1.1 JVM 基本常識(shí)

什么是JVM?

平時(shí)我們所說(shuō)的JVM廣義上指的是一種規(guī)范。狹義上的是JDK中的JVM虛擬機(jī)。JVM的實(shí)現(xiàn)是由各個(gè)廠商來(lái)做的。比如現(xiàn)在流傳最廣泛的是hotspot。其他實(shí)現(xiàn):BEA公司 JRocket、IBM j9、zing 號(hào)稱(chēng)世界最快JVM、taobao.vm。從廣義上講Java,Kotlin、Clojure、JRuby、Groovy等運(yùn)行于Java虛擬機(jī)上的編程語(yǔ)言及其相關(guān)的程序都屬于Java技術(shù)體系中的一員。

Java技術(shù)體系主要包括如下四個(gè)方面。

  • Java程序設(shè)計(jì)語(yǔ)言

  • Java類(lèi)庫(kù)API

  • 來(lái)自商業(yè)機(jī)構(gòu)和開(kāi)源社區(qū)的第三方Java類(lèi)庫(kù)

    • Google
    • Apache
    • 等等
  • Java虛擬機(jī):各種硬件平臺(tái)上的Java虛擬機(jī)實(shí)現(xiàn)

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

可以簡(jiǎn)單類(lèi)比一下:Java虛擬機(jī)是宿主,Java代碼開(kāi)發(fā)的程序則寄生在宿主上!

JVM架構(gòu)圖

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

Java和JVM的關(guān)系:

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

1.2 類(lèi)加載子系統(tǒng)

1.2.1 類(lèi)加載的時(shí)機(jī)

類(lèi)加載主要有四個(gè)時(shí)機(jī):

  1. 遇到new 、 getstatic 、 putstatic和invokestatic這四條指令時(shí),如果對(duì)應(yīng)的類(lèi)沒(méi)有初始化,則要對(duì)對(duì)應(yīng)的類(lèi)先進(jìn)行初始化。
public class Student{
    private static int age ;
    public static void method(){
        
    }
}
//Student.age
//Student.method();
//new Student();
  1. 使用java.lang.reflect 包方法時(shí),對(duì)類(lèi)進(jìn)行反射調(diào)用的時(shí)候。
Class c = Class.forname("com.hero.Student");
  1. 初始化一個(gè)類(lèi)的時(shí)候發(fā)現(xiàn)其父類(lèi)還沒(méi)初始化,要先初始化其父類(lèi)

  2. 當(dāng)虛擬機(jī)開(kāi)始啟動(dòng)時(shí),用戶需要指定一個(gè)主類(lèi)(main),虛擬機(jī)會(huì)先執(zhí)行這個(gè)主類(lèi)的初始化。

1.2.2 類(lèi)加載的過(guò)程

類(lèi)加載主要做三件事:

  1. 全限定名稱(chēng) ==> 二進(jìn)制字節(jié)流加載class文件

  2. 字節(jié)流的靜態(tài)數(shù)據(jù)結(jié)構(gòu) ==> 方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)

  3. 創(chuàng)建字節(jié)碼Class對(duì)象

一個(gè)類(lèi)的一生:

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

可以從哪些途徑加載字節(jié)碼?

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

1.2.3 類(lèi)加載器

JVM的類(lèi)加載是通過(guò)ClassLoader及其子類(lèi)來(lái)完成的。

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

  • 檢查順序是自底向上:加載過(guò)程中會(huì)先檢查類(lèi)是否被已加載,從Custom
    ClassLoader到BootStrap ClassLoader逐層檢查,只要某個(gè)classloader已加載就視為已加載此類(lèi),保證此類(lèi)只所有 ClassLoader加載一次。

  • 加載的順序是自頂向下:也就是由上層來(lái)逐層嘗試加載此類(lèi)。

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

  • 啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader):

    • 負(fù)責(zé)加載 JAVA_HOME\lib 目錄中的,或通過(guò)-Xbootclasspath參數(shù)指定路徑中的,且被虛擬機(jī)認(rèn)可(按文件名識(shí)別,如rt.jar)的類(lèi)。由C++實(shí)現(xiàn),不是ClassLoader的子類(lèi)
  • 擴(kuò)展類(lèi)加載器(Extension ClassLoader):

    • 負(fù)責(zé)加載 JAVA_HOME\lib\ext 目錄中的,或通過(guò)java.ext.dirs系統(tǒng)變量指定路徑中的類(lèi)庫(kù)。
  • 應(yīng)用程序類(lèi)加載器(Application ClassLoader):負(fù)責(zé)加載用戶路徑classpath上的類(lèi)庫(kù)

  • 自定義類(lèi)加載器(User ClassLoader):

    • 作用:JVM自帶的三個(gè)加載器只能加載指定路徑下的類(lèi)字節(jié)碼,如果某些情況下,我們需要加載應(yīng)用程序之外的類(lèi)文件呢?就需要用到自定義類(lèi)加載器,就像是在汽車(chē)行駛的時(shí)候,為汽車(chē)更換輪子。
    • 比如本地D盤(pán)下的,或者去加載網(wǎng)絡(luò)上的某個(gè)類(lèi)文件,這種情況就可以使用自定義加載器了。
    • 舉個(gè)栗子:JRebel

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

自定義類(lèi)加載器案例:

目標(biāo):自定義類(lèi)加載器,加載指定路徑在D盤(pán)下的lib文件夾下的類(lèi)。

步驟:

  1. 新建一個(gè)需要被加載的類(lèi)Test.jave

  2. 編譯Test.jave到指定lib目錄

  3. 自定義類(lèi)加載器HeroClassLoader繼承ClassLoader:重寫(xiě)findClass()方法調(diào)用defineClass()方法

  4. 測(cè)試自定義類(lèi)加載器

實(shí)現(xiàn):

(1)新建一個(gè)Test.java 類(lèi),代碼如下:

package com.hero.jvm.classloader;
public class Test {
    public void say(){
        System.out.println("Hello HeroClassLoader");
    }
}

(2)使用javac Test.java 命令,將生成的Test.class 文件放到D:/lib/com/hero/jvm/classloader 文件夾下。

(3)自定義類(lèi)加載器,代碼如下:

package com.hero.jvm.classloader;
import java.io.*;
public class HeroClassLoader extends ClassLoader {
    private String classpath;
    
    public HeroClassLoader(String classpath) {
        this.classpath = classpath;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException{
        try {
            //輸入流,通過(guò)類(lèi)的全限定名稱(chēng)加載文件到字節(jié)數(shù)組
            byte[] classDate = getData(name);
            if (classDate != null) {
                //defineClass方法將字節(jié)數(shù)組數(shù)據(jù) 轉(zhuǎn)為 字節(jié)碼對(duì)象
                return defineClass(name, classDate, 0, classDate.length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    //加載類(lèi)的字節(jié)碼數(shù)據(jù)
    private byte[] getData(String className) throws IOException {
        String path = classpath + File.separatorChar +className.replace('.',File.separatorChar) + ".class";
        try (InputStream in = new FileInputStream(path);
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[2048];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            return out.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

(4)測(cè)試,代碼如下:

package com.hero.jvm.classloader;
import java.lang.reflect.Method;
public class TestMyClassLoader {
    public static void main(String[] args) throws Exception{
        //自定義類(lèi)加載器的加載路徑
        HeroClassLoader hClassLoader=new HeroClassLoader("D:\\lib");
        //包名+類(lèi)名
        Class c=hClassLoader.loadClass("com.hero.jvm.classloader.Test");
        if(c!=null){
            Object obj=c.newInstance();
            Method method=c.getMethod("say", null);
            method.invoke(obj, null);
            System.out.println(c.getClassLoader().toString());
        }
    }
}

輸出結(jié)果如下:

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

1.2.4 雙親委派模型與打破雙親委派

1)什么是雙親委派?

  • 當(dāng)一個(gè)類(lèi)加載器收到類(lèi)加載任務(wù),會(huì)先交給其父類(lèi)加載器去完成。因此,最終加載任務(wù)都會(huì)傳遞到頂層的啟動(dòng)類(lèi)加載器,只有當(dāng)父類(lèi)加載器無(wú)法完成加載任務(wù)時(shí),子類(lèi)才會(huì)嘗試執(zhí)行加載任務(wù)。

Oracle 官網(wǎng)文檔描述:

The Java Class Loading Mechanism

The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a “parent” class loader. When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself.

------ Oracel Document

https://docs.oracle.com/javase/tutorial/ext/basics/load.html

看到這里,應(yīng)該叫父親委派對(duì)吧?那么為什么要叫雙親委派呢,因?yàn)樽钤绲姆g者,導(dǎo)致雙親委派的概念流行起來(lái)了。

2)為什么需要雙親委派呢?

  • 考慮到安全因素,雙親委派可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類(lèi)的時(shí)候,就沒(méi)有必要子 ClassLoader再加載一次。

  • 比如:加載位于rt.jar包中的類(lèi)java.lang.Object,不管是哪個(gè)加載器加載這個(gè)類(lèi),最終都是委托給頂層的啟動(dòng)類(lèi)加載器進(jìn)行加載,這樣就保證了使用不同的類(lèi)加載器最終得到的都是同樣一個(gè)Object對(duì)象。

3)雙親委派機(jī)制源碼:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    synchronized (getClassLoadingLock(name)) {
        // 首先, 檢查class是否被加載,如果沒(méi)有加載則進(jìn)行加載
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {//如果父類(lèi)加載不為空,則交給父類(lèi)加載器加載
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
                //父類(lèi)加載器沒(méi)有加載到,則由子類(lèi)進(jìn)行加載
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

4)為什么還需要破壞雙親委派?

  • 在實(shí)際應(yīng)用中,雙親委派解決了Java基礎(chǔ)類(lèi)統(tǒng)一加載的問(wèn)題,但是卻存在著缺陷。JDK中的基礎(chǔ)類(lèi)作為典型的api被用戶調(diào)用,但是也存在api調(diào)用用戶代碼的情況,典型的如:SPI代碼。這種情況就需要打破雙親委派模式。

  • 舉個(gè)栗子:數(shù)據(jù)庫(kù)驅(qū)動(dòng)DriverManager。以Driver接口為例,Driver接口定義在JDK中,其實(shí)現(xiàn)由各個(gè)數(shù)據(jù)庫(kù)的服務(wù)商來(lái)提供,由系統(tǒng)類(lèi)加載器加載。這個(gè)時(shí)候就需要啟動(dòng)類(lèi)加載器來(lái)委托子類(lèi)來(lái)加載Driver實(shí)現(xiàn),這就破壞了雙親委派。類(lèi)似情況還有很多

5)如何破壞雙親委派?

第一種方式

在 jdk 1.2 之前,那時(shí)候還沒(méi)有雙親委派模型,不過(guò)已經(jīng)有了 ClassLoader這個(gè)抽象類(lèi),所以已經(jīng)有人繼承這個(gè)抽象類(lèi),重寫(xiě) loadClass方法來(lái)實(shí)現(xiàn)用戶自定義類(lèi)加載器。

而在 1.2 的時(shí)候要引入雙親委派模型,為了向前兼容, loadClass 這個(gè)方法還得保留著使之得以重 寫(xiě),新搞了個(gè) findClass 方法讓用戶去重寫(xiě),并呼吁大家不要重寫(xiě) loadClass只要重寫(xiě) findClass。這就是第一次對(duì)雙親委派模型的破壞,因?yàn)殡p親委派的邏輯在 loadClass 上,但是又允許重寫(xiě) loadClass,重寫(xiě)了之后就可以破壞委派邏輯了。

第二種方式:

雙親委派機(jī)制是一種自上而下的加載需求,越往上類(lèi)越基礎(chǔ)。

SPI代碼打破了雙親委派

DriverManager源碼

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            ServiceLoader<Driver> loadedDrivers =ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
                // Do nothing
            }
            return null;
        }
    });
    
    println("DriverManager.initialize: jdbc.drivers = " + drivers);
    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            //在這里需要加載各個(gè)廠商實(shí)現(xiàn)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)com.mysql.jdbc.Driver
            Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

如果出現(xiàn)SPI相關(guān)代碼時(shí),我們應(yīng)該如何解決基礎(chǔ)類(lèi)去加載用戶代碼類(lèi)呢?

這個(gè)時(shí)候,JVM不得不妥協(xié),推出線程上下文類(lèi)加載器的概念,去解決該問(wèn)題。這樣也就打破了雙親委派

線程上下文類(lèi)加載器 (ThreadContextClassLoader)

設(shè)置線程上下文類(lèi)加載器源碼

public Launcher() {
    // Create the extension class loader
    ClassLoader extcl;
    try {
        // 擴(kuò)展類(lèi)加載器
        extcl = ExtClassLoader.getExtClassLoader();
    } catch (IOException e) {
        throw new InternalError("Could not create extension class loader", e);
    }
    // Now create the class loader to use to launch the application
    try {
        // 應(yīng)用類(lèi)加載器/系統(tǒng)類(lèi)加載器
        loader = AppClassLoader.getAppClassLoader(extcl);
    } catch (IOException e) {
        throw new InternalError("Could not create application class loader", e);
    }
    // 線程上下文類(lèi)加載器
// 同時(shí)為原始線程設(shè)置上下文類(lèi)加載器
Thread.currentThread().setContextClassLoader(loader);

// 最后,如果需要,安裝安全管理器
String s = System.getProperty("java.security.manager");
if (s != null) {
    SecurityManager sm = null;
    if ("".equals(s) || "default".equals(s)) {
        sm = new java.lang.SecurityManager();
    } else {
        try {
            sm = (SecurityManager) loader.loadClass(s).newInstance();
        } catch (IllegalAccessException | InstantiationException | ClassNotFoundException | ClassCastException e) {
            // 處理異常,此處省略具體操作
        }
    }
    if (sm != null) {
        System.setSecurityManager(sm);
    } else {
        throw new InternalError("Could not create SecurityManager: " + s);
    }
}

獲取線程上下文類(lèi)加載器源碼

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

第三種方式

為了滿足熱部署、不停機(jī)更新需求。OSGI 就是利用自定義的類(lèi)加載器機(jī)制來(lái)完成模塊化熱部署,而它實(shí)現(xiàn)的類(lèi)加載機(jī)制就沒(méi)有完全遵循自下而上的委托,有很多平級(jí)之間的類(lèi)加載器查找,具體就不展開(kāi)了,有興趣可以自行研究一下。

1.3 運(yùn)行時(shí)數(shù)據(jù)區(qū)

整個(gè)JVM構(gòu)成里面,由三部分組成:類(lèi)加載系統(tǒng)、運(yùn)行時(shí)數(shù)據(jù)區(qū)、執(zhí)行引擎

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

按照線程使用情況和職責(zé)分成兩大類(lèi)

  • 線程獨(dú)享 (程序執(zhí)行區(qū)域)
    • 不需要垃圾回收
    • 虛擬機(jī)棧、本地方法棧、程序計(jì)數(shù)器
  • 線程共享 (數(shù)據(jù)存儲(chǔ)區(qū)域)
    • 垃圾回收
    • 存儲(chǔ)類(lèi)的靜態(tài)數(shù)據(jù)和對(duì)象數(shù)據(jù)
    • 堆和方法區(qū)

1.3.1 堆

Java堆在JVM啟動(dòng)時(shí)創(chuàng)建內(nèi)存區(qū)域去實(shí)現(xiàn)對(duì)象、數(shù)組與運(yùn)行時(shí)常量的內(nèi)存分配,它是虛擬機(jī)管理最大的,也是垃圾回收的主要內(nèi)存區(qū)域。

內(nèi)存劃分:

核心邏輯就是三大假說(shuō),基于程序運(yùn)行情況進(jìn)行不斷的優(yōu)化設(shè)計(jì)。

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

堆內(nèi)存為什么會(huì)存在新生代和老年代?

分代收集理論:當(dāng)前商業(yè)虛擬機(jī)的垃圾收集器,大多數(shù)都遵循了"分代收集"(Generational Collection)的理論進(jìn)行設(shè)計(jì),分代收集名為理論,實(shí)質(zhì)是一套符合大多數(shù)程序運(yùn)行實(shí)際情況的經(jīng)驗(yàn)法則,它建立在兩個(gè)分代假說(shuō)之上:

  • 弱分代假說(shuō)(Weak Generational Hypothesis):絕大多數(shù)對(duì)象都是朝生夕滅的。

  • 強(qiáng)分代假說(shuō)(Strong Generational Hypothesis):熬過(guò)越多次垃圾收集過(guò)程的對(duì)象就越難以消亡。

這兩個(gè)分代假說(shuō)共同奠定了多款常用的垃圾收集器的一致的設(shè)計(jì)原則:收集器應(yīng)該將Java堆劃分出不同的區(qū)域,然后將回收對(duì)象依據(jù)其年齡(年齡即對(duì)象熬過(guò)垃圾收集過(guò)程的次數(shù))分配到不同的區(qū)域之中存儲(chǔ)。

  • 如果一個(gè)區(qū)域中大多數(shù)對(duì)象都是朝生夕滅,難以熬過(guò)垃圾收集過(guò)程的話,那么把它們集中放在一起,每次回收時(shí)只關(guān)注如何保留少量存活而不是去標(biāo)記那些大量將要被回收的對(duì)象,就能以較低代價(jià)回收到大量的空間;

  • 如果剩下的都是難以消亡的對(duì)象,那把它們集中放在一塊,虛擬機(jī)便可以使用較低的頻率來(lái)回收這個(gè)區(qū)域。

這就同時(shí)兼顧了垃圾收集的時(shí)間開(kāi)銷(xiāo)和內(nèi)存的空間有效利用。

為什么新生代里面需要有兩個(gè)Survivor區(qū)域呢?

  • 這個(gè)咱們?cè)诶占餍」?jié)進(jìn)行解釋

內(nèi)存模型變遷:

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

JDK1.7:

  • Young 年輕區(qū) :主要保存年輕對(duì)象,分為三部分,Eden區(qū)、兩個(gè)Survivor區(qū)。

  • Tenured 年老區(qū):主要保存年長(zhǎng)對(duì)象,當(dāng)對(duì)象在Young復(fù)制轉(zhuǎn)移一定的次數(shù)后,對(duì)象就會(huì)被轉(zhuǎn)移到Tenured區(qū)。

  • Perm 永久區(qū) :主要保存class、method、filed對(duì)象,這部份的空間一般不會(huì)溢出,除非一次性加載了很多的類(lèi),不過(guò)在涉及到熱部署的應(yīng)用服務(wù)器的時(shí)候,有時(shí)候會(huì)遇到OOM : PermGen space 的錯(cuò)誤。

  • Virtual區(qū): 最大內(nèi)存和初始內(nèi)存的差值,就是Virtual區(qū)。

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

JDK1.8:

  • 由2部分組成,新生代(Eden + 2\Survivor ) + 年老代(OldGen )

  • JDK1.8中變化最大的是Perm永久區(qū)用Metaspace進(jìn)行了替換

注意:Metaspace所占用的內(nèi)存空間不是在虛擬機(jī)內(nèi)部,而是在本地內(nèi)存空間中。區(qū)別于JDK1.7

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

JDK1.9:

  • 取消新生代、老年代的物理劃分

  • 將堆劃分為若干個(gè)區(qū)域(Region),這些區(qū)域中包含了有邏輯上的新生代、老年代區(qū)域

儲(chǔ)物收納

03-JVM虛擬機(jī)-課堂筆記,jvm,筆記

內(nèi)存信息案例:

package com.hero.jvm.memory;

/**
 * -Xms100m -Xmx100m
 */
public class HeapDemo {
    public static void main(String[] args) {
        System.out.println("======start=========");
        try {
            Thread.sleep(1000000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("========end=========");
    }
}

JDK6堆內(nèi)存結(jié)構(gòu)

C:\develop\java\jdk1.6.0_45\bin\javac HeapDemo.java
C:\develop\java\jdk1.6.0_45\bin\java -Xms100m -Xmx100m HeapDemo
C:\develop\java\jdk1.6.0_45\bin\jmap -heap 3612

JDK7堆內(nèi)存結(jié)構(gòu)

C:\develop\java\jdk1.7.0_80\bin\javac HeapDemo.java
C:\develop\java\jdk1.7.0_80\bin\java -Xms100m -Xmx100m HeapDemo
C:\develop\java\jdk1.7.0_80\bin\jmap -heap 10420

JDK8堆內(nèi)存結(jié)構(gòu)

C:\develop\java\jdk1.8.0_251\bin\javac HeapDemo.java
C:\develop\java\jdk1.8.0_251\bin\java -Xms100m -Xmx100m HeapDemo
C:\develop\java\jdk1.8.0_251\bin\jmap -heap 18276

JDK11堆內(nèi)存結(jié)構(gòu)

C:\develop\java\jdk-11.0.7\bin\javac HeapDemo.java
C:\develop\java\jdk-11.0.7\bin\java -Xms100m -Xmx100m HeapDemo
C:\develop\java\jdk-11.0.7\bin\jhsdb jmap --heap --pid 19380

1.3.2 虛擬機(jī)棧

1)棧幀是什么

棧幀(Stack Frame)是用于支持虛擬機(jī)進(jìn)行方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)。

棧幀存儲(chǔ)了方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法返回地址等信息。每一個(gè)方法從調(diào)用至執(zhí)行完成的過(guò)程,都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里從入棧到出棧的過(guò)程。

棧內(nèi)存為線程私有的空間,每個(gè)線程都會(huì)創(chuàng)建私有的棧內(nèi)存,生命周期與線程相同,每個(gè)Java方法在執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)。棧內(nèi)存大小決定了方法調(diào)用的深度,棧內(nèi)存過(guò)小則會(huì)導(dǎo)致方法調(diào)用的深度較小,如遞歸調(diào)用的次數(shù)較少。

2)當(dāng)前棧幀

一個(gè)線程中方法的調(diào)用鏈可能會(huì)很長(zhǎng),所以會(huì)有很多棧幀。只有位于JVM虛擬機(jī)棧棧頂?shù)脑夭攀怯行У?,即稱(chēng)為當(dāng)前棧幀,與這個(gè)棧幀相關(guān)連的方法稱(chēng)為當(dāng)前方法,定義這個(gè)方法的類(lèi)叫做當(dāng)前類(lèi)。

執(zhí)行引擎運(yùn)行的所有字節(jié)碼指令都只針對(duì)當(dāng)前棧幀進(jìn)行操作。如果當(dāng)前方法調(diào)用了其他方法,或者當(dāng)前方法執(zhí)行結(jié)束,那這個(gè)方法的棧幀就不再是當(dāng)前棧幀了。

3)什么時(shí)候創(chuàng)建棧幀

調(diào)用新的方法時(shí),新的棧幀也會(huì)隨之創(chuàng)建。并且隨著程序控制權(quán)轉(zhuǎn)移到新方法,新的棧幀成為了當(dāng)前棧幀。方法返回之際,原棧幀會(huì)返回方法的執(zhí)行結(jié)果給之前的棧幀(返回給方法調(diào)用者),隨后虛擬機(jī)將會(huì)丟棄此棧幀。

4)棧異常的兩種情況:

如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度(Xss默認(rèn)1m),會(huì)拋出StackOverflowError異常如果在創(chuàng)建新的線程時(shí),沒(méi)有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,會(huì)拋出OutOfMemoryError異常

【不一定】

5)棧異常案例:

如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將會(huì)拋出StackOverflowError異常(-Xss);

package com.hero.jvm.memory;

public class StackErrorMock {
    private static int index = 1;
    
    public void call() {
        index++;
        call();
    }
    
    public static void main(String[] args) {
        StackErrorMock mock = new StackErrorMock();
        try {
            mock.call();
        } catch (Throwable e) {
            System.out.println("Stack deep: " + index);
            e.printStackTrace();
        }
    }
}
C:\develop\java\jdk1.8.0_251\bin\javac StackErrorMock.java
C:\develop\java\jdk1.8.0_251\bin\java -Xss1m StackErrorMock
C:\develop\java\jdk1.8.0_251\bin\java -Xss256k StackErrorMock

補(bǔ)充案例:用來(lái)演示大量創(chuàng)建線程撐爆內(nèi)存會(huì)發(fā)生什么!

思考題:如果創(chuàng)建海量線程線程的時(shí)候,同時(shí)每個(gè)線程瘋狂遞歸,請(qǐng)問(wèn)到底是先OOM還是 StackOverflowError?

public class TestThread {
    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            new Thread("Thread-" + i) {
                @Override
                public void run() {
                    try {
                        String name = Thread.currentThread().getName();
                        System.out.println(name);
                        recursive(30000);
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println();
                }
            }.start();
        }
    }

    public static void recursive(double d) {
        if (d == 0)
            return;
        recursive(d - 1);
    }
}

1.3.3 本地方法棧

本地方法棧和虛擬機(jī)棧相似,區(qū)別就是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java服務(wù)(字節(jié)碼服務(wù)),而本地方法棧為虛擬機(jī)使用到的Native方法(比如C++方法)服務(wù)。

簡(jiǎn)單地講,一個(gè)Native Method就是一個(gè)Java調(diào)用非Java代碼的接口。

public class IHaveNatives {
    native public void Native1(int x);
    native static public long Native2();
    native synchronized private float Native3(Object o);
    native void Native4(int[] ary) throws Exception;
}

為什么需要本地方法?

Java是一門(mén)高級(jí)語(yǔ)言,我們不直接與操作系統(tǒng)資源、系統(tǒng)硬件打交道。如果想要直接與操作系統(tǒng)與硬件打交道,就需要使用到本地方法了。說(shuō)白了,Java可以直接通過(guò)native方法調(diào)用cpp編寫(xiě)的接口!多線程底層就是這么實(shí)現(xiàn)的,在多線程部分我們會(huì)看一下Thread實(shí)現(xiàn)的源碼,到時(shí)候就可以理解了。

1.3.4 方法區(qū)

方法區(qū)(Method Area)是可供各個(gè)線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域,方法區(qū)本質(zhì)上是Java語(yǔ)言編譯后代碼存儲(chǔ)區(qū)域,它存儲(chǔ)每一個(gè)類(lèi)的結(jié)構(gòu)信息,例如:運(yùn)行時(shí)常量池、成員變量、方法數(shù)據(jù)、構(gòu)造方法和普通方法的字節(jié)碼指令等內(nèi)容。很多語(yǔ)言都有類(lèi)似區(qū)域。

方法區(qū)的具體實(shí)現(xiàn)有兩種:永久代(PermGen)、元空間(Metaspace)

1)方法區(qū)存儲(chǔ)什么數(shù)據(jù)?

{width=“4.999998906386701in”
height=“3.3958333333333335in”}

主要有如下三種類(lèi)型第一:Class

  1. 類(lèi)型信息,比如Class(com.hero.User類(lèi))

  2. 方法信息,比如Method(方法名稱(chēng)、方法參數(shù)列表、方法返回值信息)

  3. 字段信息,比如Field(字段類(lèi)型,字段名稱(chēng)需要特殊設(shè)置才能保存的?。?/p>

  4. 類(lèi)變量(靜態(tài)變量):JDK1.7之后,轉(zhuǎn)移到堆中存儲(chǔ)

  5. 方法表(方法調(diào)用的時(shí)候)
    在A類(lèi)的main方法中去調(diào)用B類(lèi)的method1方法,是根據(jù)B類(lèi)的方法表去查找合適的方法,進(jìn)行調(diào)用的。

第二:運(yùn)行時(shí)常量池(字符串常量池):從class中的常量池加載而來(lái),JDK1.7之后,轉(zhuǎn)移到堆中存儲(chǔ)

  • 字面量類(lèi)型

  • 引用類(lèi)型–>內(nèi)存地址

第三:JIT編譯器編譯之后的代碼緩存

如果需要訪問(wèn)方法區(qū)中類(lèi)的其他信息,都必須先獲得Class對(duì)象,才能取訪問(wèn)該Class對(duì)象關(guān)聯(lián)的方法信息或者字段信息。

2)永久代和元空間的區(qū)別是什么?

  1. JDK1.8之前使用的方法區(qū)實(shí)現(xiàn)是永久代,JDK1.8及以后使用的方法區(qū)實(shí)現(xiàn)是元空間。

  2. 存儲(chǔ)位置不同:

  • 永久代所使用的內(nèi)存區(qū)域是JVM進(jìn)程所使用的區(qū)域,它的大小受整個(gè)JVM的大小所限制。

  • 元空間所使用的內(nèi)存區(qū)域是物理內(nèi)存區(qū)域。那么元空間的使用大小只會(huì)受物理內(nèi)存大小的限制。

  1. 存儲(chǔ)內(nèi)容不同:
  • 永久代存儲(chǔ)的信息基本上就是上面方法區(qū)存儲(chǔ)內(nèi)容中的數(shù)據(jù)。

  • 元空間只存儲(chǔ)類(lèi)的元信息,而靜態(tài)變量和運(yùn)行時(shí)常量池都挪到堆中。

3)為什么要使用元空間來(lái)替換永久代?

  1. 字符串存在永久代中,容易出現(xiàn)性能問(wèn)題和永久代內(nèi)存溢出。

  2. 類(lèi)及方法的信息等比較難確定其大小,因此對(duì)于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出。

  3. 永久代會(huì)為 GC 帶來(lái)不必要的復(fù)雜度,并且回收效率偏低。

  4. Oracle 計(jì)劃將HotSpot 與 JRockit 合二為一。

方法區(qū)實(shí)現(xiàn)變遷歷史:

{width=“6.160161854768154in”
height=“3.147603893263342in”}

移除永久代的工作從JDK1.7就開(kāi)始了。JDK1.7中,存儲(chǔ)在永久代的部分?jǐn)?shù)據(jù)就已經(jīng)轉(zhuǎn)移到了Java Heap。但永久代仍存在于JDK1.7中,并沒(méi)完全移除,譬如:字面量轉(zhuǎn)移到了java heap;類(lèi)的靜態(tài)變量(class statics)轉(zhuǎn)移到了java heap。

4)字符串OOM異常案例案例代碼

以下這段程序以2的指數(shù)級(jí)不斷的生成新的字符串,這樣可以比較快速的消耗內(nèi)存:

package com.hero.jvm.memory;
import java.util.ArrayList;
import java.util.List;

public class StringOomMock {
    static String base = "string";

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            String str = base + base;
            base = str;
            list.add(str.intern());
        }
    }
}

JDK1.6

JDK 1.6 的運(yùn)行結(jié)果:{width=“5.867308617672791in”
height=“0.5523392388451444in”}

在JDK 1.6下,會(huì)出現(xiàn)永久代的內(nèi)存溢出。

JDK1.7

C:\develop\java\jdk1.7.0_80\bin\javac StringOomMock.java
C:\develop\java\jdk1.7.0_80\bin\java -XX:PermSize=8m -XX:MaxPermSize=8m -
Xmx16m StringOomMock

JDK 1.7的運(yùn)行結(jié)果:{width=“5.902015529308836in”
height=“0.9854155730533684in”}

在JDK 1.7中,會(huì)出現(xiàn)堆內(nèi)存溢出。

結(jié)論是:JDK 1.7 已經(jīng)將字符串常量由永久代轉(zhuǎn)移到堆中。

JDK1.8+

C:\develop\java\jdk1.8.0_251\bin\javac StringOomMock.java
C:\develop\java\jdk1.8.0_251\bin\java -XX:PermSize=8m -XX:MaxPermSize=8m -
Xmx16m StringOomMock

JDK 1.8的運(yùn)行結(jié)果:

{width=“5.782699037620297in”
height=“1.0887489063867017in”}

在JDK 1.8 中,也會(huì)出現(xiàn)堆內(nèi)存溢出,并且顯示 JDK 1.8中 PermSize 和 MaxPermGen 已經(jīng)無(wú)效。

結(jié)論是:可以驗(yàn)證 JDK 1.8 中已經(jīng)不存在永久代的結(jié)論。

1.3.5 字符串常量池

1)三種常量池的比較

class常量池:一個(gè)class文件只有一個(gè)class常量池

字面量:數(shù)值型(int、float、long、double)、雙引號(hào)引起來(lái)的字符串值等符號(hào)引用:Class、Method、Field等

運(yùn)行時(shí)常量池:一個(gè)class對(duì)象有一個(gè)運(yùn)行時(shí)常量池

字面量:數(shù)值型(int、float、long、double)、雙引號(hào)引起來(lái)的字符串值等符號(hào)引用:Class、Method、Field等

字符串常量池:全局只有一個(gè)字符串常量池

雙引號(hào)引起來(lái)的字符串值

{width=“6.202195975503062in”
height=“3.19in”}

2)字符串常量池如何存儲(chǔ)數(shù)據(jù)?

為了提高匹配速度, 即更快的查找某個(gè)字符串是否存在于常量池 Java 在設(shè)計(jì)字符串常量池的時(shí)候,還搞了一張StringTable, StringTable里面保存了字符串的引用。StringTable類(lèi)似于HashTable(哈希表)。在JDK1.7+,StringTable可以通過(guò)參數(shù)指定-XX:StringTableSize=99991

什么是哈希表呢?

哈希表(Hash table,也叫散列表),是根據(jù)關(guān)鍵碼值(Key value)而直接進(jìn)行訪問(wèn)的數(shù)據(jù)結(jié)構(gòu)。也就是說(shuō),它通過(guò)把關(guān)鍵碼值映射到表中一個(gè)位置來(lái)訪問(wèn)記錄,以加快查找的速度。這個(gè)映射函數(shù)叫做散列函數(shù),存放記錄的數(shù)組叫做散列表。

哈希表本質(zhì)上是一個(gè)數(shù)組+鏈表

目的 : 為了加快數(shù)據(jù)查找的速度。

存在問(wèn)題:hash沖突問(wèn)題,一旦出現(xiàn)沖突,那么就會(huì)形成鏈表,鏈表的特點(diǎn)是增刪快,但查詢(xún)慢。數(shù)組下標(biāo)計(jì)算公式:hash(字符串) % 數(shù)組長(zhǎng)度

數(shù)組中存儲(chǔ)的是Entry,通過(guò)指針next形成鏈表

{width=“4.741064085739283in”
height=“3.6678116797900264in”}

HashMap<String, Integer> map = new HashMap<>();
map.put("hello", 53);
map.put("world", 35);
map.put("java", 55);
map.put("world", 52);
map.put("通話", 51);
map.put("重地", 55);

3)字符串常量池如何查找字符串:

根據(jù)字符串的hashcode找到對(duì)應(yīng)entry如果沒(méi)有沖突,它可能只是一個(gè)entry

如何有沖突,它可能是一個(gè)entry的鏈表,然后Java再遍歷鏈表,匹配引用對(duì)應(yīng)的字符串如果找到字符串,返回引用

如果找不到字符串,在使用intern()方法的時(shí)候,會(huì)將intern()方法調(diào)用者的引用放入到stringtable中

{width=“4.354166666666667in”
height=“3.8333333333333335in”}

4)字符串常量池案例

import java.util.HashMap;

public class StringTableDemo {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("hello", 53);
        map.put("world", 35);
        map.put("java", 55);
        map.put("world", 52);
        map.put("通話", 51);
        map.put("重地", 55);
        
        test();
    }
    
    public static void test() {
        String str1 = "abc";
        String str2 = new String("abc");
        System.out.println(str1 == str2); // false
        
        String str3 = new String("abc");
        System.out.println(str3 == str2); // false
        
        String str4 = "a" + "b";
        System.out.println(str4 == "ab"); // true
        
        String s1 = "a";
        String s2 = "b";
        String str6 = s1 + s2;
        System.out.println(str6 == "ab"); // false
        
        String str7 = "abc".substring(0, 2);
        System.out.println(str7 == "ab"); // false
        
        String str8 = "abc".toUpperCase();
        System.out.println(str8 == "ABC"); // false
        
        String s5 = "a";
        String s6 = "abc";
        String s7 = s5 + "bc";
        System.out.println(s6 == s7.intern()); // true
    }
}

總結(jié):

單獨(dú)使用"“引號(hào)創(chuàng)建的字符串都是常量,編譯期就已經(jīng)確定存儲(chǔ)到String Pool中。使用new String(”")創(chuàng)建的對(duì)象會(huì)存儲(chǔ)到heap中,是運(yùn)行期新創(chuàng)建的。

使用只包含常量的字符串連接符如"aa"+"bb"創(chuàng)建的也是常量,編譯期就能確定已經(jīng)存儲(chǔ)到StringPool中。

使用包含變量的字符串連接如"aa"+s創(chuàng)建的對(duì)象是運(yùn)行期才創(chuàng)建的,存儲(chǔ)到heap中。

運(yùn)行期調(diào)用String的intern()方法可以向String Pool中動(dòng)態(tài)添加對(duì)象。

1.3.6 程序計(jì)數(shù)器

程序計(jì)數(shù)器(Program Counter Register),也叫PC寄存器,是一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼指令的行號(hào)指示器。字節(jié)碼解釋器的工作就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令。分支,循環(huán),跳轉(zhuǎn),異常處理,線程回復(fù)等都需要依賴(lài)這個(gè)計(jì)數(shù)器來(lái)完成。

為什么需要程序計(jì)數(shù)器?

由于Java虛擬機(jī)的多線程是通過(guò)線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來(lái)實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(針對(duì)多核處理器來(lái)說(shuō)是一個(gè)內(nèi)核)都只會(huì)執(zhí)行一條線程中的指令。因此,為了線程切換(系統(tǒng)上下文切換)后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ),我們稱(chēng)這類(lèi)內(nèi)存區(qū)域?yàn)?線程私有"的內(nèi)存。

存儲(chǔ)的什么數(shù)據(jù)?

如果一個(gè)線程正在執(zhí)行的是一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是一個(gè)Native方法,這個(gè)計(jì)數(shù)器的值則為空。

異常:此內(nèi)存區(qū)域是唯一一個(gè)在Java的虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError異常情況的區(qū)域。

1.3.7 直接內(nèi)存

直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java 虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。在JDK1.4 中新加入了NIO(New Input/Output)類(lèi),引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O 方式,它可以使用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ù)。

本機(jī)直接內(nèi)存的分配不會(huì)受到Java 堆大小的限制,受到本機(jī)總內(nèi)存大小限制。

直接內(nèi)存(堆外內(nèi)存)與堆內(nèi)存比較:

  • 直接內(nèi)存申請(qǐng)空間耗費(fèi)更高的性能,當(dāng)頻繁申請(qǐng)到一定量時(shí)尤為明顯

  • 直接內(nèi)存IO讀寫(xiě)的性能要優(yōu)于普通的堆內(nèi)存,在多次讀寫(xiě)操作的情況下差異明顯

直接內(nèi)存案例:

package com.hero.jvm.memory;
import java.nio.ByteBuffer;

public class ByteBufferCompare {
    public static void main(String[] args) {
        //allocateCompare(); //分配比較
        operateCompare(); //讀寫(xiě)比較
    }

    /**
    * 直接內(nèi)存和堆內(nèi)存的分配空間比較
    * 結(jié)論:在數(shù)據(jù)量提升時(shí),直接內(nèi)存相比非直接內(nèi)存的申請(qǐng),有很?chē)?yán)重的性能問(wèn)題
    */
    public static void allocateCompare() {
        int time = 1000 * 10000; //操作次數(shù),1千萬(wàn)
        long st = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            //ByteBuffer.allocate(int capacity) 分配一個(gè)新的字節(jié)緩沖區(qū)。
            ByteBuffer buffer = ByteBuffer.allocate(2); //非直接內(nèi)存分配申請(qǐng)
        }
        long et = System.currentTimeMillis();
        System.out.println("在進(jìn)行" + time + "次分配操作時(shí),堆內(nèi)存分配耗時(shí):" + (et - st) + "ms");

        long st_heap = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            //ByteBuffer.allocateDirect(int capacity) 分配新的直接字節(jié)緩沖區(qū)。
            ByteBuffer buffer = ByteBuffer.allocateDirect(2); //直接內(nèi)存分配申請(qǐng)
        }
        long et_direct = System.currentTimeMillis();
        System.out.println("在進(jìn)行" + time + "次分配操作時(shí),直接內(nèi)存分配耗時(shí):" + (et_direct- st_heap) + "ms");
    }

    /**
    * 直接內(nèi)存和堆內(nèi)存的讀寫(xiě)性能比較
    * 結(jié)論:直接內(nèi)存在直接的IO操作上,在頻繁的讀寫(xiě)時(shí)會(huì)有顯著的性能提升
    */
    public static void operateCompare() {
        int time = 10 * 10000 * 10000; //操作次數(shù),10億
        ByteBuffer buffer = ByteBuffer.allocate(2 * time);
        long st = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            // putChar(char value) 用來(lái)寫(xiě)入 char 值的相對(duì) put 方法
            buffer.putChar('a');
        }
        buffer.flip();
        for (int i = 0; i < time; i++) {
            buffer.getChar();
        }
        long et = System.currentTimeMillis();
        System.out.println("在進(jìn)行" + time + "次讀寫(xiě)操作時(shí),非直接內(nèi)存讀寫(xiě)耗時(shí):" + (et - st) + "ms");

        ByteBuffer buffer_d = ByteBuffer.allocateDirect(2 * time);
        long st_direct = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            // putChar(char value) 用來(lái)寫(xiě)入 char 值的相對(duì) put 方法
            buffer_d.putChar('a');
        }
        buffer_d.flip();
        for (int i = 0; i < time; i++) {
            buffer_d.getChar();
        }
        long et_direct = System.currentTimeMillis();
        System.out.println("在進(jìn)行" + time + "次讀寫(xiě)操作時(shí),直接內(nèi)存讀寫(xiě)耗時(shí):" + (et_direct - st_direct) + "ms");
    }
}

輸出:

在進(jìn)行10000000次分配操作時(shí),堆內(nèi)存 分配耗時(shí):82ms

在進(jìn)行10000000次分配操作時(shí),直接內(nèi)存分配耗時(shí):6817ms

在進(jìn)行1000000000次讀寫(xiě)操作時(shí),堆內(nèi)存讀寫(xiě)耗時(shí):1137ms

在進(jìn)行1000000000次讀寫(xiě)操作時(shí),直接內(nèi)存讀寫(xiě)耗時(shí):512ms

為什么會(huì)是這樣?

從數(shù)據(jù)流的角度,來(lái)看

  • 非直接內(nèi)存作用鏈:本地IO–>直接內(nèi)存–>非直接內(nèi)存–>直接內(nèi)存–>本地IO

  • 直接內(nèi)存作用鏈:本地IO–>直接內(nèi)存–>本地IO

直接內(nèi)存的使用場(chǎng)景:

  • 有很大的數(shù)據(jù)需要存儲(chǔ),它的生命周期很長(zhǎng)
  • 適合頻繁的IO操作,例如:網(wǎng)絡(luò)并發(fā)場(chǎng)景

今日總結(jié)

0 1-JVM基本常識(shí)

什么是JVM?廣義上的JVM是指一種規(guī)范,狹義上的JVM指的是Hotspot類(lèi)的虛擬機(jī)實(shí)現(xiàn)

Java語(yǔ)言與JVM的關(guān)系:Java語(yǔ)言編寫(xiě)程序生成class字節(jié)碼在JVM虛擬機(jī)里執(zhí)行。其他語(yǔ)言也可以比如Scala、Groovy

學(xué)習(xí)JVM主要學(xué)啥?類(lèi)加載子系統(tǒng) --\ 運(yùn)行時(shí)數(shù)據(jù)區(qū) --\一個(gè)對(duì)象的一生–\ GC垃圾收集器

學(xué)了JVM可以干啥?JVM調(diào)優(yōu),底層能力 決定 上層建筑

0 2-類(lèi)加載子系統(tǒng)

類(lèi)加載四個(gè)時(shí)機(jī):1.new、getstatic、putstatic、invokestatic。2.反射。3.初始化子類(lèi)發(fā)現(xiàn)父類(lèi)沒(méi)有初始化時(shí)。4.main函數(shù)的類(lèi)

類(lèi)加載主要過(guò)程:加載 -> 驗(yàn)證 -> 準(zhǔn)備 -> 解析 ->初始化 ->使用 ->卸載

類(lèi)加載主要做了三件事:

全限定名稱(chēng) ==> 二進(jìn)制字節(jié)流加載class文件字節(jié)流的靜態(tài)數(shù)據(jù)結(jié)構(gòu) ==>
方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)創(chuàng)建字節(jié)碼Class對(duì)象

可以從哪些途徑加載字節(jié)碼:

Jar、war

JSP生成的class

數(shù)據(jù)庫(kù)中二進(jìn)制字節(jié)流網(wǎng)絡(luò)中二進(jìn)制字節(jié)流

動(dòng)態(tài)代理生成的二進(jìn)制字節(jié)流

類(lèi)加載器有哪些?

啟動(dòng)類(lèi)加載器BootstrapClassLoader,擴(kuò)展類(lèi)加載器ExtensionClassLoader應(yīng)用類(lèi)加載器ApplicationClassLoader,自定義類(lèi)加載器UserClassLoader檢查順序自底向上,加載順序自頂向下

什么是雙親委派?當(dāng)一個(gè)類(lèi)加載器收到加載任務(wù),會(huì)先交給其父類(lèi)加載器去加載

為何要打破雙親委派?父類(lèi)加載器加載范圍受限,無(wú)法加載的類(lèi)需要委托子類(lèi)加載器去完成加載

0 3-運(yùn)行時(shí)數(shù)據(jù)區(qū)

堆:JVM啟動(dòng)是創(chuàng)建的最大的一塊內(nèi)存區(qū)域,對(duì)象,數(shù)組,運(yùn)行時(shí)常量池都在這里內(nèi)存劃分:Eden、2個(gè)Survivor、老年代

為什么要?jiǎng)澐中律c老年代?基于分代收集理論里的量大假說(shuō),弱分代和強(qiáng)分代假說(shuō)。提升垃圾收集的效率。

內(nèi)存模型變遷史:JDK1.7 —取消永久代,多了元空間—> JDK1.8
—取消新生代與老年代物理劃分—> JDK1.9

虛擬機(jī)棧:??臻g為線程私有,每個(gè)線程都會(huì)創(chuàng)建棧內(nèi)存,生命周期與線程相同

線程內(nèi)的棧內(nèi)存占滿了會(huì)出現(xiàn)StackOverflowError

棧幀是什么?棧幀(StackFrame)是用于支持虛擬機(jī)進(jìn)行方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)。

本地方法棧:與虛擬機(jī)棧類(lèi)似,區(qū)別在于本地方法棧為本地方法服務(wù),也就是native方法方法區(qū):方法區(qū)的實(shí)現(xiàn)有兩種:永久代(PermGen)、元空間(Metaspace)

方法區(qū)存什么數(shù)據(jù)?類(lèi)型信息,方法信息,字段信息,類(lèi)變量信息,方法表,指向類(lèi)加載器的引用,指向Class實(shí)例的引用

永久代和元空間有什么區(qū)別?

存儲(chǔ)位置不同存儲(chǔ)內(nèi)容不同

為什么要使用元空間來(lái)替換永久代?

基于性能、穩(wěn)定性、GC垃圾收集的復(fù)雜度考慮,當(dāng)然也有Oracle收購(gòu)了Java原因

字符串常量池 {#字符串常量池-1}

三種常量池:class常量池、運(yùn)行時(shí)常量池、字符串常量池

字符串常量池如何存儲(chǔ)數(shù)據(jù)?使用哈希表【哈希沖突,哈希碰撞…】字符串常量池如何查找字符串?類(lèi)似于HashMap

程序計(jì)數(shù)器

存儲(chǔ)什么數(shù)據(jù)?當(dāng)前線程執(zhí)行時(shí)的字節(jié)碼指令地址為什么需要程序計(jì)數(shù)器?因?yàn)橄到y(tǒng)的上下文切換

直接內(nèi)存

相對(duì)堆內(nèi)存,直接內(nèi)存申請(qǐng)空間更耗時(shí)

直接內(nèi)存IO讀寫(xiě)的性能要優(yōu)于普通的堆內(nèi)存文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-792760.html

到了這里,關(guān)于03-JVM虛擬機(jī)-課堂筆記的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 云計(jì)算課堂筆記——參考模型

    云計(jì)算課堂筆記——參考模型

    12.9參考模型 OSI(open system internet)七層參考模型(不管廠商怎么弄,就要按七層模型來(lái)弄(同一標(biāo)準(zhǔn))) OSI/RM(參考模型) ISO--國(guó)際公有化組織——提出網(wǎng)絡(luò)參考模型標(biāo)準(zhǔn)(統(tǒng)一標(biāo)準(zhǔn)) 分層的意義: 1.降低層次之間關(guān)聯(lián)性,上一層都在下層基礎(chǔ)上提供增值服務(wù) 2.大而化小的思

    2024年02月02日
    瀏覽(33)
  • 人工智能導(dǎo)論課堂筆記

    人工智能導(dǎo)論課堂筆記

    時(shí)間:2022年10月19日下午 班級(jí):2022級(jí)人工智能應(yīng)用技術(shù)1班 作業(yè)問(wèn)題: Python安裝注意事項(xiàng) 1.下載Python3.X的版本,如:3.10, 3.9, 3.8,不推薦下載2.7版本(已經(jīng)不使用) 2.在命令行中,無(wú)法運(yùn)行path-添加,需要知道安裝的路徑; Pycharm安裝注意: 1.官網(wǎng)下載,推薦下載免費(fèi)(社區(qū)

    2024年02月01日
    瀏覽(21)
  • 大數(shù)據(jù)課堂筆記——lianxi數(shù)據(jù)庫(kù)

    大數(shù)據(jù)課堂筆記——lianxi數(shù)據(jù)庫(kù)

    創(chuàng)建數(shù)據(jù)庫(kù) create database 庫(kù)名; create database lianxi; 打開(kāi) use 庫(kù)名; use lianxi; use table 表名; 創(chuàng)建表(主鍵:primary key ) create table 表名 (列名1 數(shù)據(jù)類(lèi)型1,……); create table course (couid char(4), couname char(10), teachername char(3) ); 刪除表 drop database 數(shù)據(jù)表名; 顯示命令運(yùn)行結(jié)果 show table

    2024年04月25日
    瀏覽(30)
  • 【云計(jì)算與大數(shù)據(jù)概述 】課堂筆記

    【云計(jì)算與大數(shù)據(jù)概述 】課堂筆記

    1.1 云計(jì)算基礎(chǔ) 1.1.1 云計(jì)算簡(jiǎn)介 云計(jì)算的技術(shù)內(nèi)容包括分布式計(jì)算技術(shù),虛擬化技術(shù),網(wǎng)絡(luò)技術(shù),服務(wù)器技術(shù),數(shù)據(jù)中心技術(shù),云計(jì)算平臺(tái)技術(shù),存儲(chǔ)技術(shù)等 云計(jì)算的定義:一種基于互聯(lián)網(wǎng)的計(jì)算方式,通過(guò)這種方式,共享的軟硬件資源和信息可以按需求提供給計(jì)算機(jī)和其他

    2024年02月06日
    瀏覽(26)
  • JSP_5.16_課堂筆記

    完整 的可以與 數(shù)據(jù)庫(kù) 連接的 登錄界面 的代碼 1、加載驅(qū)動(dòng)(告訴JDBC程序,連接的是哪一個(gè)數(shù)據(jù)庫(kù)) Class.forName (“驅(qū)動(dòng)程序名”); MySQL : com.mysql.jdbc.Driver com.mysql.cj.jdbc.Driver Oracle : oracle.jdbc.driver.OracleDriver SQL Server : com.microsoft.jdbc.sqlserver.SQLServerDriver 2、獲取和數(shù)據(jù)庫(kù)之間的連

    2024年02月05日
    瀏覽(24)
  • redis復(fù)習(xí)筆記06(小滴課堂)

    redis復(fù)習(xí)筆記06(小滴課堂)

    分布式鎖核心知識(shí)介紹和注意事項(xiàng) 基于Redis實(shí)現(xiàn)分布式鎖的幾種坑 綜合偽代碼: 運(yùn)行:

    2024年02月22日
    瀏覽(27)
  • linux復(fù)習(xí)筆記01(小滴課堂)

    linux復(fù)習(xí)筆記01(小滴課堂)

    ? ? ? ?點(diǎn)擊下一步在自定義硬件中: 我們可以刪除我們不使用的,后續(xù)如果需要再加上即可。 ? ?然后我們就可以開(kāi)啟這臺(tái)虛擬機(jī)了。 我們可以進(jìn)行下載cetos7. ? 這里選擇簡(jiǎn)體中文就可以。 ? 時(shí)間的設(shè)置。 ? 可以開(kāi)啟下網(wǎng)絡(luò)。 ? 在這里選擇設(shè)置root密碼,我設(shè)置的密碼是

    2024年02月10日
    瀏覽(18)
  • 【課堂筆記】運(yùn)籌學(xué)第二章:對(duì)偶問(wèn)題

    【課堂筆記】運(yùn)籌學(xué)第二章:對(duì)偶問(wèn)題

    聽(tīng)說(shuō)運(yùn)籌學(xué)這門(mén)課挺好的,有值得一聽(tīng)的必要;此篇用作課堂總結(jié)、期末復(fù)習(xí)及記錄。 或許與教材內(nèi)容會(huì)有很大程度重復(fù)。 本章開(kāi)始會(huì)適當(dāng)結(jié)合一些B站網(wǎng)課【運(yùn)籌學(xué)】應(yīng)試向基礎(chǔ)教程 對(duì)偶問(wèn)題的對(duì)偶問(wèn)題就是原問(wèn)題 矩陣表達(dá) 要弄清楚矩陣 A A A 和 C C C 分別是什么 最好記住

    2024年02月07日
    瀏覽(31)
  • JS課堂筆記(4.11-4.16)

    1. JavaScript(簡(jiǎn)稱(chēng)JS)是作為開(kāi)發(fā)Web頁(yè)面的腳本語(yǔ)言。 2. JS是從1995年由網(wǎng)景公司的布蘭德開(kāi)發(fā)。 3. JavaScript的標(biāo)準(zhǔn)是ECMAScript。 4. JS代碼是從上往下執(zhí)行的。 1. 變量名的值可以重復(fù)賦值(值可以修改),變量可以重復(fù)聲明。 2. JS中“+”號(hào)很特殊,只要是和字符串相加都會(huì)變成字

    2023年04月21日
    瀏覽(24)
  • 內(nèi)網(wǎng)安全“小迪安全課堂筆記”域橫向

    內(nèi)網(wǎng)安全“小迪安全課堂筆記”域橫向

    DMZ,是英文“demilitarized zone”的縮寫(xiě),中文名稱(chēng)為“隔離區(qū)”,也稱(chēng)“非軍事化區(qū)”。它是為了解決安裝防火墻后外部網(wǎng)絡(luò)的訪問(wèn)用戶不能訪問(wèn)內(nèi)部網(wǎng)絡(luò)服務(wù)器的問(wèn)題,而設(shè)立的一個(gè)非安全系統(tǒng)與安全系統(tǒng)之間的緩沖區(qū)。該緩沖區(qū)位于企業(yè)內(nèi)部網(wǎng)絡(luò)和外部網(wǎng)絡(luò)之間的小網(wǎng)絡(luò)區(qū)

    2024年02月04日
    瀏覽(23)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包