亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入了解Java虛擬機(jī)棧以及內(nèi)存模型

 更新時(shí)間:2021年04月14日 11:21:36   作者:chun_soft  
這篇文章主要介紹了深入了解Java虛擬機(jī)棧以及內(nèi)存模型,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有很大的幫助,需要的朋友可以參考下

1、結(jié)合字節(jié)碼指令理解Java虛擬機(jī)棧和棧幀

棧幀:每個(gè)棧幀對應(yīng)一個(gè)被調(diào)用的方法,可以理解為一個(gè)方法的運(yùn)行空間。

每個(gè)棧幀中包括局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)、指向運(yùn)行時(shí)常量池的引用(A reference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。

局部變量表:方法中定義的局部變量以及方法的參數(shù)存放在這張表中,局部變量表中的變量不可直接使用,如需要使用的話,必須通過相關(guān)指令將其加載至操作數(shù)棧中作為操作數(shù)使用。

操作數(shù)棧:以壓棧和出棧的方式存儲(chǔ)操作數(shù)的。

動(dòng)態(tài)鏈接:每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過程中的動(dòng)態(tài)連接(Dynamic Linking)。

方法返回地址:當(dāng)一個(gè)方法開始執(zhí)行后,只有兩種方式可以退出,一種是遇到方法返回的字節(jié)碼指令;一種是遇見異常,并且這個(gè)異常沒有在方法體內(nèi)得到處理。

在這里插入圖片描述

class Person{
    private String name="Jack";
    private int age;
    private final double salary=100;
    private static String address;
    private final static String hobby="Programming";
   
    public void say(){
        System.out.println("person say..."); 
    }
    
    public static int calc(int op1,int op2){
        op1=3;
        int result=op1+op2;
        return result;
    }
    
    public static void order(){
    }
    
    public static void main(String[] args){
        calc(1,2);
        order(); 
    }
}
Compiled from "Person.java" class Person {
...
  public static int calc(int, int);
    Code:
        0: iconst_3 //將int類型常量3壓入[操作數(shù)棧]
        1: istore_0 //將int類型值存入[局部變量0]
        2: iload_0  //從[局部變量0]中裝載int類型值入棧
        3: iload_1  //從[局部變量1]中裝載int類型值入棧
        4: iadd //將棧頂元素彈出棧,執(zhí)行int類型的加法,結(jié)果入棧
        【For example, the iadd instruction (§iadd) adds two int values together. It requires that the int values to be added be the top two values of the operand stack, pushed there by previous instructions. Both of the int values are popped from the operand stack. They are added, and their sum is pushed back onto the operand stack. Subcomputations may be nested on the operand stack, resulting in values that can be used by the encompassing computation.】
        5: istore_2 //將棧頂int類型值保存到[局部變量2]中
        6: iload_2  //從[局部變量2]中裝載int類型值入棧
        7: ireturn  //從方法中返回int類型的數(shù)據(jù)
...
}

在這里插入圖片描述

2、深入分析

2.1 棧指向堆

如果在棧幀中有一個(gè)變量,類型為引用類型,比如 Object obj=new Object(),這時(shí)候就是典型的棧中元素指向堆中的對象。

在這里插入圖片描述

2.2 方法區(qū)指向堆

方法區(qū)中會(huì)存放靜態(tài)變量,常量等數(shù)據(jù)。如果是下面這種情況,就是典型的方法區(qū)中元素指向堆中的對象。

private static Object obj=new Object();

在這里插入圖片描述

2.3 堆指向方法區(qū)

方法區(qū)中會(huì)包含類的信息,堆中會(huì)有對象,那怎么知道對象是哪個(gè)類創(chuàng)建的呢?

在這里插入圖片描述

思考:一個(gè)對象怎么知道它是由哪個(gè)類創(chuàng)建出來的?怎么記錄?這就需要了解一個(gè)Java對象的具體信息咯。

2.4 Java對象內(nèi)存布局

一個(gè)Java對象在內(nèi)存中包括3個(gè)部分:對象頭、實(shí)例數(shù)據(jù)和對齊填充。

在這里插入圖片描述

3、內(nèi)存模型

 3.1 圖解

一塊是非堆區(qū),一塊是堆區(qū)。
堆區(qū)分為兩大塊,一個(gè)是Old區(qū),一個(gè)是Young區(qū)。 Young區(qū)分為兩大塊,一個(gè)是Survivor區(qū)(S0+S1),一塊是Eden區(qū)。 Eden:S0:S1=8:1:1 S0和S1一樣大,也可以叫From和To。

在這里插入圖片描述

根據(jù)之前對于Heap的介紹可以知道,一般對象和數(shù)組的創(chuàng)建會(huì)在堆中分配內(nèi)存空間,關(guān)鍵是堆中有這么多區(qū)域,那一個(gè)對象的創(chuàng)建到底在哪個(gè)區(qū)域呢?

3.2 對象創(chuàng)建所在區(qū)域

一般情況下,新創(chuàng)建的對象都會(huì)被分配到Eden區(qū),一些特殊的大的對象會(huì)直接分配到Old區(qū)。
比如有對象A,B,C等創(chuàng)建在Eden區(qū),但是Eden區(qū)的內(nèi)存空間肯定有限,比如有100M,假如已經(jīng)使用了 100M 或者達(dá)到一個(gè)設(shè)定的臨界值,這時(shí)候就需要對Eden內(nèi)存空間進(jìn)行清理,即垃圾收集(Garbage Collect), 這樣的GC我們稱之為Minor GC,Minor GC指的是Young區(qū)的GC。

經(jīng)過GC之后,有些對象就會(huì)被清理掉,有些對象可能還存活著,對于存活著的對象需要將其復(fù)制到Survivor 區(qū),然后再清空Eden區(qū)中的這些對象。

3.3 Survivor區(qū)詳解

由圖解可以看出,Survivor區(qū)分為兩塊S0和S1,也可以叫做From和To。 在同一個(gè)時(shí)間點(diǎn)上,S0和S1只能有一個(gè)區(qū)有數(shù)據(jù),另外一個(gè)是空的。

接著上面的GC來說,比如一開始只有Eden區(qū)和From中有對象,To中是空的。 此時(shí)進(jìn)行一次GC操作,F(xiàn)rom區(qū)中對象的年齡就會(huì)+1,我們知道Eden區(qū)中所有存活的對象會(huì)被復(fù)制到To區(qū),
From區(qū)中還能存活的對象會(huì)有兩個(gè)去處。

若對象年齡達(dá)到之前設(shè)置好的年齡閾值,此時(shí)對象會(huì)被移動(dòng)到Old區(qū),􏰀􏰁Eden􏰂􏰃From􏰂沒有達(dá)到閾值的 對象會(huì)被復(fù)制到To區(qū)。 此時(shí)Eden區(qū)和From區(qū)已經(jīng)被清空(被GC的對象肯定沒了,沒有被GC的對象都有了各自的去處)。
這時(shí)候From和To交換角色,之前的From變成了To,之前的To變成了From。 也就是說無論如何都要保證名為To的Survivor區(qū)域是空的。
Minor GC會(huì)一直重復(fù)這樣的過程,直到To區(qū)被填滿,然后會(huì)將所有對象復(fù)制到老年代中。

3.4 Old區(qū)詳解

從上面的分析可以看出,一般Old區(qū)都是年齡比較大的對象,或者相對超過了某個(gè)閾值的對象。

在Old區(qū)也會(huì)有GC的操作,Old區(qū)的GC我們稱作為Major GC。

3.5 對象的一輩子理解

我是一個(gè)普通的Java對象,我出生在Eden區(qū),在Eden區(qū)我還看到和我長的很像的小兄弟,我們在Eden區(qū)中玩了挺長時(shí)間。有 一天Eden區(qū)中的人實(shí)在是太多了,我就被迫去了Survivor區(qū)的“From”區(qū),自從去了Survivor區(qū),我就開始漂了,有時(shí)候在 Survivor的“From”區(qū),有時(shí)候在Survivor的“To”區(qū),居無定所。直到我18歲的時(shí)候,爸爸說我成人了,該去社會(huì)上闖闖了。于是我就去了年老代那邊,年老代里,人很多,并且年齡都挺大的,我在這里也認(rèn)識(shí)了很多人。在年老代里,我生活了20年(每次 GC加一歲),然后被回收。

在這里插入圖片描述

3.6 常見問題

  •  如何理解Minor/Major/Full GC

Minor GC:新生代

Major GC:老年代

Full GC:新生代+老年代

  • 為什么需要Survivor區(qū)?只有Eden不行嗎?

如果沒有Survivor,Eden區(qū)每進(jìn)行一次Minor GC,存活的對象就會(huì)被送到老年代。 這樣一來,老年代很快被填滿,觸發(fā)Major GC(因?yàn)镸ajor GC一般伴隨著Minor GC,也可以看做觸發(fā)了Full GC)。 老年代的內(nèi)存空間遠(yuǎn)大于新生代,進(jìn)行一次Full GC消耗的時(shí)間比Minor GC長得多。執(zhí)行時(shí)間長有什么壞處?頻發(fā)的Full GC消耗的時(shí)間很長,會(huì)影響大型程序的執(zhí)行和響應(yīng)速度。

