国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

首頁 Java Java基礎(chǔ) JVM學(xué)習(xí)之 Java內(nèi)存結(jié)構(gòu)

JVM學(xué)習(xí)之 Java內(nèi)存結(jié)構(gòu)

Feb 01, 2021 pm 05:41 PM

JVM學(xué)習(xí)之 Java內(nèi)存結(jié)構(gòu)

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

  • 1.JVM概述
  • 2.程序計(jì)數(shù)器
    • 2.1.定義
    • 2.2.作用及特點(diǎn)解釋
  • 3.虛擬機(jī)棧
    • 3.1.棧的特點(diǎn)
    • 3.2.棧的演示
    • 3.3.棧的問題辨析
    • 3.4.棧的線程安全問題
    • 3.5.棧內(nèi)存溢出(StackOverflowError)
    • 3.6.線程運(yùn)行診斷
      • 3.6.1.案例1:cpu占用過多(linux系統(tǒng)為例)
      • 3.6.2.案例2:線程診斷_遲遲得不到結(jié)果
  • 4.本地方法棧
  • 5.堆
    • 5.1.定義
    • 5.2.堆內(nèi)存溢出(OutOfMemoryError:Java heap space)
    • 5.3.堆內(nèi)存診斷
  • 6.方法區(qū)
    • 6.1.定義
    • 6.2.定義
    • 6.3.方法區(qū)內(nèi)存溢出(OutOfMemoryError: Metaspace)
    • 6.4.常量池

1.JVM概述

定義:
JVM全稱是Java Virtual Machine-java程序的運(yùn)行環(huán)境(java二進(jìn)制字節(jié)碼的運(yùn)行環(huán)境)

好處:

  • 一次編寫,到處運(yùn)行(跨平臺(tái))
  • 自動(dòng)內(nèi)存管理,垃圾回收功能
  • 數(shù)組下標(biāo)越界檢查
  • 多態(tài)

比較JVM,JRE,JDK之間的聯(lián)系和區(qū)別,我們可以用一張圖來解釋
在這里插入圖片描述
JVM體系結(jié)構(gòu)如圖所示
在這里插入圖片描述
一個(gè)類從Java源代碼(.java文件)編譯成了Java二進(jìn)制字節(jié)碼以后,必須經(jīng)過類加載器才能被加載到JVM里面才能運(yùn)行。
我們一般把類放在方法區(qū)里。類將來創(chuàng)建的對象放在堆的部分,而堆里面的對象在調(diào)用方法時(shí)會(huì)用到虛擬機(jī)棧和程序計(jì)數(shù)器以及本地方發(fā)展。
方法執(zhí)行時(shí)每行代碼是由執(zhí)行引擎中的解釋器逐行進(jìn)行執(zhí)行的。方法里面的熱點(diǎn)代碼也就是頻繁調(diào)用的代碼,由即時(shí)編譯器來編譯執(zhí)行。GC會(huì)對垃圾進(jìn)行回收。
我們可以通過本地方法接口來調(diào)用操作系統(tǒng)提供的功能。

JVM的內(nèi)存結(jié)構(gòu)包括:
1.方法區(qū)
2.程序計(jì)數(shù)器
3.虛擬機(jī)棧
4.本地方法棧
5.堆

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

2.1.定義

Program Counter Register程序計(jì)數(shù)器(寄存器)
作用:
??是記住下一條jvm指令的執(zhí)行地址
特點(diǎn)
??是線程私有的
??不會(huì)存在內(nèi)存溢出
(內(nèi)存結(jié)構(gòu)中唯一一個(gè)不會(huì)內(nèi)存溢出的結(jié)構(gòu))

在2.2中我們將會(huì)解釋程序計(jì)數(shù)器的作用及特點(diǎn)。

2.2.作用及特點(diǎn)解釋

