最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JVM常量池的深入講解

 更新時間:2021年04月17日 08:03:09   作者:怎么起個名就那么難  
這篇文章主要給大家介紹了關(guān)于JVM常量池的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

提示:這里咱們要說的常量池,常量池就是咱們面試中所說的常量池,談?wù)勀銓ΤA砍氐恼J(rèn)識?面試官一問咱們就懵逼了,你要記得你腦子中有一張圖?。。?剩下的就好辦了

提示:請各位大佬批評指正??!

前言

提示:學(xué)習(xí)的時候會有點頭疼哦

一、Class常量池與運行時常量池

Class常量池可以理解為是Class文件中的資源倉庫。 Class文件中除了包含類的版本、字段、方法、接口等描述信息外,還有一項信息就是 常量池(constant pool table) ,用于存放編譯期生成的各種 字面量(Literal)和符號引用(Symbolic References)。

還是回到前面說的class文件的16進(jìn)制大體結(jié)構(gòu)如下圖:

在這里插入圖片描述

對應(yīng)的含義如下,細(xì)節(jié)可以查下oracle官方文檔

在這里插入圖片描述

當(dāng)然我們一般不會去人工解析這種16進(jìn)制的字節(jié)碼文件,我們一般可以通過javap命令生成更可讀的JVM字節(jié)碼指令文件:

javap -v Test.class

public class com.qjc.construction.Test
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#27         // java/lang/Object."<init>":()V
   #2 = Class              #28            // com/qjc/construction/Test
   #3 = Methodref          #2.#27         // com/qjc/construction/Test."<init>":()V
   #4 = Methodref          #2.#29         // com/qjc/construction/Test.test:()I
   #5 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = Methodref          #32.#33        // java/io/PrintStream.println:(Ljava/lang/Object;)V
   #7 = Class              #34            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/qjc/construction/Test;
  #15 = Utf8               test
  #16 = Utf8               ()I
  #17 = Utf8               a
  #18 = Utf8               I
  #19 = Utf8               b
  #20 = Utf8               c
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Utf8               args
  #24 = Utf8               [Ljava/lang/String;
  #25 = Utf8               SourceFile
  #26 = Utf8               Test.java
  #27 = NameAndType        #8:#9          // "<init>":()V
  #28 = Utf8               com/qjc/construction/Test
  #29 = NameAndType        #15:#16        // test:()I
  #30 = Class              #35            // java/lang/System
  #31 = NameAndType        #36:#37        // out:Ljava/io/PrintStream;
  #32 = Class              #38            // java/io/PrintStream
  #33 = NameAndType        #39:#40        // println:(Ljava/lang/Object;)V
  #34 = Utf8               java/lang/Object
  #35 = Utf8               java/lang/System
  #36 = Utf8               out
  #37 = Utf8               Ljava/io/PrintStream;
  #38 = Utf8               java/io/PrintStream
  #39 = Utf8               println
  #40 = Utf8               (Ljava/lang/Object;)V

Constant pool: 就是class常量池信息,常量池中主要存放兩大類常量:字面量和符號引用。

字面量

字面量就是指由字母、數(shù)字等構(gòu)成的字符串或者數(shù)值常量

字面量只可以右值出現(xiàn),所謂右值是指等號右邊的值,如:int a=1 這里的a為左值,1為右值。在這個例子中1就是字面量。

int a = 1;
int b = 2;
int c = "abcdefg";
int d = "abcdefg";

符號引用

符號引用是編譯原理中的概念,是相對于直接引用來說的。主要包括了以下三類常量:

  • 類和接口的全限定名
  • 字段的名稱和描述符
  • 方法的名稱和描述符

上面的a,b就是字段名稱,就是一種符號引用,還有Test類常量池里的 Lcom/qjc/construction/Test; 是類的全限定名,main和上面的a,b就是字段名稱,就是一種符號引用,還有Test類常量池里的 Lcom/qjc/construction/Test; 是類的全限定名,main和test是方法名稱,()是一種UTF8格式的描述符,這些都是符號引用。

這些常量池現(xiàn)在是靜態(tài)信息,只有到運行時被加載到內(nèi)存后,這些符號才有對應(yīng)的內(nèi)存地址信息,這些常量池一旦被裝入內(nèi)存就變成運行時常量池,對應(yīng)的符號引用在程序加載或運行時會被轉(zhuǎn)變?yōu)楸患虞d到內(nèi)存區(qū)域的代碼的直接引用,也就是我們說的動態(tài)鏈接了。例如,test()這個符號引用在運行時就會被轉(zhuǎn)變?yōu)閠est()方法具體代碼在內(nèi)存中的地址,主要通過對象頭里的類型指針去轉(zhuǎn)換直接引用。是方法名稱,()是一種UTF8格式的描述符,這些都是符號引用。

