亚洲精品中文字幕无乱码_久久亚洲精品无码AV大片_最新国产免费Av网址_国产精品3级片

java語言

java程序運行時內(nèi)存如何分配

時間:2024-09-28 08:45:47 java語言 我要投稿
  • 相關(guān)推薦

java程序運行時內(nèi)存如何分配

  一、 基本概念

  每運行一個java程序會產(chǎn)生一個java進(jìn)程,每個java進(jìn)程可能包含一個或者多個線程,每一個Java進(jìn)程對應(yīng)唯一一個JVM實例,每一個JVM實例唯一對應(yīng)一個堆,每一個線程有一個自己私有的棧。進(jìn)程所創(chuàng)建的所有類的實例(也就是對象)或數(shù)組(指的是數(shù)組的本身,不是引用)都放在堆中,并由該進(jìn)程所有的線程共享。Java中分配堆內(nèi)存是自動初始化的,即為一個對象分配內(nèi)存的時候,會初始化這個對象中變量。雖然Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在棧中分配,也就是說在建立一個對象時在堆和棧中都分配內(nèi)存,在堆中分配的內(nèi)存實際存放這個被創(chuàng)建的對象的本身,而在棧中分配的內(nèi)存只是存放指向這個堆對象的引用而已。局部變量 new 出來時,在?臻g和堆空間中分配空間,當(dāng)局部變量生命周期結(jié)束后,棧空間立刻被回收,堆空間區(qū)域等待GC回收。

  具體的概念:JVM的內(nèi)存可分為3個區(qū):堆(heap)、棧(stack)和方法區(qū)(method,也叫靜態(tài)區(qū)):

  堆區(qū):

  1.存儲的全部是對象,每個對象都包含一個與之對應(yīng)的class的信息(class的目的是得到操作指令) ;

  2.jvm只有一個堆區(qū)(heap),且被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身和數(shù)組本身;

  棧區(qū):

  1.每個線程包含一個棧區(qū),棧中只保存基礎(chǔ)數(shù)據(jù)類型本身和自定義對象的引用;

  2.每個棧中的數(shù)據(jù)(原始類型和對象引用)都是私有的,其他棧不能訪問;

  3.棧分為3個部分:基本類型變量區(qū)、執(zhí)行環(huán)境上下文、操作指令區(qū)(存放操作指令);

  方法區(qū)(靜態(tài)區(qū)):

  1.被所有的線程共享,方法區(qū)包含所有的class(class是指類的原始代碼,要創(chuàng)建一個類的對象,首先要把該類的代碼加載到方法區(qū)中,并且初始化)和static變量。

  2.方法區(qū)中包含的都是在整個程序中永遠(yuǎn)唯一的元素,如class,static變量。

  二、實例演示

  AppMain.java

  public class AppMain //運行時, jvm 把appmain的代碼全部都放入方法區(qū) { public static void main(String[] args) //main 方法本身放入方法區(qū)。 { Sample test1 = new Sample( " 測試1 " ); //test1是引用,所以放到棧區(qū)里, Sample是自定義對象應(yīng)該放到堆里面 Sample test2 = new Sample( " 測試2 " ); test1.printName(); test2.printName(); } } public class Sample //運行時, jvm 把appmain的信息都放入方法區(qū) { /** 范例名稱 */ private String name; //new Sample實例后, name 引用放入棧區(qū)里, name 對應(yīng)的 String 對象放入堆里 /** 構(gòu)造方法 */ public Sample(String name) { this .name = name; } /** 輸出 */ public void printName() //在沒有對象的時候,print方法跟隨sample類被放入方法區(qū)里。 { System.out.println(name); } }

  運行該程序時,首先啟動一個Java虛擬機進(jìn)程,這個進(jìn)程首先從classpath中找到AppMain.class文件,讀取這個文件中的二進(jìn)制數(shù)據(jù),然后把Appmain類的類信息存放到運行時數(shù)據(jù)區(qū)的方法區(qū)中,這就是AppMain類的加載過程。

  接著,Java虛擬機定位到方法區(qū)中AppMain類的Main()方法的字節(jié)碼,開始執(zhí)行它的指令。這個main()方法的第一條語句就是:

  復(fù)制代碼 代碼如下:

  Sample test1=new Sample("測試1");

  該語句的執(zhí)行過程:

  1、 Java虛擬機到方法區(qū)找到Sample類的類型信息,沒有找到,因為Sample類還沒有加載到方法區(qū)(這里可以看出,java中的內(nèi)部類是單獨存在的,而且剛開始的時候不會跟隨包含類一起被加載,等到要用的時候才被加載)。Java虛擬機立馬加載Sample類,把Sample類的類型信息存放在方法區(qū)里。

  2、 Java虛擬機首先在堆區(qū)中為一個新的Sample實例分配內(nèi)存, 并在Sample實例的內(nèi)存中存放一個方法區(qū)中存放Sample類的類型信息的內(nèi)存地址。

  3、 JVM的進(jìn)程中,每個線程都會擁有一個方法調(diào)用棧,用來跟蹤線程運行中一系列的方法調(diào)用過程,棧中的每一個元素就被稱為棧幀,每當(dāng)線程調(diào)用一個方法的時候就會向方法棧壓入一個新幀。這里的幀用來存儲方法的參數(shù)、局部變量和運算過程中的臨時數(shù)據(jù)。

  4、位于“=”前的Test1是一個在main()方法中定義的一個變量(一個Sample對象的引用),因此,它被會添加到了執(zhí)行main()方法的主線程的JAVA方法調(diào)用棧中。而“=”將把這個test1變量指向堆區(qū)中的Sample實例。

  5、JVM在堆區(qū)里繼續(xù)創(chuàng)建另一個Sample實例,并在main方法的方法調(diào)用棧中添加一個Test2變量,該變量指向堆區(qū)中剛才創(chuàng)建的Sample新實例。

  6、JVM依次執(zhí)行它們的printName()方法。當(dāng)JAVA虛擬機執(zhí)行test1.printName()方法時,JAVA虛擬機根據(jù)局部變量test1持有的引用,定位到堆區(qū)中的Sample實例,再根據(jù)Sample實例持有的引用,定位到方法去中Sample類的類型信息,從而獲得printName()方法的字節(jié)碼,接著執(zhí)行printName()方法包含的指令,開始執(zhí)行。

  三、辨析

  在Java語言里堆(heap)和棧(stack)里的區(qū)別 :

  1. 棧(stack)與堆(heap)都是Java用來在Ram中存放數(shù)據(jù)的地方。與C++不同,Java自動管理棧和堆,程序員不能直接地設(shè)置棧或堆。

  2. 棧的優(yōu)勢是,存取速度比堆要快,僅次于直接位于CPU中的寄存器。但缺點是,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性。另外,棧數(shù)據(jù)可以共享(詳見下面的介紹)。堆的優(yōu)勢是可以動態(tài)地分配內(nèi)存大小,生存期也不必事先告訴編譯器,Java的垃圾收集器會自動收走這些不再使用的數(shù)據(jù)。但缺點是,由于要在運行時動態(tài)分配內(nèi)存,存取速度較慢。

  Java中的2種數(shù)據(jù)類型:

  一種是基本類型(primitive types), 共有8類,即int, short, long, byte, float, double, boolean, 注意,并沒有string的基本類型)。這種類型的定義是通過諸如int a = 3; long b = 255L;的形式來定義的,稱為自動變量。自動變量存的是字面值,不是類的實例,即不是類的引用,這里并沒有類的存在。如int a = 3; 這里的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數(shù)據(jù),由于大小可知,生存期可知(這些字面值固定定義在某個程序塊里面,程序塊退出后,字段值就消失了),出于追求速度的原因,就存在于棧中。

  棧有一個很重要的特性:存在棧中的數(shù)據(jù)可以共享。假設(shè)我們同時定義: int a = 3;int b = 3; 編譯器先處理int a = 3;首先它會在棧中創(chuàng)建一個變量為a的引用,然后查找有沒有字面值為3的地址,如果沒找到,就開辟一個存放3這個字面值的地址,然后將a指向3的地址。接著處理int b = 3;在創(chuàng)建完b的引用變量后,由于在棧中已經(jīng)有3這個字面值,便將b直接指向3的地址。這樣,就出現(xiàn)了a與b同時均指向3的情況。

  這種字面值的引用與類對象的引用不同。假定兩個類對象的引用同時指向一個對象,如果一個對象引用變量修改了這個對象的內(nèi)部狀態(tài),那么另一個對象引用變量也即刻反映出這個變化。相反,通過字面值的引用來修改其值,不會導(dǎo)致另一個指向此字面值的引用的值也跟著改變的情況。如上例,我們定義完a與 b的值后,再令a=4;那么,b不會等于4,還是等于3。在編譯器內(nèi)部,遇到a=4;時,它就會重新搜索棧中是否有4的字面值,如果沒有,重新開辟地址存放4的值;如果已經(jīng)有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。

  另一種是包裝類數(shù)據(jù),如Integer, String, Double等將相應(yīng)的基本數(shù)據(jù)類型包裝起來的類。這些類數(shù)據(jù)全部存在于堆中,Java用new()語句來顯示地告訴編譯器,在運行時才根據(jù)需要動態(tài)創(chuàng)建,因此比較靈活,但缺點是要占用更多的時間。

  四、總結(jié)

  java內(nèi)存分配條理還是很清楚的,如果要徹底搞懂,可以去查閱JVM相關(guān)的書籍。在java中,內(nèi)存分配最讓人頭疼的是String對象,由于其特殊性,所以很多程序員容易搞混淆,下一篇文章再詳細(xì)講解。

【java程序運行時內(nèi)存如何分配】相關(guān)文章:

如何解決java內(nèi)存泄漏的問題08-13

Java內(nèi)存回收07-17

Java的內(nèi)存模型09-22

如何搭建Java程序開發(fā)環(huán)境09-01

Java編程節(jié)省內(nèi)存的方法07-03

JAVA對象創(chuàng)造及內(nèi)存布局介紹10-09

C語言中內(nèi)存分配問題09-13

如何擴大電腦內(nèi)存08-09

查看java對象所占的內(nèi)存大小的方法09-03

Java編程節(jié)省內(nèi)存的方法有哪些08-10