?二進(jìn)制字節(jié)碼			JVM指令					Java源代碼?0:?getstatic?????#20?????????????????//?PrintStream?out?=?System.out;?
?3:?astore_1??????????????????????????//?-?
?4:?aload_1???????????????????????????//?out.println(1);?
?5:?iconst_1??????????????????????????//?-?
?6:?invokevirtual?#26?????????????????//?-?
?9:?aload_1???????????????????????????//?out.println(2);?
?10:?iconst_2??????????????????????????//?-
?11:?invokevirtual?#26?????????????????//?-
?14:?aload_1???????????????????????????//?out.println(3);?
?15:?iconst_3??????????????????????????//?-
?16:?invokevirtual?#26?????????????????//?-
?19:?aload_1???????????????????????????//?out.println(4);?
?20:?iconst_4??????????????????????????//?-
?21:?invokevirtual?#26?????????????????//?-
?24:?aload_1???????????????????????????//?out.println(5);?
?25:?iconst_5??????????????????????????//?-
?26:?invokevirtual?#26?????????????????//?-
?29:?return

我們可以看到這些代碼,第一行System.out賦值給了一個(gè)變量,在4:中去調(diào)用println()方法。然后依次打印1,2,3,4,5。這些指令不能直接交給CPU來執(zhí)行,必須經(jīng)過解釋器的作用。它負(fù)責(zé)把一條一條的字節(jié)碼指令解釋成機(jī)器碼,然后機(jī)器碼就可以交給CPU來執(zhí)行。
也就是
二進(jìn)制字節(jié)碼->解釋器->機(jī)器碼->CPU
實(shí)際上程序計(jì)數(shù)器的作用就是在指令的執(zhí)行過程中,記住下一條JVM指令的執(zhí)行地址。
上面我們二進(jìn)制字節(jié)碼前面的數(shù)字0,3,4…我們可以把其理解為地址。根據(jù)這些地址信息,我們就可以找到命令來執(zhí)行。
在每次拿到指令交給CPU執(zhí)行之后,程序計(jì)數(shù)器就會(huì)把下一條指令的地址放入到程序計(jì)數(shù)器中,等一條指令執(zhí)行完成之后,解釋器就會(huì)到程序計(jì)數(shù)器中取到下一條指令的地址。再把其經(jīng)過解釋器解釋成機(jī)器碼然后交給CPU執(zhí)行。然后一直重復(fù)這樣的過程。
在物理上,實(shí)現(xiàn)程序計(jì)數(shù)器是通過寄存器來實(shí)現(xiàn)的。寄存器是CPU組件里讀取最快的存儲(chǔ)單元。

程序計(jì)數(shù)器是線程私有的
假如說上述代碼都在線程1中運(yùn)行,同時(shí)運(yùn)行的還有線程2和線程3,多個(gè)線程運(yùn)行的時(shí)候,CPU會(huì)給每個(gè)線程分配時(shí)間片,給線程1分配時(shí)間片,如果線程1在指定的時(shí)間沒有運(yùn)行完,它就會(huì)把狀態(tài)暫存,切換到線程2,線程2執(zhí)行自己的代碼。線程2執(zhí)行完了,再繼續(xù)執(zhí)行線程1的代碼,在線程切換的過程中,我們要記住下一條指令的執(zhí)行地址。就需要用到程序計(jì)數(shù)器。假如說線程1剛開始執(zhí)行到第9行代碼,恰好這個(gè)時(shí)候時(shí)間片用完,CPU切換到線程2去執(zhí)行,這時(shí)它就會(huì)把下一條指令的地址10記錄到程序計(jì)數(shù)器里面,而且程序計(jì)數(shù)器是線程私有的,它是屬于線程1的,等線程2代碼執(zhí)行完了,線程1搶到了時(shí)間片,它就會(huì)從自己的程序計(jì)數(shù)器里面取出下一行代碼。每個(gè)線程都有自己的程序計(jì)數(shù)器

3.虛擬機(jī)棧

3.1.棧的特點(diǎn)

棧類似現(xiàn)實(shí)生活中的子彈夾。棧最重要的特點(diǎn)是后進(jìn)先出。
在這里插入圖片描述
如圖,1是最先進(jìn)入棧中的,3是最后進(jìn)入棧中的,但是在出棧的時(shí)候,3最先出棧,1最后出棧。即他們按照1,2,3的順序入棧,按照3,2,1的順序出棧