可能你會(huì)說,那就對老年代的空間進(jìn)行增加或者較少咯。假如增加老年代空間,更多存活對象才能填滿老年代。雖然降低Full GC頻率,但是隨著老年代空間加大,一旦發(fā)生Full GC,執(zhí)行所需要的時(shí)間更長。

假如減少老年代空間,雖然Full GC所需時(shí)間減少,但是老年代很快被存活對象填滿,F(xiàn)ull GC頻率增加。

所以Survivor的存在意義,就是減少被送到老年代的對象,進(jìn)而減少Full GC的發(fā)生,Survivor的預(yù)篩選保證,只有經(jīng)歷16 次Minor GC還能在新生代中存活的對象,才會(huì)被送到老年代。

  • 為什么需要兩個(gè)Survivor區(qū)?

最大的好處就是解決了碎片化。也就是說為什么一個(gè)Survivor區(qū)不行?第一部分中,我們知道了必須設(shè)置Survivor區(qū)。假設(shè),現(xiàn)在只有一個(gè)Survivor區(qū),我們來模擬一下流程:

剛剛新建的對象在Eden中,一旦Eden滿了,觸發(fā)一次Minor GC,Eden中的存活對象就會(huì)被移動(dòng)到Survivor區(qū)。這樣繼續(xù)循環(huán)下去,下一次Eden滿了的時(shí)候,問題來了,此時(shí)進(jìn)行Minor GC,Eden和Survivor各有一些存活對象,如果此時(shí)把Eden區(qū)的,存活對象硬放到Survivor區(qū),很明顯這兩部分對象所占有的內(nèi)存是不連續(xù)的,也就導(dǎo)致了內(nèi)存碎片化。
永遠(yuǎn)有一個(gè)Survivor space是空的,另一個(gè)非空的Survivor space無碎片。

  • 新生代中Eden:S1:S2為什么是8:1:1?

