1. 程式人生 > >Java物件建立的具體過程

Java物件建立的具體過程

Java是一門面向物件的程式語言,在Java程式執行過程中無時無刻都有物件被創建出來。在語言層面上,建立物件(例如克隆、反序列化)通常僅僅是一個new關鍵字而已,而在虛擬機器中,物件(文中討論的物件限於普通Java物件,不包括陣列和Class物件等)的建立又是怎樣一個過程呢?

一、幾個概念

1、執行時常量池

執行時常量池是方法區的一部分,用於存放編譯期生成的各種字面量和符號引用。記憶體不夠會丟擲OutOfMemoryError異常。

2、字面量:

文字字串、宣告為final的常量值等

3、符號引用:

包括了下面三類常量:

  1. 類和介面的全限定名
  2. 欄位的名稱和描述符
  3. 方法的名稱和描述符

虛擬機器載入Class檔案需要進行動態連線。虛擬機器執行時,需要從常量池中獲取對應的符號引用,再在類建立時或執行時解析、翻譯到具體的記憶體地址之中。


符號引用理解:

符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能夠無歧義的定位到目標即可。例如,在Class檔案中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等型別的常量出現。符號引用與虛擬機器的記憶體佈局無關,引用的目標並不一定載入到記憶體中。在J中,一個java類將會編譯成一個class檔案。在編譯時,java類並不知道所引用的類的實際地址,因此只能使用符號引用來代替。比如org.simple.People類引用了org.simple.Language類,在編譯時People類並不知道Language類的實際記憶體地址,因此只能使用符號org.simple.Language(假設是這個,當然實際中是由類似於CONSTANT_Class_info的常量來表示的)來表示Language類的地址。各種虛擬機器實現的記憶體佈局可能有所不同,但是它們能接受的符號引用都是一致的,因為符號引用的字面量形式明確定義在Java虛擬機器規範的Class檔案格式中。

4、表

常量池中每一項常量都是一個表。JDK1.7中共有14中表,每個表的第一位是一個ul型別的標誌位(tag,取值為每個常量的標誌),代表當前這個常量是屬於哪一種型別。比如建立物件過程中涉及到的CONSTANT__Class_info(類或介面的符號引用),它的結構為:

CONSTANT_Class_info型常量的結構
    型別 名稱 數量
          u1                       tag        1
          u2                name_index        1

tag是標誌位,用於區分常量型別;name_index是一個索引值,指向常量池中一個CONSTANT__Utf8_info型別常量,此常量代表了這個類或者介面的全限定名。

二、建立過程

1、類載入檢查

虛擬機器遇到一條new指令時,首先將去檢查這個指令的引數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被載入、解析和初始化過。如果沒有,那必須先執行相應的類載入過程,具體載入過程這裡略過。

2、分配記憶體(指標碰撞空閒列表的選擇)

在類載入檢查通過後,接下來虛擬機器將為新生物件分配記憶體。物件所需記憶體的大小在類載入完成後便可完全確定,為物件分配空間的任務等同於把一塊確定大小的記憶體從Java堆中劃分出來。

(1)指標碰撞:假設Java堆中記憶體是絕對規整的,所有用過的記憶體都放在一邊,空閒的記憶體放在另一邊,中間放著一個指標作為分界點的指示器,那所分配記憶體就僅僅是把那個指標向空閒空間那邊挪動一段與物件大小相等的距離,這種分配方式稱為“指標碰撞”(Bump the Pointer)。

(2)空閒列表:如果Java堆中的記憶體並不是規整的,已使用的記憶體和空閒的記憶體相互交錯,那就沒有辦法簡單地進行指標碰撞了,虛擬機器就必須維護一個列表,記 錄上哪些記憶體塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給物件例項, 並更新列表上的記錄,這種分配方式稱為“空閒列表”(Free List)。

(3)選擇依據:
選擇哪種分配方式由 Java堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決 定。因此,在使用Serial、ParNew等帶Compact過程的收集器時,系統採用的分配演算法是指標碰撞,而使用CMS這種基於Mark-Sweep演算法的收集器時,通常採用空閒列表。