虛擬機(jī)棧就是我們線程運(yùn)行時(shí)需要的內(nèi)存空間,一個(gè)線程運(yùn)行時(shí)需要一個(gè)棧。如果將來有多個(gè)線程的話,它就會(huì)有多個(gè)虛擬機(jī)棧。
每個(gè)??梢钥闯墒怯啥鄠€(gè)棧幀組成,例如上圖中每個(gè)元素1,2,3都可以看成是棧幀。
一個(gè)棧幀就對應(yīng)著Java中一個(gè)方法的調(diào)用,即棧幀就是每個(gè)方法運(yùn)行時(shí)需要的內(nèi)存。每個(gè)方法運(yùn)行時(shí)需要的內(nèi)存一般有參數(shù),局部變量,返回地址,這些都需要占用內(nèi)存,所以每個(gè)方法執(zhí)行時(shí),都要預(yù)先把這些內(nèi)存分配好。
當(dāng)我們調(diào)用第一個(gè)方法棧幀時(shí),它就會(huì)給第一個(gè)方法分配棧幀空間,并且壓入棧內(nèi),當(dāng)這個(gè)方法執(zhí)行完了,就會(huì)把這個(gè)方法棧幀出棧,釋放這個(gè)方法所占用的內(nèi)存。
一個(gè)棧內(nèi)可能有多個(gè)棧幀存在。

總結(jié)
Java Virtual Machine Stacks(Java虛擬機(jī)棧)

  • 每個(gè)線程運(yùn)行時(shí)所需要的內(nèi)存,稱為虛擬機(jī)棧
  • 每個(gè)棧由多個(gè)棧幀(Frame)組成,對應(yīng)著每次方法調(diào)用時(shí)所占用的內(nèi)存
  • 每個(gè)線程只能有一個(gè)活動(dòng)棧幀,對應(yīng)著當(dāng)前正在執(zhí)行的那個(gè)方法(位于棧頂)

活動(dòng)棧幀表示線程正在執(zhí)行的方法。

3.2.棧的演示

public?class?teststacks?{
	public?static?void?main(String[]?args)?throws?InterruptedException{
		method1();
	}
	public?static?void?method1(){
		method2(1,2);
	}
	public?static?int?method2(int?a,int?b){
		int?c=a+b;
		return?c;
	}}

可以自行調(diào)試以上代碼來觀察棧中的變化情況。
入棧順序:main->method1->method2
出棧順序:method2->method1->main

3.3.棧的問題辨析

  1. 垃圾回收是否涉及棧內(nèi)存?
    不涉及,垃圾回收只是回收堆內(nèi)存中的無用對象,棧內(nèi)存不需要對它執(zhí)行垃圾回收,隨著方法的調(diào)用結(jié)束,棧內(nèi)存就釋放了。
  2. 棧內(nèi)存分配越大越好嗎?
    首先棧內(nèi)存可以指定:-Xss size(如果不指定棧內(nèi)存大小,不同系統(tǒng)會(huì)有一個(gè)不同的默認(rèn)值)
    其次由于電腦內(nèi)存一定,假如有100Mb,如果給棧內(nèi)存指定為2Mb,則最多只能存在50個(gè)線程,所以并不是越大越好,棧內(nèi)存較大一般是可以進(jìn)行較多次的方法遞歸調(diào)用,而不會(huì)增強(qiáng)線程效率,反而會(huì)使線程數(shù)量減少,一般使用默認(rèn)大小。

3.4.棧的線程安全問題

看一個(gè)變量是否線程安全,首先就是看這個(gè)變量對多個(gè)線程是共享的還是私有的,共享的變量需要考慮線程安全。
其次局部變量也不能保證是線程安全的,需要看此變量是否逃離了方法的作用范圍(作為參數(shù)和返回值逃出方法作用范圍時(shí)需要考慮線程安全問題)
例如:
以下代碼中局部變量是私有的,是線程安全的

	//多個(gè)線程同時(shí)執(zhí)行該方法,會(huì)不會(huì)造成x值混亂呢?
	//不會(huì),因?yàn)閤是方法內(nèi)的局部變量,是線程私有的,互不干擾
	static?void?m1(){
		int?x=0;
		for(int?i=0;i<5000;i++){
			x++;
		}
		System.out.println(x);
	}

但是如果我們把變量的類型改為static,此時(shí)就大不一樣了,x是靜態(tài)變量,線程1和線程2同時(shí)擁有同一個(gè)x,static變量針對多個(gè)線程是一個(gè)共享的,不加安全保護(hù)的話,就會(huì)出現(xiàn)線程安全問題。

	static void m1(){
		static int x=0;
		for(int i=0;i<5000;i++){
			x++;
		}
		System.out.println(x);
	}