新生代中的可用內(nèi)存:復(fù)制算法用來擔(dān)保的內(nèi)存為9:1

可用內(nèi)存中Eden:S1區(qū)為8:1

即新生代中Eden:S1:S2 = 8:1:1

4、驗(yàn)證

4.1 堆內(nèi)存溢出

程序:

@RestController
public class HeapController {
    List<Person> list=new ArrayList<Person>();
    @GetMapping("/heap")
    public String heap() throws Exception{
        while(true){
            list.add(new Person()); 
            Thread.sleep(1);
        } 
    }
}

記得設(shè)置參數(shù)比如-Xmx20M -Xms20M

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

Exception in thread "http-nio-8080-exec-2" java.lang.OutOfMemoryError: GC overhead limit exceeded

4.2 方法區(qū)內(nèi)存溢出

比如向方法區(qū)中添加Class的信息

4.2.1 asm依賴和Class代碼

<dependency> 
    <groupId>asm</groupId> 
    <artifactId>asm</artifactId> 
    <version>3.3.1</version>
</dependency>
public class MyMetaspace extends ClassLoader {
    public static List<Class<?>> createClasses() {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        for (int i = 0; i < 10000000; ++i) {
            ClassWriter cw = new ClassWriter(0);
            cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
            "java/lang/Object", null);
            MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
            "()V", null, null);
            mw.visitVarInsn(Opcodes.ALOAD, 0); mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
            "<init>", "()V"); mw.visitInsn(Opcodes.RETURN);
            mw.visitMaxs(1, 1);
            mw.visitEnd();
            Metaspace test = new Metaspace();
            byte[] code = cw.toByteArray();
            Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length); 
            classes.add(exampleClass);
        }
        return classes;
    }
}

4.2.2 代碼

@RestController
public class NonHeapController {
    List<Class<?>> list=new ArrayList<Class<?>>();
    @GetMapping("/nonheap")
    public String nonheap() throws Exception{
        while(true){ 
        list.addAll(MyMetaspace.createClasses()); 
        Thread.sleep(5);
        } 
    }
}

設(shè)置Metaspace的大小,比如-XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M

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

java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_191]
at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_191]

4.3 虛擬機(jī)棧

 4.3.1 代碼演示StackOverFlow

public class StackDemo {
    public static long count=0;
    public static void method(long i){
        System.out.println(count++);
        method(i); 
    }
    public static void main(String[] args) {
        method(1);
    }
}

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

在這里插入圖片描述

4.3.2 理解和說明

Stack Space用來做方法的遞歸調(diào)用時(shí)壓入Stack Frame(棧幀)。所以當(dāng)遞歸調(diào)用太深的時(shí)候,就有可能耗盡Stack Space,爆出StackOverflow的錯(cuò)誤。