這些常量池現(xiàn)在是靜態(tài)信息,只有到運行時被加載到內(nèi)存后,這些符號才有對應(yīng)的內(nèi)存地址信息,這些常量池一旦被裝入內(nèi)存就變成運行時常量池,對應(yīng)的符號引用在程序加載或運行時會被轉(zhuǎn)變?yōu)楸患虞d到內(nèi)存區(qū)域的代碼的直接引用,也就是我們說的動態(tài)鏈接了。例如,test()這個符號引用在運行時就會被轉(zhuǎn)變?yōu)閠est()方法具體代碼在內(nèi)存中的地址,主要通過對象頭里的類型指針去轉(zhuǎn)換直接引用。

 二、字符串常量池

字符串常量池的設(shè)計思想

1.字符串的分配,和其他的對象分配一樣,耗費高昂的時間與空間代價,作為最基礎(chǔ)的數(shù)據(jù)類型,大量頻繁的創(chuàng)建字符串,極大程度地影響程序的性能

2.JVM為了提高性能和減少內(nèi)存開銷,在實例化字符串常量的時候進(jìn)行了一些優(yōu)化

  • 為字符串開辟一個字符串常量池,類似于緩存區(qū)
  • 創(chuàng)建字符串常量時,首先查詢字符串常量池是否存在該字符串
  • 存在該字符串,返回引用實例,不存在,實例化該字符串并放入池中
  • 三種字符串操作(Jdk1.7 及以上版本)
  • 直接賦值字符串

三種字符串操作(Jdk1.7 及以上版本)直接賦值字符串

String s = "qjc";  // s指向常量池中的引用

在這里插入圖片描述

這種方式創(chuàng)建的字符串對象,只會在常量池中。

因為有"qjc"這個字面量,創(chuàng)建對象s的時候,JVM會先去常量池中通過 equals(key) 方法,判斷是否有相同的對象。如果有,則直接返回該對象在常量池中的引用;如果沒有,則會在常量池中創(chuàng)建一個新對象,再返回引用。

new String();

String s1 = new String("qjc");  // s1指向內(nèi)存中的對象引用

在這里插入圖片描述

這種方式會保證字符串常量池和堆中都有這個對象,沒有就創(chuàng)建,最后返回堆內(nèi)存中的對象引用。

步驟大致如下:

因為有"qjc"這個字面量,所以會先檢查字符串常量池中是否存在字符串"qjc"

不存在,先在字符串常量池里創(chuàng)建一個字符串對象;再去內(nèi)存中創(chuàng)建一個字符串對象"qjc";

存在的話,就直接去堆內(nèi)存中創(chuàng)建一個字符串對象"qjc";

最后,將內(nèi)存中的引用返回。

intern方法

String s1 = new String("qjc");   
String s2 = s1.intern();

System.out.println(s1 == s2);  //false

在這里插入圖片描述

String中的intern方法是一個 native 的方法,當(dāng)調(diào)用 intern方法時,如果池已經(jīng)包含一個等于此String對象的字符串(用equals(oject)方法確定),則返回池中的字符串。否則,將intern返回的引用指向當(dāng)前字符串 s1(jdk1.6版本需要將 s1 復(fù)制到字符串常量池里)。

字符串常量池位置

Jdk1.6及之前: 有永久代, 運行時常量池在永久代,運行時常量池包含字符串常量池

Jdk1.7:有永久代,但已經(jīng)逐步“去永久代”,字符串常量池從永久代里的運行時常量池分離到堆里

Jdk1.8及之后: 無永久代,運行時常量池在元空間,字符串常量池里依然在堆里

用一個程序證明下字符串常量池在哪里:

/**
 * @author qijianchun
 * @title: TestPool
 * @projectName 
 * @description: TODO JDK8:-Xms6M -Xmx6M -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
 * @description: TODO JDK6:-Xms6M -Xmx6M -XX:PermSize=6M -XX:MaxPermSize=6M
 * @date 2021/4/1313:02
 */
public class TestPool {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000000; i++) {
            String str = String.valueOf(i).intern();
            list.add(str);
        }
    }
}