我們再看幾個(gè)方法

public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append(4);
        sb.append(5);
        sb.append(6);
        new Thread(()->{
????????????m2(sb);
????????}).start();
????}
????public?static?void?m1()?{
????????StringBuilder?sb?=?new?StringBuilder();
????????sb.append(1);
????????sb.append(2);
????????sb.append(3);
????????System.out.println(sb.toString());
????}
????public?static?void?m2(StringBuilder?sb)?{
????????sb.append(1);
????????sb.append(2);
????????sb.append(3);
????????System.out.println(sb.toString());
????}
????public?static?StringBuilder?m3()?{
????????StringBuilder?sb?=?new?StringBuilder();
????????sb.append(1);
????????sb.append(2);
????????sb.append(3);
????????return?sb;
????}

m1是線程安全的:m1中的sb是線程中的局部變量,它是屬于線程私有的
m2線程不安全:sb它是方法的參數(shù),有可能有其它的線程訪問到它,它就不再是線程私有的了,它對多個(gè)線程是共享的。
m3不是線程安全的:它被當(dāng)成返回結(jié)果返回了,返回了有可能其它的線程拿到這個(gè)對象,從而并發(fā)的修改。

3.5.棧內(nèi)存溢出(StackOverflowError)

什么情況下會(huì)導(dǎo)致棧內(nèi)存溢出吶?
1.棧幀過多導(dǎo)致棧內(nèi)存溢出(一般遞歸調(diào)用次數(shù)太多,進(jìn)棧太多導(dǎo)致溢出)
這里最容易出現(xiàn)的場景是函數(shù)的遞歸調(diào)用。
2.棧幀過大導(dǎo)致棧內(nèi)存溢出(不太容易出現(xiàn))

棧內(nèi)存溢出代碼演示1(自己開發(fā)):
測試以下的程序,其中遞歸函數(shù)沒有遞歸邊界

public?class?Demo1_2?{
	private?static?int?count;
????public?static?void?main(String[]?args)?{
????????try?{
????????????method1();
????????}?catch?(Throwable?e)?{
????????????e.printStackTrace();
????????????System.out.println(count);
????????}
????}
????private?static?void?method1()?{
????????count++;
????????method1();
????}}

運(yùn)行結(jié)果如下
在這里插入圖片描述

在這里插入圖片描述
這里報(bào)了錯(cuò)誤StackOverflowError。
總共進(jìn)行了22846次遞歸調(diào)用

idea中設(shè)置棧內(nèi)存大?。?/strong>
在這里插入圖片描述
將棧內(nèi)存設(shè)置的小一點(diǎn),發(fā)現(xiàn)5000多次遞歸調(diào)用就溢出了。

棧內(nèi)存溢出代碼演示2(第三方依賴庫出現(xiàn)):
在這里插入圖片描述
本案例可以使用JsonIgnore注解解決循環(huán)依賴,數(shù)據(jù)轉(zhuǎn)換時(shí),只讓部門類去關(guān)聯(lián)員工類,員工類不再關(guān)聯(lián)部門類,在員工類的部門屬性(dept)上加@JsonIgnore注解。具體使用詳情可以點(diǎn)擊此處查看

3.6.線程運(yùn)行診斷

3.6.1.案例1:cpu占用過多(linux系統(tǒng)為例)

排查步驟:

1.在linux中使用top命令,去查看后臺(tái)進(jìn)程對cpu的占用情況
注意,在這之前我們運(yùn)行了一道Java程序
在這里插入圖片描述
Java代碼占用了CPU的99.3%.top命令只能定位到進(jìn)程,而無法定位到線程。

2.查看線程對cpu的占用情況:ps H -eo pid,tid,%cpu
如果顯示過多,可使用ps H -eo pid,tid,%cpu | grep 進(jìn)程id,過濾掉不想看的部分進(jìn)程

注意:ps不僅可以查看進(jìn)程,也可以查看線程對CPU的占用情況。H把進(jìn)程中的線程所有信息都展示出來。-eo規(guī)定輸出感興趣的內(nèi)容,這里我們想看看pid,tid和CPU的占用情況%cpu
在這里插入圖片描述
當(dāng)線程數(shù)太多,排查不方便的話,我們可以用grep pid來進(jìn)行篩選,過濾掉不感興趣的進(jìn)程
ps H -eo pid,tid,%cpu |grep 32655

