自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官:Java中實(shí)例對象存儲(chǔ)在哪?

存儲(chǔ) 存儲(chǔ)軟件
低級語言是計(jì)算機(jī)認(rèn)識的語言、高級語言是程序員認(rèn)識的語言。如何從高級語言轉(zhuǎn)換成低級語言呢?這個(gè)過程其實(shí)就是編譯。

 [[387810]]

本文轉(zhuǎn)載自微信公眾號「java寶典」,作者iTengyu。轉(zhuǎn)載本文請聯(lián)系java寶典公眾號。

目錄:

  • 理解Java編譯流程
    • 前端編譯(Front End)
    • 后端編譯(Back End)
  • 什么是JIT (Just in time)
    • 編譯器和解釋器的優(yōu)缺點(diǎn)以及實(shí)用場景
    • 熱點(diǎn)檢測算法
    • 1)基于采樣的熱點(diǎn)探測
    • 2) 基于計(jì)數(shù)器的熱點(diǎn)探測
  • 對象棧上分配的優(yōu)化
    • 逃逸分析
    • 標(biāo)量替換
    • 同步消除(鎖消除)
  • 棧上分配
  • 對象的內(nèi)存分配
  • 解決堆內(nèi)存分配的并發(fā)問題
    • CAS
    • TLAB
  • 總結(jié)

理解Java編譯流程

低級語言是計(jì)算機(jī)認(rèn)識的語言、高級語言是程序員認(rèn)識的語言。如何從高級語言轉(zhuǎn)換成低級語言呢?這個(gè)過程其實(shí)就是編譯。

不同的語言都有自己的編譯器,Java語言中負(fù)責(zé)編譯的編譯器是一個(gè)命令:javac

通過javac命令將Java程序的源代碼編譯成Java字節(jié)碼,即我們常說的.class文件。這也是我們所理解的編譯.

但是.class并不是計(jì)算機(jī)能夠識別的語言.要想讓機(jī)器能夠執(zhí)行,需要把字節(jié)碼再翻譯成機(jī)器指令,這個(gè)過程是JVM來完成的.這個(gè)過程也叫編譯.只是層次更深..

因此我們了解到,編譯器可劃分為前端(Front End)與后端(Back End)。

 

我們可以把將.java文件編譯成.class的編譯過程稱之為前端編譯。把將.class文件翻譯成機(jī)器指令的編譯過程稱之為后端編譯。

前端編譯(Front End)

前端編譯主要指與源語言有關(guān)但與目標(biāo)機(jī)無關(guān)的部分,包括詞法分析、語法分析、語義分析與中間代碼生成。