結(jié)果:

運行結(jié)果:
jdk7及以上:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
jdk6:Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

字符串常量池設(shè)計原理

字符串常量池底層是hotspot的C++實現(xiàn)的,底層類似一個 HashTable, 保存的本質(zhì)上是字符串對象的引用。

看一道比較常見的面試題,下面的代碼創(chuàng)建了多少個 String 對象?

String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
 
System.out.println(s1 == s2);
// 在 JDK 1.6 下輸出是 false,創(chuàng)建了 6 個對象
// 在 JDK 1.7 及以上的版本輸出是 true,創(chuàng)建了 5 個對象
// 當(dāng)然我們這里沒有考慮GC,但這些對象確實存在或存在過

為什么輸出會有這些變化呢?主要還是字符串池從永久代中脫離、移入堆區(qū)的原因, intern() 方法也相應(yīng)發(fā)生了變化:

1、在 JDK 1.6 中,調(diào)用 intern() 首先會在字符串池中尋找 equal() 相等的字符串,假如字符串存在就返回該字符串在字符串池中的引用;假如字符串不存在,虛擬機(jī)會重新在永久代上創(chuàng)建一個實例,將 StringTable 的一個表項指向這個新創(chuàng)建的實例。

在這里插入圖片描述

2、在 JDK 1.7 (及以上版本)中,由于字符串池不在永久代了,intern() 做了一些修改,更方便地利用堆中的對象。字符串存在時和 JDK 1.6一樣,但是字符串不存在時不再需要重新創(chuàng)建實例,可以直接指向堆上的實例。

在這里插入圖片描述

在這里插入圖片描述

由上看出也不難理解為什么 JDK 1.6 字符串池溢出會拋出 OutOfMemoryError: PermGen space ,而在 JDK 1.7 及以上版本拋出 OutOfMemoryError: Java heap space 。

String常量池問題的幾個例子

示例1:

String s0="qjc";
String s1="qjc";
String s2="qj" + "c";
System.out.println( s0==s1 ); //true
System.out.println( s0==s2 ); //true

在這里插入圖片描述

分析:因為例子中的 s0和s1中的”qjc”都是字符串常量,它們在編譯期就被確定了,所以s0==s1為true;而”qj”和”c”也都是字符串常量,當(dāng)一個字 符串由多個字符串常量連接而成時,它自己肯定也是字符串常量,所以s2也同樣在編譯期就被優(yōu)化為一個字符串常量"qjc",所以s2也是常量池中” qjc”的一個引用。所以我們得出s0s1s2;

示例2:

String s0="qjc";
String s1=new String("qjc");
String s2="qj" + new String("c");
System.out.println( s0==s1 );// false
System.out.println( s0==s2 );// false
System.out.println( s1==s2 );// false

在這里插入圖片描述

分析:用new String() 創(chuàng)建的字符串不是常量,不能在編譯期就確定,所以new String() 創(chuàng)建的字符串不放入常量池中,它們有自己的地址空間。

s0還是常量池 中"qjc”的引用,s1因為無法在編譯期確定,所以是運行時創(chuàng)建的新對象”qjc”的引用,s2因為有后半部分 new String(”c”)所以也無法在編譯期確定,所以也是一個新創(chuàng)建對象”qjc”的引用;明白了這些也就知道為何得出此結(jié)果了。

示例3:

String a = "a1";
String b = "a" + 1;
System.out.println(a == b); // true

String a1 = "atrue";
String b1 = "a" + "true";
System.out.println(a1 == b1); // true

String a2 = "a3.4";
String b2 = "a" + 3.4;
System.out.println(a2 == b2); // true

在這里插入圖片描述

分析:JVM對于字符串常量的"+“號連接,將在程序編譯期,JVM就將常量字符串的”+“連接優(yōu)化為連接后的值,拿"a” + 1來說,經(jīng)編譯器優(yōu)化后在class中就已經(jīng)是a1。在編譯期其字符串常量的值就確定下來,故上面程序最終的結(jié)果都為true。在編譯時就確定了,然后放入常量池

示例4:

String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println(a == b); // false

分析:JVM對于字符串引用,由于在字符串的"+“連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的,即"a” + bb無法被編譯器優(yōu)化,只有在程序運行期來動態(tài)分配并將連接后的新地址賦給b。所以上面程序的結(jié)果也就為false。

示例5:

String a = "ab";
final String bb = "b";
String b = "a" + bb;

System.out.println(a == b); // true

分析:和示例4中唯一不同的是bb字符串加了final修飾,對于final修飾的變量,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節(jié)碼流中。所以此時的"a" + bb和"a" + "b"效果是一樣的。故上面程序的結(jié)果為true。

示例6:

String a = "ab";
final String bb = getBB();
String b = "a" + bb;

System.out.println(a == b); // false

private static String getBB() 
{  
    return "b";  
 }

分析:JVM對于字符串引用bb,它的值在編譯期無法確定,只有在程序運行期調(diào)用方法后,將方法的返回值和"a"來動態(tài)連接并分配地址為b,故上面 程序的結(jié)果為false。

關(guān)于String是不可變的

通過上面例子可以得出得知:

String  s  =  "a" + "b" + "c";  //就等價于String s = "abc";
String  a  =  "a";
String  b  =  "b";
String  c  =  "c";
String  s1  =   a  +  b  +  c;

s1 這個就不一樣了,可以通過觀察其JVM指令碼發(fā)現(xiàn)s1的"+"操作會變成如下操作:

StringBuilder temp = new StringBuilder();
temp.append(a).append(b).append(c);
String s = temp.toString(); 

因為調(diào)用toString方法就會 newString

在這里插入圖片描述

這可就不一樣了 new String

再看一個例子:

//字符串常量池:"計算機(jī)"和"技術(shù)"     堆內(nèi)存:str1引用的對象"計算機(jī)技術(shù)"  
//堆內(nèi)存中還有個StringBuilder的對象,但是會被gc回收,StringBuilder的toString方法會new String(),這個String才是真正返回的對象引用
String str2 = new StringBuilder("計算機(jī)").append("技術(shù)").toString();   //沒有出現(xiàn)"計算機(jī)技術(shù)"字面量,所以不會在常量池里生成"計算機(jī)技術(shù)"對象
System.out.println(str2 == str2.intern());  //true
//"計算機(jī)技術(shù)" 在池中沒有,但是在heap中存在,則intern時,會直接返回該heap中的引用
//字符串常量池:"ja"和"va"     堆內(nèi)存:str1引用的對象"java"  
//堆內(nèi)存中還有個StringBuilder的對象,但是會被gc回收,StringBuilder的toString方法會new String(),這個String才是真正返回的對象引用
String str1 = new StringBuilder("ja").append("va").toString();    //沒有出現(xiàn)"java"字面量,所以不會在常量池里生成"java"對象
System.out.println(str1 == str1.intern());  //false
//java是關(guān)鍵字,在JVM初始化的相關(guān)類里肯定早就放進(jìn)字符串常量池了

String s1=new String("test");  
System.out.println(s1==s1.intern());   //false
//"test"作為字面量,放入了池中,而new時s1指向的是heap中新生成的string對象,s1.intern()指向的是"test"字面量之前在池中生成的字符串對象

String s2=new StringBuilder("abc").toString();
System.out.println(s2==s2.intern());  //false
//同上

八種基本類型的包裝類和對象池

java中基本類型的包裝類的大部分都實現(xiàn)了常量池技術(shù)(嚴(yán)格來說應(yīng)該叫對象池,在堆上),這些類是**Byte,Short,Integer,Long,Character,Boolean,**另外兩種浮點數(shù)類型的包裝類則沒有實現(xiàn)。另外Byte,Short,Integer,Long,Character這5種整型的包裝類也只是在對應(yīng)值小于等于127時才可使用對象池,也即對象不負(fù)責(zé)創(chuàng)建和管理大于127的這些類的對象。因為一般這種比較小的數(shù)用到的概率相對較大。

        //5種整形的包裝類Byte,Short,Integer,Long,Character的對象,
        //在值小于127時可以使用對象池
        Integer i1 = 127;  //這種調(diào)用底層實際是執(zhí)行的Integer.valueOf(127),里面用到了IntegerCache對象池
        Integer i2 = 127;
        System.out.println(i1 == i2);//輸出true
       
       //值大于127時,不會從對象池中取對象
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);//輸出false
 	   //用new關(guān)鍵詞新生成對象不會使用對象池
        Integer i5 = new Integer(127);
        Integer i6 = new Integer(127);
        System.out.println(i5 == i6);//輸出false
     

在這里插入圖片描述
在這里插入圖片描述