3.定位到是哪個(gè)線程占用內(nèi)存過高后,再使用Jdk提供的命令(jstack+進(jìn)程id)去查看進(jìn)程中各線程的運(yùn)行信息,需要把第二步中查到的線程id(十進(jìn)制)轉(zhuǎn)為十六進(jìn)制,然后進(jìn)行比較查詢到位置后判斷異常信息。
在這里插入圖片描述
thread1,thread2,thread3是我們自己定義的線程。
可以根據(jù)線程id,找到有問題的線程,進(jìn)一步定位到問題代碼的源碼行號
在這里插入圖片描述

3.6.2.案例2:線程診斷_遲遲得不到結(jié)果

仍然通過jdk提供的 jstack+進(jìn)程id的方式,去查看進(jìn)程中各個(gè)線程的運(yùn)行信息
在這里插入圖片描述
在這里插入圖片描述

4.本地方法棧

含義:Java虛擬機(jī)調(diào)用本地方法時(shí),需要給本地方法提供的一些內(nèi)存空間
本地方法不是由Java編寫的代碼,由于Java有時(shí)不能直接和操作系統(tǒng)打交道,所以需要用C/C++語言來與操作系統(tǒng)打交道,那么Java就可以通過調(diào)用本地方法來獲得這些功能。本地方法非常的多,如Object類的clone(),hashCode方法,wait方法,notify方法等

public?native?int?hashCode();

5.堆

5.1.定義

1.虛擬機(jī)棧,程序計(jì)數(shù)器,本地方法棧,這些都是線程私有的,而堆和方法區(qū),是線程公用的一塊內(nèi)存區(qū)域。
2.通過new關(guān)鍵字創(chuàng)建的對象都會(huì)使用堆內(nèi)存
3.由于堆是線程共享的,堆內(nèi)的對象都要考慮線程安全問題(也有一些例外)
4.堆有垃圾回收機(jī)制,不再被引用的對象會(huì)被回收

5.2.堆內(nèi)存溢出(OutOfMemoryError:Java heap space)

對象一直存在于堆中未被回收,且占用內(nèi)存越來越大,最終導(dǎo)致堆內(nèi)存溢出(雖然堆中有垃圾回收機(jī)制,但垃圾回收機(jī)制不是回收所有的對象)
我們可以看看下面的代碼

public?static?void?main(String[]?args)?{
????????int?i?=?0;
????????try?{
????????????List<String>?list?=?new?ArrayList<>();
????????????String?a?=?"hello";
????????????while?(true)?{
????????????????list.add(a);?//?hello,?hellohello,?hellohellohellohello?...
????????????????a?=?a?+?a;??//?hellohellohellohello
????????????????i++;
????????????}
????????}?catch?(Throwable?e)?{
????????????e.printStackTrace();
????????????System.out.println(i);
????????}}

在這里插入圖片描述
報(bào)了錯(cuò)誤java.lang.OutOfMemoryError
代碼中每次都拼接一個(gè)hello,由于定義的list集合創(chuàng)建在try語句里面,所以在for循環(huán)不斷執(zhí)行過程中,list集合是不會(huì)被回收的,只要程序還沒到catch之前,它就一直有效。而字符串對象都被追加到了集合內(nèi)部,字符串對象由于一直被使用,所以不會(huì)被回收。
我們可以通過-Xmx來設(shè)置堆空間大小。
在這里插入圖片描述
我們把堆內(nèi)存改成8M(之前內(nèi)存是4G),此時(shí)只運(yùn)行了17次。

5.3.堆內(nèi)存診斷

1.jps工具:jps,查看當(dāng)前進(jìn)程中有哪些Java進(jìn)程,并將進(jìn)程id顯示出來(idea中通過terminal命令行輸入命令)
2.jmap工具:jmap -heap 進(jìn)程id 查詢某一個(gè)時(shí)刻堆內(nèi)存的占用情況
3.jconsole工具:圖形界面的,多功能監(jiān)測工具,可連續(xù)監(jiān)測,使用流程圖如下(1-2-3):

6.方法區(qū)

6.1.定義