-Xss128k:設(shè)置每個(gè)線程的堆棧大小。JDK 5以后每個(gè)線程堆棧大小為1M,以前每個(gè)線程堆棧大小為256K。根據(jù)應(yīng)用的線程所需內(nèi)存大小進(jìn)行調(diào)整。在相同物理內(nèi)存下,減小這個(gè)值能生成更多的線程。但是操作系統(tǒng)對一個(gè)進(jìn)程內(nèi)的線程數(shù)還是有限制的,不能無限生成,經(jīng)驗(yàn)值在3000~5000左右。

線程棧的大小是個(gè)雙刃劍,如果設(shè)置過小,可能會(huì)出現(xiàn)棧溢出,特別是在該線程內(nèi)有遞歸、大的循環(huán)時(shí)出現(xiàn)溢出的可能性更大,如果該值設(shè)置過大,就有影響到創(chuàng)建棧的數(shù)量,如果是多線程的應(yīng)用,就會(huì)出現(xiàn)內(nèi)存溢出的錯(cuò)誤。

到此這篇關(guān)于深入了解Java虛擬機(jī)棧以及內(nèi)存模型的文章就介紹到這了,更多相關(guān)java虛擬機(jī)棧和內(nèi)存模型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java適配器模式之如何靈活應(yīng)對不匹配的接口

    Java適配器模式之如何靈活應(yīng)對不匹配的接口

    本文介紹了Java中的適配器模式,包括對象適配器模式和接口適配器模式,適配器模式通過將一個(gè)類的接口轉(zhuǎn)換成客戶期望的另一個(gè)接口,解決了不同接口之間的不兼容問題,它提高了系統(tǒng)的靈活性、復(fù)用性和解耦性,需要的朋友可以參考下
    2024-10-10
  • Spring boot通過切面,實(shí)現(xiàn)超靈活的注解式數(shù)據(jù)校驗(yàn)過程

    Spring boot通過切面,實(shí)現(xiàn)超靈活的注解式數(shù)據(jù)校驗(yàn)過程

    這篇文章主要介紹了Spring boot通過切面,實(shí)現(xiàn)超靈活的注解式數(shù)據(jù)校驗(yàn)過程,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Spring Boot非Web項(xiàng)目運(yùn)行配置的方法教程

    Spring Boot非Web項(xiàng)目運(yùn)行配置的方法教程

    這篇文章主要介紹了Spring Boot非Web項(xiàng)目運(yùn)行配置的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • java利用正則表達(dá)式處理特殊字符的方法實(shí)例

    java利用正則表達(dá)式處理特殊字符的方法實(shí)例

    這篇文章主要給大家介紹了關(guān)于java利用正則表達(dá)式處理特殊字符的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • Java中四種線程池的使用示例詳解

    Java中四種線程池的使用示例詳解

    這篇文章主要給大家介紹了關(guān)于Java中四種線程池的使用方法,四種線程池分別包括FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor,文中給出了詳細(xì)的示例代碼供大家參考,需要的朋友們下面來一起看看吧。
    2017-08-08
  • Java構(gòu)建JDBC應(yīng)用程序的實(shí)例操作

    Java構(gòu)建JDBC應(yīng)用程序的實(shí)例操作

    在本篇文章里小編給大家整理了一篇關(guān)于Java構(gòu)建JDBC應(yīng)用程序的實(shí)例操作,有興趣的朋友們可以學(xué)習(xí)參考下。
    2021-03-03
  • J2SE基礎(chǔ)之下載eclipse并創(chuàng)建項(xiàng)目

    J2SE基礎(chǔ)之下載eclipse并創(chuàng)建項(xiàng)目

    本文給大家介紹的是最流行的java 集成開發(fā)環(huán)境IDE eclipse的使用方法,非常的簡單,有需要的小伙伴可以參考下
    2016-05-05
  • spring-boot-maven-plugin?配置有啥用

    spring-boot-maven-plugin?配置有啥用

    這篇文章主要介紹了spring-boot-maven-plugin?配置是干啥的,這個(gè)是SpringBoot的Maven插件,主要用來打包的,通常打包成jar或者war文件,本文通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08
  • java如何獲取指定文件夾下的所有文件名

    java如何獲取指定文件夾下的所有文件名

    這篇文章主要介紹了java如何獲取指定文件夾下的所有文件名問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Spring MVC項(xiàng)目中l(wèi)og4J和AOP使用詳解

    Spring MVC項(xiàng)目中l(wèi)og4J和AOP使用詳解

    項(xiàng)目日志記錄是項(xiàng)目開發(fā)、運(yùn)營必不可少的內(nèi)容,有了它可以對系統(tǒng)有整體的把控,出現(xiàn)任何問題都有蹤跡可尋。下面這篇文章主要給大家介紹了關(guān)于Spring MVC項(xiàng)目中l(wèi)og4J和AOP使用的相關(guān)資料,需要的朋友可以參考下。
    2017-12-12

最新評論