HotSpot採取G1垃圾回收器,其具有壓縮整理功能,系統採用的分配演算法是指標碰撞。

3、併發處理

物件建立在虛擬機器中是非常頻繁的行為,即使是僅僅修改一個指標所指向的位置,在併發情況下也並不是執行緒安全的, 可能出現正在給物件A分配記憶體,指標還沒來得及修改,物件B又同時使用了原來的指標來 分配記憶體的情況。解決這個問題有兩種方案,一種是對分配記憶體空間的動作進行同步處理 ——實際上虛擬機器採用CAS配上失敗重試的方式保證更新操作的原子性;另一種是把記憶體分 配的動作按照執行緒劃分在不同的空間之中進行,即每個執行緒在Java堆中預先分配一小塊記憶體,稱為本地執行緒分配緩衝(Thread Local Allocation Buffer,TLAB)。哪個執行緒要分配內 存,就在哪個執行緒的TLAB上分配,只有TLAB用完並分配新的TLAB時,才需要同步鎖定。 虛擬機器是否使用TLAB,可以通過-XX:+/-UseTLAB引數來設定。

4、零值初始化

記憶體分配完成後,虛擬機器需要將分配到的記憶體空間都初始化為零值(不包括物件頭), 如果使用TLAB,這一工作過程也可以提前至TLAB分配時進行。這一步操作解釋了物件的例項欄位在Java程式碼中為什麼可以不賦初始值就直接使用,程式能訪問到這些欄位的資料型別所對應的零值。

5、由物件頭資訊設定物件

接下來,虛擬機器要對物件進行必要的設定,例如這個物件是哪個類的例項、如何才能找到類的元資料資訊、物件的雜湊碼、物件的GC分代年齡等資訊。這些資訊存放在物件的對 象頭(Object Header)之中。根據虛擬機器當前的執行狀態的不同,如是否啟用偏向鎖等,物件頭會有不同的設定方式。

在上面工作都完成之後,從虛擬機器的視角來看,一個新的物件已經產生了。但從Java程 序的視角來看,物件建立才剛剛開始——<init>方法還沒有執行,所有的欄位都還為零。

6、執行初始化和構造器

class是從子類到基類依次查詢,有關靜態初始化的動作從基類到子類依次執行。在為所建立物件的儲存空間清零後,找到繼承鏈中最上層的基類: 然後從基類到子類依次執行以下這兩步操作。

(1)執行其出現在域定義處的初始化動作 ;
(2)然後再執行其構造器 。

7、例子

以建立Pernson物件為例,分析物件建立過程中在記憶體中的分配情況

/**
 * Created by wqh on 2017/7/9.
 */
public class Person
{
    //成員屬性
    private String name;
    private int age;
    private static String Country = "CN";
    DemoTest dTest = new DemoTest();

    //建構函式
    public Person(String name, int age)
    {
        System.out.println("這是person的建構函式");
        this.name = name;
        this.age = age;
    }

    //構造程式碼塊
    {
        System.out.println("這是person的構造程式碼塊");
    }

    //靜態程式碼塊
    static
    {
        System.out.println("這是person類的靜態程式碼塊");
    }

    public static void main(String[] args)
    {
        Person p = new Person("wqh",24);
    }
}

class DemoTest
{
    public DemoTest()
    {
        System.out.println("這是一個測試的類");
    }
}

輸出結果為:
這是person類的靜態程式碼塊
這是一個測試的類
這是person的構造程式碼塊
這是person的建構函式

過程分析:

首先要明確的一點是: 類的成員變數在不同物件中各不相同,都有自己的儲存空間(儲存在各自物件所佔用的堆記憶體空間中)。
類的方法卻是該類的所有物件共享的,它們存放在了方法區中,各方法的引用儲存在了常量池中。方法區優先於物件存在而且物件只儲存了成員變數和一些地址資訊。