方法區(qū)(Method Area)與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,他用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)常量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。(與類有關(guān)的信息)。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是他卻有一個(gè)別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來。方法區(qū)在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。
對于習(xí)慣在HotSpot虛擬機(jī)上開發(fā)、部署程序的開發(fā)者來說,很多都更愿意把方法取稱為“永久代”(Permanent Generation),本質(zhì)上兩者并不等價(jià),僅僅是因?yàn)镠otSpot虛擬機(jī)的設(shè)計(jì)團(tuán)隊(duì)選擇把GC分代收集擴(kuò)展至方法區(qū),或者說使用永久代來實(shí)現(xiàn)方法區(qū)而已,這樣HotSpot的垃圾收集器可以像管理Java堆一樣管理這部分內(nèi)存,能夠省去專門為方法區(qū)編寫內(nèi)存管理代碼的工作。對于其他虛擬機(jī)(如BEA JRockit、IBM J9等)來說是不存在永久代的概念的。原則上,如何實(shí)現(xiàn)方法區(qū)屬于虛擬機(jī)實(shí)現(xiàn)細(xì)節(jié),不受虛擬機(jī)規(guī)范約束,但使用永久代來實(shí)現(xiàn)方法區(qū),現(xiàn)在看來并不是一個(gè)好主意,因?yàn)檫@樣更容易遇到內(nèi)存溢出問題(永久代有-XX:MaxPermSize的上限,J9和JRockit只要沒有觸碰到進(jìn)程可用內(nèi)存的上限,例如32位系統(tǒng)中的4GB,就不會(huì)出現(xiàn)問題),而且有極少數(shù)方法(例如String.intern())會(huì)因這個(gè)原因?qū)е虏煌摂M機(jī)下有不同的表現(xiàn)。因此,對于HotSpot虛擬機(jī),根據(jù)官方發(fā)布的路線圖信息,現(xiàn)在也已放棄永久代并逐步改為采用Navtive Memory來實(shí)現(xiàn)方法區(qū)的規(guī)劃,在JDK1.7的HostSpot中,已經(jīng)把原本放在永久代的字符串常量池移出,jdk1.8中后稱作元空間,用的操作系統(tǒng)內(nèi)存。
Java虛擬機(jī)規(guī)范對方法區(qū)的限制非常寬松,除了和Java堆一樣不需要連續(xù)的內(nèi)存和可以喧囂而固定大小或者可擴(kuò)展外,還可以選擇不實(shí)現(xiàn)垃圾收集。相對而言,垃圾收集行為在這個(gè)區(qū)域是比較少出現(xiàn)的,但并非數(shù)據(jù)進(jìn)入了方法區(qū)就如永久代的名字一樣“永久”存在了。這區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載,一般來說,這個(gè)區(qū)域的回收“成績”比較難以令人滿意,尤其是類型的卸載,條件相當(dāng)苛刻,但是這部分區(qū)域的回收確實(shí)是必要的。在Sun公司的BUG列表中,曾出現(xiàn)過的若干個(gè)嚴(yán)重的BUG就是由于低版本的HotSpot虛擬機(jī)對此區(qū)域未完全回收而導(dǎo)致內(nèi)存泄漏。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError異常。

文原文關(guān)于虛擬機(jī)的定義:

在這里插入圖片描述

6.2.定義

jdk1.8之前,方法區(qū)是用的堆內(nèi)存,1.8之后,方法區(qū)用的操作系統(tǒng)內(nèi)存。
這塊不是太清晰,可以參考下此篇博客點(diǎn)擊查看
常量池分為靜態(tài)常量池和動(dòng)態(tài)常量池,下圖中的常量池指的是動(dòng)態(tài)常量池,因?yàn)樗鼈円呀?jīng)被讀入內(nèi)存中去,而靜態(tài)常量池存在于class文件中
在這里插入圖片描述

6.3.方法區(qū)內(nèi)存溢出(OutOfMemoryError: Metaspace)

1.8以前會(huì)導(dǎo)致永久代內(nèi)存溢出

1.8以后會(huì)導(dǎo)致元空間內(nèi)存溢出