例如我們使用很多的IDE,如eclipse,idea等,都內(nèi)置了前端編譯器。主要功能就是把.java代碼轉(zhuǎn)換成`.class字節(jié)碼

后端編譯(Back End)

后端編譯主要指與目標(biāo)機(jī)有關(guān)的部分,包括代碼優(yōu)化和目標(biāo)代碼生成等。

在后端編譯中,通常都經(jīng)過前端編譯的處理,已經(jīng)加工成.class字節(jié)碼文件了 JVM通過解釋字節(jié)碼將其逐條讀入并翻譯為對應(yīng)機(jī)器指令,讀一條翻譯一條,勢必是分產(chǎn)生效率問題因此引入了JIT(just in time)

什么是JIT (Just in time)

當(dāng)JVM發(fā)現(xiàn)某個(gè)方法或代碼塊運(yùn)行特別頻繁的時(shí)候,就會(huì)認(rèn)為這是“熱點(diǎn)代碼”(Hot Spot Code)。JIT會(huì)把部分“熱點(diǎn)代碼”翻譯成本地機(jī)器相關(guān)的機(jī)器碼,并進(jìn)行優(yōu)化,然后緩存起來,以備下次使用

在HotSpot虛擬機(jī)中內(nèi)置了兩個(gè)JIT編譯器分別是:

  1. - Client complier  [客戶端] 
  2. - Server complier  [服務(wù)端] 

目前JVM中默認(rèn)都是采用: 解釋器+一個(gè)JIT編譯器 配合的方式進(jìn)行工作 即混合模式

下圖是我機(jī)器上安裝的JDK ,可以看出,使用的JIT是Server Complier, 解釋器和JIT的工作方式是mixed mode

 

面試題:為何HotSpot虛擬機(jī)要實(shí)現(xiàn)兩個(gè)不同的即時(shí)編譯器?

HotSpot虛擬機(jī)中內(nèi)置了兩個(gè)即時(shí)編譯器:Client Complier和Server Complier,簡稱為C1、C2編譯器,分別用在客戶端和服務(wù)端。目前主流的HotSpot虛擬機(jī)中默認(rèn)是采用解釋器與其中一個(gè)編譯器直接配合的方式工作。程序使用哪個(gè)編譯器,取決于虛擬機(jī)運(yùn)行的模式。HotSpot虛擬機(jī)會(huì)根據(jù)自身版本與宿主機(jī)器的硬件性能自動(dòng)選擇運(yùn)行模式,用戶也可以使用“-client”或“-server”參數(shù)去強(qiáng)制指定虛擬機(jī)運(yùn)行在Client模式或Server模式。

用Client Complier獲取更高的編譯速度,用Server Complier 來獲取更好的編譯質(zhì)量。和為什么提供多個(gè)垃圾收集器類似,都是為了適應(yīng)不同的應(yīng)用場景。

編譯器和解釋器的優(yōu)缺點(diǎn)以及實(shí)用場景

在JVM執(zhí)行代碼時(shí),它并不是馬上開始編譯代碼,當(dāng)一段經(jīng)常被執(zhí)行的代碼被編譯后,下次運(yùn)行就不用重復(fù)編譯,此時(shí)使用JIT是劃算的,但是它也不是萬能的,比如說一些極少執(zhí)行的代碼在編譯時(shí)花費(fèi)的時(shí)間比解釋器還久,這時(shí)就是得不償失了

所以,解釋器和JIT各有千秋:

解釋器與編譯器兩者各有優(yōu)勢:當(dāng)程序需要迅速啟動(dòng)和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用,省去編譯的時(shí)間,立即執(zhí)行。在程序運(yùn)行后,隨著時(shí)間的推移,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后,可以獲取更高的執(zhí)行效率。

當(dāng)極少執(zhí)行或者執(zhí)行次數(shù)較少的JAVA代碼使用解釋器最優(yōu).

當(dāng)重復(fù)執(zhí)行或者執(zhí)行次數(shù)較多的JAVA代碼使用JIT更劃算.

熱點(diǎn)檢測算法

要想觸發(fā)JIT,首先需要識別出熱點(diǎn)代碼。目前主要的熱點(diǎn)代碼識別方式是熱點(diǎn)探測(Hot Spot Detection),有以下兩種:

1)基于采樣的熱點(diǎn)探測

采用這種方法的虛擬機(jī)會(huì)周期性地檢查各個(gè)線程的棧頂,如果發(fā)現(xiàn)某些方法經(jīng)常出現(xiàn)在棧頂,那這個(gè)方法就是“熱點(diǎn)方法”。這種探測方法的好處是實(shí)現(xiàn)簡單高效,還可以很容易地獲取方法調(diào)用關(guān)系(將調(diào)用堆棧展開即可),缺點(diǎn)是很難精確地確認(rèn)一個(gè)方法的熱度,容易因?yàn)槭艿骄€程阻塞或別的外界因素的影響而擾亂熱點(diǎn)探測。

2) 基于計(jì)數(shù)器的熱點(diǎn)探測

采用這種方法的虛擬機(jī)會(huì)為每個(gè)方法(甚至是代碼塊)建立計(jì)數(shù)器,統(tǒng)計(jì)方法的執(zhí)行次數(shù),如果執(zhí)行次數(shù)超過一定的閥值,就認(rèn)為它是“熱點(diǎn)方法”。這種統(tǒng)計(jì)方法實(shí)現(xiàn)復(fù)雜一些,需要為每個(gè)方法建立并維護(hù)計(jì)數(shù)器,而且不能直接獲取到方法的調(diào)用關(guān)系,但是它的統(tǒng)計(jì)結(jié)果相對更加精確嚴(yán)謹(jǐn)。

那么在HotSpot虛擬機(jī)中使用的是哪個(gè)熱點(diǎn)檢測方式呢?

在HotSpot虛擬機(jī)中使用的是第二種,基于計(jì)數(shù)器的熱點(diǎn)探測方法,因此它為每個(gè)方法準(zhǔn)備了兩個(gè)計(jì)數(shù)器:

>1 方法調(diào)用計(jì)數(shù)器

顧名思義,就是記錄一個(gè)方法被調(diào)用次數(shù)的計(jì)數(shù)器。

>2 回邊計(jì)數(shù)器

是記錄方法中的for或者while的運(yùn)行次數(shù)的計(jì)數(shù)器。

在確定虛擬機(jī)運(yùn)行參數(shù)的前提下,這兩個(gè)計(jì)數(shù)器都有一個(gè)確定的閾值,當(dāng)計(jì)數(shù)器超過閾值溢出了,就會(huì)觸發(fā)JIT編譯。

對象棧上分配的優(yōu)化

逃逸分析逃逸分析是一種有效減少JAVA程序中 同步負(fù)載 和 堆內(nèi)存分配壓力 的分析算法.Hotspot編譯器能夠分析出一個(gè)新的對象的引用的使用范圍從而決定是否要將這個(gè)對象分配到棧上.

  1. public static StringBuffer method(String s1, String s2) { 
  2.     StringBuffer sb = new StringBuffer(); 
  3.     sb.append("關(guān)注"); 
  4.     sb.append("java寶典"); 
  5.     return sb; 
  6.    //此時(shí)sb對象從method方法逃出.. 
  1. public static String method(String s1, String s2) { 
  2.     StringBuffer sb = new StringBuffer(); 
  3.     sb.append("關(guān)注"); 
  4.     sb.append("java寶典"); 
  5.     return sb.toString(); 
  6.    //此時(shí)sb對象 沒有離開 作用域 
  1. public void globalVariableEscape(){ 
  2.     globalVariableObject = new Object(); //靜態(tài)變量,外部線程可見,發(fā)生逃逸 
  3.  
  4. public void instanceObjectEscape(){ 
  5.     instanceObject = new Object(); //賦值給堆中實(shí)例字段,外部線程可見,發(fā)生逃逸 

public void globalVariableEscape(){ globalVariableObject = new Object(); //靜態(tài)變量,外部線程可見,發(fā)生逃逸 } public void instanceObjectEscape(){ instanceObject = new Object(); //賦值給堆中實(shí)例字段,外部線程可見,發(fā)生逃逸 }

在確定對象不會(huì)逃逸后,JIT將可以進(jìn)行以下優(yōu)化: 標(biāo)量替換 同步消除 棧上分配

第一段代碼中的sb就逃逸了,而第二段代碼中的sb就沒有逃逸。

在Java代碼運(yùn)行時(shí),通過JVM參數(shù)可指定是否開啟逃逸分析,

-XX:+DoEscapeAnalysis :表示開啟逃逸分析

-XX:-DoEscapeAnalysis :表示關(guān)閉逃逸分析

-XX:+PrintEscapeAnalysis 開啟打印逃逸分析篩選結(jié)果

從jdk 1.7開始已經(jīng)默認(rèn)開始逃逸分析

標(biāo)量替換

允許將對象打散分配在棧上,比如若一個(gè)對象擁有兩個(gè)字段,會(huì)將這兩個(gè)字段視作局部變量進(jìn)行分配。

逸分析只是棧上內(nèi)存分配的前提,還需要進(jìn)行標(biāo)量替換才能真正實(shí)現(xiàn)。例:

  1. public static void main(String[] args) throws Exception { 
  2.     long start = System.currentTimeMillis(); 
  3.     for (int i = 0; i < 10000; i++) { 
  4.         allocate(); 
  5.     } 
  6.     System.out.println((System.currentTimeMillis() - start) + " ms"); 
  7.     Thread.sleep(10000); 
  8. public static void allocate() { 
  9.     MyObject myObject = new MyObject(2019, 2019.0); 
  10. public static class MyObject { 
  11.     int a; 
  12.     double b; 
  13.     MyObject(int a, double b) { 
  14.         this.a = a; 
  15.         this.b = b; 
  16.     } 

標(biāo)量,就是指JVM中無法再細(xì)分的數(shù)據(jù),比如int、long、reference等。相對地,能夠再細(xì)分的數(shù)據(jù)叫做聚合量

Java虛擬機(jī)中的原始數(shù)據(jù)類型(int,long等數(shù)值類型以及reference類型等)都不能再進(jìn)一步分解,它們就可以稱為標(biāo)量。相對的,如果一個(gè)數(shù)據(jù)可以繼續(xù)分解,那它稱為聚合量,Java中最典型的聚合量是對象

如果逃逸分析證明一個(gè)對象不會(huì)被外部訪問,并且這個(gè)對象是可分解的,那程序真正執(zhí)行的時(shí)候?qū)⒖赡懿粍?chuàng)建這個(gè)對象,而改為直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用到的成員變量來代替。拆散后的變量便可以被單獨(dú)分析與優(yōu)化,可以各自分別在棧幀或寄存器上分配空間,原本的對象就無需整體分配空間了

仍然考慮上面的例子,MyObject就是一個(gè)聚合量,因?yàn)樗蓛蓚€(gè)標(biāo)量a、b組成。通過逃逸分析,JVM會(huì)發(fā)現(xiàn)myObject沒有逃逸出allocate()方法的作用域,標(biāo)量替換過程就會(huì)將myObject直接拆解成a和b,也就是變成了:

  1. static void allocate() { 
  2.     int a = 2019; 
  3.     double b = 2019.0; 

可見,對象的分配完全被消滅了,而int、double都是基本數(shù)據(jù)類型,直接在棧上分配就可以了。所以,在對象不逃逸出作用域并且能夠分解為純標(biāo)量表示時(shí),對象就可以在棧上分配

  • 開啟標(biāo)量替換 (-XX:+EliminateAllocations)

標(biāo)量替換的作用是允許將對象根據(jù)屬性打散后分配在棧上,默認(rèn)該配置為開啟

同步消除(鎖消除)

如果同步塊所使用的鎖對象通過逃逸分析被證實(shí)只能夠被一個(gè)線程訪問,那么JIT編譯器在編譯這個(gè)同步塊的時(shí)候就會(huì)取消對這部分代碼的同步。這個(gè)取消同步的過程就叫同步省略,也叫鎖消除

例子:

  1. public void f() { 
  2.     Object java_bible = new Object(); 
  3.     synchronized(java_bible) { 
  4.         System.out.println(java_bible); 
  5.     } 

在經(jīng)過逃逸分析后,JIT編譯階段會(huì)被優(yōu)化成:

  1. public void f() { 
  2.     Object java_bible = new Object(); 
  3.     System.out.println(java_bible);  //鎖被去掉了. 

如果JIT經(jīng)過逃逸分析之后發(fā)現(xiàn)并無線程安全問題的話,就會(huì)做鎖消除。

棧上分配

通過逃逸分析,我們發(fā)現(xiàn),許多對象的生命周期會(huì)隨著方法的調(diào)用開始而開始,方法的調(diào)用結(jié)束而結(jié)束,很多的對象的作用域都不會(huì)逃逸出方法外,對于此種對象,我們可以考慮使用棧上分配,而不是在堆中分配.

因?yàn)橐坏┓峙湓诙芽臻g中,當(dāng)方法調(diào)用結(jié)束,沒有了引用指向該對象,該對象就需要被gc回收,而如果存在大量的這種情況,對gc來說反而是一種負(fù)擔(dān)。

JVM提供了一種叫做棧上分配的概念,針對那些作用域不會(huì)逃逸出方法的對象,在分配內(nèi)存時(shí)不在將對象分配在堆內(nèi)存中,而是將對象屬性打散后分配在棧(線程私有的,屬于棧內(nèi)存,標(biāo)量替換)上,這樣,隨著方法的調(diào)用結(jié)束,??臻g的回收就會(huì)隨著將棧上分配的打散后的對象回收掉,不再給gc增加額外的無用負(fù)擔(dān),從而提升應(yīng)用程序整體的性能

那么問題來了,如果棧上分配失敗了怎么辦?

對象的內(nèi)存分配

創(chuàng)建個(gè)對象有多種方法: 比如 使用new , reflect , clone 不管使用哪種 ,我們都要先分配內(nèi)存

我們拿new 來舉個(gè)例子:

  1. T t = new T() 
  2.    
  3. class T{ 
  4.  int m = 8; 
  5.  
  6. //javap 
  7.    
  8. 0 new #2<T>  //new作用在內(nèi)存申請開辟一塊空間 new完之后m的值為 0 
  9. 3 dup  
  10. 4 invokespecial #3 <T.<init>>   
  11. 7 astore_1  
  12. return 

那么它是怎么分配的呢?

當(dāng)我們使用new創(chuàng)建對象后代碼開始運(yùn)行后,虛擬機(jī)執(zhí)行到這條new指令的時(shí)候,會(huì)先檢查要new的對象對應(yīng)的類是否已被加載,如果沒有被加載則先進(jìn)行類加載,檢查通過之后,就需要給對象進(jìn)行內(nèi)存分配,分配的內(nèi)存主要用來存放對象的實(shí)例變量

為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來

根據(jù)內(nèi)存連續(xù)和不連續(xù)的情況,JVM使用不同的分配方式.

  • 連續(xù): 指針碰撞
  • 不連續(xù):空閑列表

指針碰撞(Serial、ParNew等帶Compact過程的收集器) 假設(shè)Java堆中內(nèi)存是絕對規(guī)整的,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對象大小相等的距離,這種分配方式稱為“指針碰撞”(Bump the Pointer)。

空閑列表(CMS這種基于Mark-Sweep算法的收集器) 如果Java堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),那就沒有辦法簡單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對象實(shí)例,并更新列表上的記錄,這種分配方式稱為“空閑列表”(Free List)。

無論那種方式,最終都需要確定出一塊內(nèi)存區(qū)域,用于給新建對象分配內(nèi)存。對象的內(nèi)存分配過程中,主要是對象的引用指向這個(gè)內(nèi)存區(qū)域,然后進(jìn)行初始化操作,那么在并發(fā)場景之中,如果多線程并發(fā)去堆中獲取內(nèi)存區(qū)域,怎么保證內(nèi)存分配的線程安全性.

解決堆內(nèi)存分配的并發(fā)問題

保證分配過程中的線程安全有兩種方式:

  • CAS
  • TLAB

CAS

CAS:采用CAS機(jī)制,配合失敗重試的方式保證線程安全性

CAS對于內(nèi)存的控制是使用重試機(jī)制,因此效率比較低,目前JVM使用的是TLAB方式,我們著重介紹TLAB.

TLAB

TLAB:每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存,然后再給對象分配內(nèi)存的時(shí)候,直接在自己這塊"私有"內(nèi)存中分配,當(dāng)這部分區(qū)域用完之后,再分配新的"私有"內(nèi)存,注意這個(gè)私有對于創(chuàng)建對象時(shí)是私有的,但是對于讀取是共享的.

TLAB (Thread local allcation buffer ) 在“分配”這個(gè)動(dòng)作上是線程獨(dú)占的,至于在讀取、垃圾回收等動(dòng)作上都是線程共享的。在對象的創(chuàng)建時(shí),首先嘗試進(jìn)行棧上分配,如果分配失敗,會(huì)使用TLAB嘗試分配,如果失敗查看是否是大對象,如果是大對象直接進(jìn)入老年代,否則進(jìn)入新生代(Eden).這里我總結(jié)了一張流程圖,如下:

 

我們可以總結(jié)出: 創(chuàng)建大對象和創(chuàng)建多個(gè)小對象相比,多個(gè)小對象的效率更高

不知道大家有沒有注意到,TLAB分配空間,每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存,他們在堆中去搶地盤的時(shí)候,也會(huì)出現(xiàn)并發(fā)問題,但是對于TLAB的同步控制和我們直接在堆中分配相比效率高了不少(不至于因?yàn)橐峙湟粋€(gè)對象而鎖住整個(gè)堆了).

總結(jié)

為了保證Java對象的內(nèi)存分配的安全性,同時(shí)提升效率,每個(gè)線程在Java堆中可以預(yù)先分配一小塊內(nèi)存,這部分內(nèi)存稱之為TLAB(Thread Local Allocation Buffer),這塊內(nèi)存的分配時(shí)線程獨(dú)占的,讀取、使用、回收是線程共享的。

虛擬機(jī)是否使用TLAB 可以通過 -XX:+/-UseTLAB 參數(shù)指定

 

責(zé)任編輯:武曉燕 來源: java寶典
相關(guān)推薦

2022-05-24 08:03:28

InnoDBMySQL數(shù)據(jù)

2021-05-20 08:54:16

Go面向對象

2023-02-16 07:30:38

引用計(jì)數(shù)算法

2021-09-07 10:44:33

Java 注解開發(fā)

2023-12-26 09:34:47

系統(tǒng)MongoDB存儲(chǔ)

2022-05-23 08:43:02

BigIntJavaScript內(nèi)置對象

2022-07-15 08:22:42

對象符串鍵Symbol

2024-06-04 09:02:03

2015-08-13 10:29:12

面試面試官

2024-02-22 15:36:23

Java內(nèi)存模型線程

2024-03-28 10:37:44

IoC依賴注入依賴查找

2021-06-02 09:42:29

Node. js全局對象

2023-02-16 08:10:40

死鎖線程

2020-09-22 07:52:32

Java對象數(shù)組

2020-10-24 15:50:54

Java值傳遞代碼

2021-06-29 09:47:34

ReactSetState機(jī)制

2021-07-06 07:27:45

React元素屬性

2024-09-26 00:00:05

2021-03-11 08:51:00

存儲(chǔ)面試位置

2024-11-14 09:29:38

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號