首先棧中的main函式執行Person = new Person("wqh",24);這個簡單的語句會涉及到如下幾個步驟:
1、由於是要建立Person類物件,java虛擬機器(JVM)先去找Person.class檔案,如果有的話,將其載入到記憶體。
2、將型別資訊(包括靜態變數,方法等)載入進方法區。
3、執行該類中static程式碼塊。
4、到這時才進行堆記憶體空間的開闢,併為物件分配首地址。
5、在堆記憶體中建立物件的成員屬性,並對其進行初始化(先進行預設初始化再進行顯示初始化)。
6、進行構造程式碼塊的初始化,由此看出構造程式碼塊初始化的優先順序要高於物件建構函式的初始化
7、物件的建構函式進行初始化。
8、將堆記憶體中的地址(引用)賦給棧記憶體中的p變數。


相關推薦

Java物件建立過程

Java建立物件的過程? 判斷是否被載入到記憶體 Jvm遇到一條new指令時,會檢查這個指令的引數是否能在常量池中定位到一個類的符號引用,檢查其是否被載入,解析和初始化,如果沒意義,把類載入到記憶體 為物件分配記憶體空間 分配記憶體有兩種

JAVA物件建立過程

JVM物件建立的過程     對於java程式設計師來說,我們無時無刻不在建立和使用物件。使用new關鍵字即可快速建立一個物件,其實在new的背後,JVM為我們完成了很多事情。     JVM的位元組碼直譯器在讀取到NEW的指令時,會先去常量區定位對應類的符

深入學習Java物件建立過程:類的初始化與例項化

  在Java中,一個物件在可以被使用之前必須要被正確地初始化,這一點是Java規範規定的。在例項化一個物件時,JVM首先會檢查相關型別是否已經載入並初始化,如果沒有,則JVM立即進行載入並呼叫類構造器完成類的初始化。在類初始化過程中或初始化完畢後,根據具體情況才會去對類進行例項化。本文試圖對JVM執行類初始

Java物件建立具體過程

Java是一門面向物件的程式語言,在Java程式執行過程中無時無刻都有物件被創建出來。在語言層面上,建立物件(例如克隆、反序列化)通常僅僅是一個new關鍵字而已,而在虛擬機器中,物件(文中討論的物件限於普通Java物件,不包括陣列和Class物件等)的建立又是怎樣一個過程呢

Java物件建立過程

物件的建立過程 物件的建立當虛擬機器遇到一條new指令時,首先將去檢查這個指令的引數是否能在常量池中定位到某個類的符號引用,並且檢查這個符號引用代表的類是否已被載入、解析、初始化。 物件記憶體的分配如果沒有,則必須先執行相應的類載入過程,當類載入檢查通過後,虛擬機

Java物件建立過程和記憶體結構分析