/**
?*?演示元空間內(nèi)存溢出?java.lang.OutOfMemoryError:?Metaspace
?*?-XX:MaxMetaspaceSize=8m
?*/public?class?Demo1_8?extends?ClassLoader?{?//?可以用來加載類的二進(jìn)制字節(jié)碼
????public?static?void?main(String[]?args)?{
????????int?j?=?0;
????????try?{
????????????Demo1_8?test?=?new?Demo1_8();
????????????
????????????for?(int?i?=?0;?i?< 10000; i++, j++) {
                // ClassWriter 作用是生成類的二進(jìn)制字節(jié)碼
                ClassWriter cw = new ClassWriter(0);
                //參數(shù):版本號, public, 類名, 包名, 父類, 接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                // 生成類,二進(jìn)制字節(jié)碼用byte來表示,返回 byte[]
                byte[] code = cw.toByteArray();
                // 執(zhí)行了類的加載
                test.defineClass("Class" + i, code, 0, code.length); // Class 對象
            }
        } finally {
            System.out.println(j);
        }
    }}

jdk1.8以后, 默認(rèn)情況下,方法區(qū)用的是系統(tǒng)內(nèi)存,所以加大還是不會(huì)導(dǎo)致內(nèi)存溢出,循環(huán)很多次都運(yùn)行成功。
當(dāng)設(shè)置了-XX:MaxMetaspaceSize=8m,到了5411次就溢出了。報(bào)的是java.lang.OutOfMemoryError: Metaspace錯(cuò)誤

而1.8以前永久代溢出報(bào)的錯(cuò)誤是java.lang.OutOfMemoryError:PermGen space

6.4.常量池

在這里插入圖片描述

常量池,就是一張表,虛擬機(jī)指令根據(jù)這站常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量信息(如字符串常量、true和false)
運(yùn)行時(shí)常量池,常量池是.class文件中的,當(dāng)該類被加載,它的常量池信息就會(huì)放入運(yùn)行時(shí)常量池,并把里面的符號地址變?yōu)檎鎸?shí)地址*。

public class HelloWorld {
	public static void main(String[] args) {
		System.out.println("hello,world");
	}}

以上是一個(gè)helloworld程序,helloworld要運(yùn)行,肯定要先編譯成一個(gè)二進(jìn)制字節(jié)碼。
二進(jìn)制字節(jié)碼由類的基本信息、常量池、類方法定義(包含了虛擬機(jī)指令)。
反編譯HelloWorld(之前需要運(yùn)行將.java文件編譯成.class文件)
使用idea工具
在這里插入圖片描述

F:\IDEA\projects\jvm>javap?-v?F:\IDEA\projects\jvm\out\production\untitled\HelloWorld.class

F:\IDEA\projects\jvm\out\production\untitled\是HelloWorld.class所在的路徑

顯示類的詳細(xì)信息

Classfile?/F:/IDEA/projects/jvm/out/production/untitled/HelloWorld.class
??Last?modified?2021-1-30;?size?533?bytes
??MD5?checksum?82d075eb7217b4d23706f6cfbd44f8f1
??Compiled?from?"HelloWorld.java"public?class?HelloWorld
??minor?version:?0
??major?version:?52
??flags:?ACC_PUBLIC,?ACC_SUPER

可以看到類的文件,最后修改時(shí)間,簽名。以及版本等等。有的還有訪問修飾符、父類和接口等詳細(xì)信息。

顯示常量池

Constant?pool:
???#1?=?Methodref??????????#6.#20?????????//?java/lang/Object."<init>":()V
???#2?=?Fieldref???????????#21.#22????????//?java/lang/System.out:Ljava/io/PrintStream;
???#3?=?String?????????????#23????????????//?hello,world
???#4?=?Methodref??????????#24.#25????????//?java/io/PrintStream.println:(Ljava/lang/String;)V
???#5?=?Class??????????????#26????????????//?HelloWorld
???#6?=?Class??????????????#27????????????//?java/lang/Object
???#7?=?Utf8???????????????<init>
???#8?=?Utf8???????????????()V
???#9?=?Utf8???????????????Code
??#10?=?Utf8???????????????LineNumberTable
??#11?=?Utf8???????????????LocalVariableTable
??#12?=?Utf8???????????????this
??#13?=?Utf8???????????????LHelloWorld;
??#14?=?Utf8???????????????main
??#15?=?Utf8???????????????([Ljava/lang/String;)V
??#16?=?Utf8???????????????args
??#17?=?Utf8???????????????[Ljava/lang/String;
??#18?=?Utf8???????????????SourceFile
??#19?=?Utf8???????????????HelloWorld.java
??#20?=?NameAndType????????#7:#8??????????//?"<init>":()V
??#21?=?Class??????????????#28????????????//?java/lang/System
??#22?=?NameAndType????????#29:#30????????//?out:Ljava/io/PrintStream;
??#23?=?Utf8???????????????hello,world
??#24?=?Class??????????????#31????????????//?java/io/PrintStream
??#25?=?NameAndType????????#32:#33????????//?println:(Ljava/lang/String;)V
??#26?=?Utf8???????????????HelloWorld
??#27?=?Utf8???????????????java/lang/Object
??#28?=?Utf8???????????????java/lang/System
??#29?=?Utf8???????????????out
??#30?=?Utf8???????????????Ljava/io/PrintStream;
??#31?=?Utf8???????????????java/io/PrintStream
??#32?=?Utf8???????????????println
??#33?=?Utf8???????????????(Ljava/lang/String;)V