Boolean

        //Boolean類也實現(xiàn)了對象池技術(shù)
        Boolean bool1 = true;
        Boolean bool2 = true;
        System.out.println(bool1 == bool2);//輸出true

Double

       //浮點類型的包裝類沒有實現(xiàn)對象池技術(shù)
        Double d1 = 1.0;
        Double d2 = 1.0;
        System.out.println(d1 == d2);//輸出false

總結(jié)

到此這篇關(guān)于JVM常量池的文章就介紹到這了,更多相關(guān)JVM常量池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用Spring Boot Mybatis 搞反向工程的步驟

    使用Spring Boot Mybatis 搞反向工程的步驟

    這篇文章主要介紹了使用Spring Boot Mybatis 搞反向工程的步驟,幫助大家更好的理解和使用spring boot框架,感興趣的朋友可以了解下
    2021-01-01
  • 深入了解Java中Synchronized的各種使用方法

    深入了解Java中Synchronized的各種使用方法

    在Java當(dāng)中synchronized關(guān)鍵字通常是用來標(biāo)記一個方法或者代碼塊。本文將通過示例為大家詳細(xì)介紹一下Synchronized的各種使用方法,需要的可以參考一下
    2022-08-08
  • MyBatis中通用SQL寫法的幾種方法實現(xiàn)

    MyBatis中通用SQL寫法的幾種方法實現(xiàn)

    本文介紹了MyBatis中常見的幾種通用SQL寫法,包括批量操作、動態(tài)SQL、多條件分支查詢、SQL語句優(yōu)化、自動生成主鍵、注解方式以及高級映射等,感興趣的可以了解一下
    2024-12-12
  • java顯示目錄文件列表和刪除目錄功能

    java顯示目錄文件列表和刪除目錄功能

    這篇文章主要介紹了java顯示目錄文件列表和刪除目錄功能,文章通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2017-12-12
  • AQS加鎖機(jī)制Synchronized相似點詳解

    AQS加鎖機(jī)制Synchronized相似點詳解

    這篇文章主要為大家介紹了AQS加鎖機(jī)制Synchronized相似點詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • Java嵌入式開發(fā)的優(yōu)勢及有點總結(jié)

    Java嵌入式開發(fā)的優(yōu)勢及有點總結(jié)

    在本篇內(nèi)容里小編給大家整理了關(guān)于Java嵌入式開發(fā)的優(yōu)勢及相關(guān)知識點內(nèi)容,有興趣的朋友們學(xué)習(xí)下。
    2022-11-11
  • MyBatis中模糊查詢使用CONCAT('%',#{str},'%')出錯的解決

    MyBatis中模糊查詢使用CONCAT('%',#{str},'%')出錯的解

    這篇文章主要介紹了MyBatis中模糊查詢使用CONCAT('%',#{str},'%')出錯的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • springcloud教程之zuul路由網(wǎng)關(guān)的實現(xiàn)

    springcloud教程之zuul路由網(wǎng)關(guān)的實現(xiàn)

    這篇文章主要介紹了springcloud教程之zuul路由網(wǎng)關(guān)的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • Java中ArrayList和SubList的坑面試題

    Java中ArrayList和SubList的坑面試題

    集合是Java開發(fā)日常開發(fā)中經(jīng)常會使用到的,下面這篇文章主要給大家介紹了關(guān)于Java中ArrayList和SubList的坑面試題,需要的朋友可以參考下
    2022-05-05
  • MyBatis-Plus簡介和快速入門教程

    MyBatis-Plus簡介和快速入門教程

    MyBatis-Plus是一個MyBatis的增強(qiáng)工具,在MyBatis的基礎(chǔ)上只做增強(qiáng)不做修改,為簡化開發(fā)、提高效率而生,本文給大家介紹MyBatis-Plus簡介和快速入門教程,需要的朋友參考下吧
    2021-09-09

最新評論

米林县| 湟中县| 教育| 揭阳市| 大城县| 海门市| 玛曲县| 仙居县| 上林县| 卢湾区| 舒城县| 晋宁县| 兰坪| 桐乡市| 连城县| 夏津县| 株洲市| 宁波市| 高雄县| 马尔康县| 十堰市| 汉寿县| 西平县| 阿克| 宜良县| 调兵山市| 天峻县| 遂平县| 中卫市| 张家港市| 陇西县| 尼木县| 玉溪市| 安平县| 菏泽市| 蒲城县| 老河口市| 威海市| 军事| 晋宁县| 清徐县|