JAVA記憶體分配和管理是JAVA的核心技術之一,在看了尚矽谷宋紅康老師講解的JAVA記憶體知識之後,結合《深入理解JVM這本書》對自己所學的知識進行簡單的總結,寫了這篇日誌。 1.JAVA記憶體分割槽  根據儲存資料的不同,java記憶體通常被劃分為5個區域:程式計數器(

java物件建立過程及初始化順序

Java虛擬機器建立一個物件都包含以下步驟: (1)給物件分配記憶體。 (2)將物件的例項變數自動初始化為其變數型別的預設值。 (3)初始化物件,給例項變數賦予正確的初始值。 對於以上第三個步驟,Java虛擬機器可採用3種方式來初始化物件,到底採用何

java物件建立(記憶體模型)過程詳解

概述 java物件建立分為兩個過程:宣告物件引用和建立物件實體。類資訊、物件引用、物件實體均在記憶體的不同區域。 記憶體結構 每一個java應用程式均會唯一的對應一個jvm例項,而這個jvm例

java物件建立流程

物件建立流程 推薦部落格 建立觸發   關於物件的建立一般是從new指令(我說的是JVM的層面)開始的。 虛擬機器遇到一條new指令時,會先去檢查這個指令的引數能否在方法區中的常量池中檢索到一個類的符號應用,並且檢查這個符號引用代表的類是否已被載入、解析、初始化

Spring 容器及物件建立過程

         Spring容器負責物件的建立過程,配置並且管理他們的建立過程,從建立到被回收。Spring有多種容器的實現,分為兩種型別,bean工廠,最簡單的容器,提供DI的支援。應用上下文是基於beanFactory構建的,提供企業極的服務。    常用的幾種應用上下

java物件初始化過程

假設有一下類: class Test{ int i; int j = 0; int count(){ return 0 }; Test() {} int n =0; } 我們知道,任何物件在使用前都會被初始化,方法裡

Java物件建立模式

      建立Java物件時,對於可為空的屬性,建立物件的時候有3種模式:重疊構造器模式、JavaBeans模式、Builder模式(推薦)、Stream模式(推薦)。              

深入理解Java物件建立過程:類的初始化與例項化

摘要:   在Java中,一個物件在可以被使用之前必須要被正確地初始化,這一點是Java規範規定的。在例項化一個物件時,JVM首先會檢查相關型別是否已經載入並初始化,如果沒有,則JVM立即進行載入並呼叫類構造器完成類的初始化。在類初始化過程中或初始化完畢後,根據具體情況才會

Java程式執行和物件建立過程簡述

Java中一個物件建立分為兩個步驟: 載入類,建立物件。 載入類是將所寫的程式.java檔案編譯生成的.class檔案載入到記憶體中,保證了物件建立的預置環境。類載入完畢後才可以建立該類的物件。 第一步:載入類 1. 當開始執行一個類,虛擬機器首先試圖訪問指定啟

物件建立過程中 例項化的順序》摘自《Thinking in JAVA

最近一直在看《Thinking In JAVA》,裡面一些知識點自己平日裡還真沒有注意過: 譬如這部分:在例項化物件的過程,物件的各部分的初始化順序: 總結一下物件的建立過程,假如有個名為Dog的類: 1.即使沒有顯示的使用Static關鍵字,構造器實際上也是靜態方法,

【深入理解Java虛擬機器】Java記憶體區域模型、物件建立過程、常見OOM

本文內容來源於《深入理解Java虛擬機器》一書,非常推薦大家去看一下這本書。最近開始看這本書,打算再開一個相關係列,來總結一下這本書中的重要知識點。呃呃呃,說好的那個圖片請求框架呢~  不要急哈,因為這個請求框架設計的內容還是比較廣的,目前業餘時間正在編寫當中,弄好了之後就會

圖解JAVA物件建立過程

前面幾篇博文分別介紹了JAVA的Class檔案格式、JVM的類載入機制和JVM的記憶體模型,這裡就索性把java物件的建立過程一併說完,這樣java物件的整個建立過程就基本上說明白了(當然你要有基礎才能真正看明白)。經常有人問我為什麼這麼喜歡鑽研底層的東西,首先,因為我以前的

Java中類載入過程物件建立過程

類載入過程: 1, JVM會先去方法區中找有沒有相應類的.class存在。如果有,就直接使用;如果沒有,則把相關類的.class載入到方法區 2, 在.class載入到方法區時,會分為兩部分載入:先載入非靜態內容,再載入靜態內容 3, 載入非靜態內容:把.class中的所有

Java記憶體區域模型、物件建立過程、常見OOM

本文內容來源於《深入理解Java虛擬機器》一書,非常推薦大家去看一下這本書。最近開始看這本書,打算再開一個相關係列,來總結一下這本書中的重要知識點。呃呃呃,說好的那個圖片請求框架呢~  不要急哈,因為這個請求框架設計的內容還是比較廣的,目前業餘時間正在編寫當中,弄好了之

Java調用ARM模板執行Azure Rest建立VM過程

sna string happy data- 交互 disk view manual name Azure Resource Manager 提供一致的管理層,用於管理通過 Azure PowerShell、Azure CLI、Azure 門戶、REST API 和開發工