顯示方法定義

{
??public?HelloWorld();
????descriptor:?()V
????flags:?ACC_PUBLIC
????Code:
??????stack=1,?locals=1,?args_size=1
?????????0:?aload_0?????????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V
?????????4:?return
??????LineNumberTable:
????????line?1:?0
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature????????????0???????5?????0??this???LHelloWorld;

??public?static?void?main(java.lang.String[]);
????descriptor:?([Ljava/lang/String;)V
????flags:?ACC_PUBLIC,?ACC_STATIC
????Code:
??????stack=2,?locals=1,?args_size=1
?????????0:?getstatic?????#2??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
?????????3:?ldc???????????#3??????????????????//?String?hello,world
?????????5:?invokevirtual?#4??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/String;)V
?????????8:?return
??????LineNumberTable:
????????line?3:?0
????????line?4:?8
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature????????????0???????9?????0??args???[Ljava/lang/String;}

第一個(gè)方法是public HelloWorld();它是編譯器自動(dòng)為我們構(gòu)造的無參構(gòu)造方法。
第二個(gè)是public static void main(java.lang.String[]);即main方法
方噶里面就包括了虛擬機(jī)的指令了。
getstatic獲取一個(gè)靜態(tài)變量,即獲取System.out靜態(tài)變量
ldc是加載一個(gè)參數(shù),參數(shù)是字符串hello,world
invokevirtual虛方法調(diào)用,println方法
return執(zhí)行結(jié)束。
我們getstatic、ldc、invokevirtual后面都有一個(gè)#2,#3,#4。在解釋器翻譯這些虛擬機(jī)指令的時(shí)候,它會(huì)把這些#2,#3,#4進(jìn)行一個(gè)查表翻譯。比如getstatic #2,就去查常量池的表。在常量池中
#2 = Fieldref #21.#22 引用的是成員變量#21,#22.
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
然后再去找#28.29,30
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
所以現(xiàn)在我就知道了,我是要找到j(luò)ava.lang.system類下叫out的成員變量,類型是java/io。
同理,ldc是找#3 = String #23 Utf8 hello,world,它是虛擬機(jī)常量池的一個(gè)字符串。把helloworld常量變成字符串對象加載進(jìn)來。
invokevirtual #4 Methodref #24.#25 等等
所以常量池的作用就是給我們指令提供一些常量符號,根據(jù)這些常量符號,我們就可以根據(jù)查表的方式去找到它,這樣虛擬機(jī)才能成功的執(zhí)行它。

相關(guān)免費(fèi)學(xué)習(xí)推薦:java基礎(chǔ)教程

以上是JVM學(xué)習(xí)之 Java內(nèi)存結(jié)構(gòu)的詳細(xì)內(nèi)容。更多信息請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本站聲明
本文內(nèi)容由網(wǎng)友自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,本站不承擔(dān)相應(yīng)法律責(zé)任。如您發(fā)現(xiàn)有涉嫌抄襲侵權(quán)的內(nèi)容,請聯(lián)系admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣服圖片

Undresser.AI Undress

Undresser.AI Undress

人工智能驅(qū)動(dòng)的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用于從照片中去除衣服的在線人工智能工具。

Clothoff.io

Clothoff.io

AI脫衣機(jī)

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的代碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

功能強(qiáng)大的PHP集成開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級代碼編輯軟件(SublimeText3)

熱門話題

Laravel 教程
1601
29
PHP教程
1502
276