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

解析Java程序中對(duì)象內(nèi)存的分配和控制的基本方法

 更新時(shí)間:2016年04月29日 08:57:57   作者:zhoulc  
這篇文章主要介紹了解析Java程序中對(duì)象內(nèi)存的分配和控制的基本方法,包括計(jì)算對(duì)象的內(nèi)存占用的方法,要的朋友可以參考下

一、對(duì)象與內(nèi)存控制的知識(shí)點(diǎn)

1.java變量的初始化過(guò)程,包括局部變量,成員變量(實(shí)例變量和類(lèi)變量)。
2.繼承關(guān)系中,當(dāng)使用的對(duì)象引用變量編譯時(shí)類(lèi)型和運(yùn)行時(shí)類(lèi)型不同時(shí),訪問(wèn)該對(duì)象的屬性和方法是有區(qū)別的。
3.final修飾符特性。

二、java變量的劃分與初始化過(guò)程

java程序的變量大體可以分為成員變量和局部變量,成員變量可以分為實(shí)例變量(非靜態(tài)變量)和類(lèi)變量(靜態(tài)變量),一般我們遇到的局部變量會(huì)在下列幾種情況中出現(xiàn):
(1)形參:在方法簽名中定義的局部變量,由調(diào)用方為其賦值,隨著方法結(jié)束消亡。
(2)方法內(nèi)的局部變量:在方法內(nèi)定義的局部變量必須在方法內(nèi)顯示的初始化(賦初始值),隨著變量初始化完成開(kāi)始,到方法結(jié)束而消亡。
(3)代碼塊內(nèi)的局部變量:在代碼塊內(nèi)定義的局部變量必須在代碼塊內(nèi)顯示的初始化(賦初始值),隨著初始化完成開(kāi)始生效,隨著代碼塊的結(jié)束而消亡。

package com.zlc.array;
 
public class TestField {
  {
    String b ;
    //如果不初始化,編譯器就會(huì)報(bào)The local variable b may not have been initialized
    System.out.println(b);
  }
  public static void main(String[] args) {
    int a ;
    //如果不初始化,編譯器就會(huì)報(bào)The local variable a may not have been initialized
    System.out.println(a);
  }
}

使用static修飾的成員變量是類(lèi)變量,屬于類(lèi)本身,沒(méi)有用static修飾的成員變量是實(shí)例變量,屬于該類(lèi)的實(shí)例,在同一個(gè)JVM里面,每個(gè)類(lèi)只能對(duì)應(yīng)一個(gè)Class對(duì)象,但每個(gè)類(lèi)可以創(chuàng)建多個(gè)java對(duì)象。(也就是說(shuō)一個(gè)類(lèi)變量只需一塊內(nèi)存空間,而該類(lèi)每創(chuàng)建一次實(shí)例,就需要為實(shí)例變量分配一塊空間)
   
實(shí)例變量的初始化過(guò)程:從語(yǔ)法角度來(lái)說(shuō),程序可以在三個(gè)地方對(duì)實(shí)例變量執(zhí)行初始化:
(1)定義實(shí)例變量時(shí)指定初始值。
(2)非靜態(tài)塊中對(duì)實(shí)例變量指定初始值。
(3)構(gòu)造器中對(duì)實(shí)例變量指定初始值。
其中(1)和(2)這兩種方式初始化時(shí)間都比(3)在構(gòu)造器中要早,(1)和(2)兩種初始化順序是按照他們?cè)谠创a中的排列順序決定的。 
 

package com.zlc.array;
 
public class TestField {
  public TestField(int age){
    System.out.println("構(gòu)造函數(shù)中初始化 this.age = "+this.age);
    this.age = age;
  }
  {
    System.out.println("非靜態(tài)塊中初始化");
    age = 22;
  }
  //定義的時(shí)候初始化
  int age = 15;
  public static void main(String[] args) {
    TestField field = new TestField(24);
    System.out.println("最終 age = "+field.age);
  }
}

 

運(yùn)行結(jié)果為:非靜態(tài)塊中初始化
構(gòu)造函數(shù)中初始化 this.age = 15
最終 age = 24
如果會(huì)使用javap的話(huà),可以通過(guò)javap -c XXXX(class文件)看下改java類(lèi)是如何編譯的。
定義實(shí)例變量時(shí)指定初始值,初始化塊中為實(shí)例變量指定初始值語(yǔ)句地位是平等的,當(dāng)經(jīng)過(guò)編譯器編譯處理之后,他們都被提到構(gòu)造器中,上面所說(shuō)的 int age = 15;會(huì)劃分下面兩個(gè)步驟執(zhí)行:
1)int age;創(chuàng)建java對(duì)象時(shí)系統(tǒng)根據(jù)該語(yǔ)句為該對(duì)象分配內(nèi)存。
2)age = 15;這條語(yǔ)句會(huì)被提取到j(luò)ava類(lèi)的構(gòu)造器中執(zhí)行。
   
類(lèi)變量的初始化過(guò)程:從語(yǔ)法角度來(lái)說(shuō),程序可以從兩個(gè)地方對(duì)類(lèi)變量進(jìn)行初始化賦值。
(1)定義類(lèi)變量時(shí)指定初始值。
(2)靜態(tài)塊中對(duì)類(lèi)變量指定初始值。
兩種執(zhí)行順序和他們?cè)谠创a中的排列順序相同,我們舉個(gè)小變態(tài)的例子:

package com.zlc.array;
 
 class TestStatic {
  //類(lèi)成員 DEMO TestStatic實(shí)例
  final static TestStatic DEMO = new TestStatic(15);
  //類(lèi)成員 age
  static int age = 20;
  //實(shí)例變量 curAge
  int curAge;
   
  public TestStatic(int years) {
    // TODO Auto-generated constructor stub
    curAge = age - years;
  }
}
 public class Test{
  public static void main(String[] args) {
    System.out.println(TestStatic.DEMO.curAge);
    TestStatic staticDemo = new TestStatic(15);
    System.out.println(staticDemo.curAge);
  }
}

輸出結(jié)果有兩行打印,一個(gè)是打印TestStatic類(lèi)屬性DEMO的實(shí)例變量,第二個(gè)通過(guò)java對(duì)象staticDemo輸出TestStatic的實(shí)例屬性,根據(jù)我們上面分析的實(shí)例變量和類(lèi)變量的初始化流程可以進(jìn)行推斷:
1)初始化第一階段,加載類(lèi)的時(shí)候?yàn)轭?lèi)變量DEMO、age分配內(nèi)存空間,此時(shí)DEMO和age的默認(rèn)值分別是null和0。
2)初始化第二階段,程序按順序依次給DEMO、age賦初始值,TestStatic(15)需要調(diào)用TestStatic的構(gòu)造器,此時(shí)age = 0 所以打印結(jié)果為 -15,而當(dāng)staticDemo被初始化的時(shí)候,age已經(jīng)被賦值等于20了,所以輸出結(jié)果為5。

三、在繼承關(guān)系中繼承成員變量和繼承成員方法的區(qū)別

當(dāng)創(chuàng)建任何java對(duì)象時(shí),程序總會(huì)先調(diào)用父類(lèi)的非靜態(tài)塊、父類(lèi)構(gòu)造器,最后才調(diào)用本類(lèi)的非靜態(tài)塊和構(gòu)造器。通過(guò)子類(lèi)的構(gòu)造器調(diào)用父類(lèi)的構(gòu)造器一般分為兩種情況,一個(gè)是隱式調(diào)用,一個(gè)通過(guò)super顯示調(diào)用父類(lèi)的構(gòu)造器。
子類(lèi)的方法可以調(diào)用父類(lèi)的實(shí)例變量,這是因?yàn)樽宇?lèi)繼承了父類(lèi)就會(huì)獲取父類(lèi)的成員變量和方法,但父類(lèi)的方法不能訪問(wèn)子類(lèi)的實(shí)例變量,因?yàn)楦割?lèi)不知道它將被哪個(gè)類(lèi)繼承,它的子類(lèi)將會(huì)增加什么樣的成員變量,當(dāng)然在一些極端的例子里面還是可以實(shí)現(xiàn)父類(lèi)調(diào)用子類(lèi)變量的,比如:子類(lèi)重寫(xiě)了父類(lèi)的方法,一般都會(huì)打印出默認(rèn)值,因?yàn)檫@個(gè)時(shí)候子類(lèi)的實(shí)例變量還沒(méi)有初始化。

package com.zlc.array;
class Father{
  int age = 50;
  public Father() {
    // TODO Auto-generated constructor stub
        System.out.println(this.getClass());
        //this.sonMethod();無(wú)法調(diào)用
    info();
  }
  public void info(){
    System.out.println(age);
  }
}
public class Son extends Father{
  int age = 24;
  public Son(int age) {
    // TODO Auto-generated constructor stub
    this.age = age;
  }
  @Override
  public void info() {
    // TODO Auto-generated method stub
    System.err.println(age);
  }
  public static void main(String[] args) {
    new Son(28);
  }
    //子類(lèi)特有的方法
    public void sonMethod(){
         System.out.println("Son method");
    }
}

按照我們正常推斷,通過(guò)子類(lèi)隱式的調(diào)用父類(lèi)的構(gòu)造器,而在父類(lèi)的構(gòu)造器中調(diào)用了info()方法(注意:我這里沒(méi)有說(shuō)調(diào)用父類(lèi)的),按道理來(lái)說(shuō)是輸出了父類(lèi)的age實(shí)例變量,打印結(jié)果預(yù)計(jì)是50,但實(shí)際輸出的結(jié)果為0,分析原因:
1)java對(duì)象的內(nèi)存分配不是在構(gòu)造器中完成的,構(gòu)造器只是完成了初始化賦值的過(guò)程,也就是在調(diào)用父類(lèi)的構(gòu)造器之前,jvm已經(jīng)給這個(gè)Son對(duì)象分類(lèi)好了內(nèi)存空間,這個(gè)空間存放了兩個(gè)age屬性,一個(gè)是子類(lèi)的age,一個(gè)是父類(lèi)的age。
2)在調(diào)用new Son(28)的時(shí)候,當(dāng)前的this對(duì)象代表著是子類(lèi)Son的對(duì)象,我們可以通過(guò)把對(duì)象.getClass()打印出來(lái)就會(huì)得到class com.zlc.array.Son的結(jié)果,但是當(dāng)前初始化過(guò)程又是在父類(lèi)的構(gòu)造器中進(jìn)行的,通過(guò)this.sonMethod()又無(wú)法被調(diào)用,這是因?yàn)閠his的編譯類(lèi)型是Father的緣故。
3)在變量的編譯時(shí)類(lèi)型和運(yùn)行時(shí)類(lèi)型不同時(shí),通過(guò)該變量訪問(wèn)它的引用對(duì)象的實(shí)例變量時(shí),該實(shí)例變量的值由聲明該變量的類(lèi)型決定,但通過(guò)該變量調(diào)用它引用的對(duì)象的實(shí)例方法時(shí),該方法的行為由它實(shí)際引用的對(duì)象決定,所以這里調(diào)用的是子類(lèi)的info方法,所以打印的是子類(lèi)的age,由于age還沒(méi)來(lái)得急初始化所以打印默認(rèn)值0。
通俗的來(lái)說(shuō)也就是,當(dāng)聲明的類(lèi)型和真正new的類(lèi)型不一致的時(shí)候,使用的屬性是父類(lèi)的,調(diào)用的方法是子類(lèi)的。
通過(guò)javap -c我們更能直接的體會(huì)為什么繼承屬性和方法會(huì)有很大的區(qū)別,如果我們把上面例子里面,子類(lèi)Son的info重寫(xiě)方法去掉,這個(gè)時(shí)候調(diào)用的會(huì)是父類(lèi)的info方法,是因?yàn)樵谶M(jìn)行編譯的時(shí)候會(huì)把父類(lèi)的info方法編譯轉(zhuǎn)移到子類(lèi)里面去,而聲名的成員變量會(huì)留在父類(lèi)中不進(jìn)行轉(zhuǎn)移,這樣子類(lèi)和父類(lèi)擁有了同名的實(shí)例變量,而如果子類(lèi)重寫(xiě)了父類(lèi)的同名方法,則子類(lèi)的方法會(huì)完全覆蓋掉父類(lèi)的方法(至于為什么java要這么設(shè)計(jì),個(gè)人也不太清楚)。同名變量能同時(shí)存在不覆蓋,同名方法子類(lèi)會(huì)徹底覆蓋父類(lèi)同名方法。
總的來(lái)說(shuō)對(duì)于一個(gè)引用變量而言,當(dāng)通過(guò)該變量訪問(wèn)它所引用的對(duì)象的實(shí)例變量時(shí),該實(shí)例變量的值取決于聲明該變量時(shí)類(lèi)型,當(dāng)通過(guò)該變量訪問(wèn)它所引用的對(duì)象的方法時(shí),該方法行為取決于它所實(shí)際引用的對(duì)象的類(lèi)型。
最后拿個(gè)小case復(fù)習(xí)下:

package com.zlc.array;
class Animal{
  int age ;
  public Animal(){
     
  }
  public Animal(int age) {
    // TODO Auto-generated constructor stub
    this.age = age;
  }
  void run(){
    System.out.println("animal run "+age);
  }
}
class Dog extends Animal{
  int age;
  String name;
  public Dog(int age,String name) {
    // TODO Auto-generated constructor stub
    this.age = age;
    this.name = name;
  }
  @Override
  void run(){
    System.out.println("dog run "+age);
  }
}
public class TestExtends {
  public static void main(String[] args) {
    Animal animal = new Animal(5);
    System.out.println(animal.age);
    animal.run();
     
    Dog dog = new Dog(1, "xiaobai");
    System.out.println(dog.age);
    dog.run();
     
    Animal animal2 = new Dog(11, "wangcai");
    System.out.println(animal2.age);
    animal2.run();
     
    Animal animal3;
    animal3 = dog;
    System.out.println(animal3.age);
    animal3.run();
  }
}

想要調(diào)用父類(lèi)的方法:可以通過(guò)super來(lái)調(diào)用,但super關(guān)鍵字沒(méi)有引用任何對(duì)象,它不能當(dāng)做真正的引用變量來(lái)使用,有興趣的朋友可以自己研究下。
上面介紹的都是實(shí)例變量和方法,類(lèi)變量和類(lèi)方法要簡(jiǎn)單多了,直接使用類(lèi)名.方法就方便了很多,也不會(huì)遇到那么多麻煩。

四、final修飾符的使用(特別是宏替換)

(1)inal可以修飾變量,被final修飾的變量被賦初始值之后,不能對(duì)他重新賦值。

(2)inal可以修飾方法,被final修飾的方法不能被重寫(xiě)。

(3)inal可以修飾類(lèi),被final修飾的類(lèi)不能派生子類(lèi)。


被final修飾的變量必須顯示的指定初始值:
對(duì)于是final修飾的是實(shí)例變量,則只能在下列三個(gè)指定位置賦初始值。
(1)定義final實(shí)例變量時(shí)指定初始值。
(2)在非靜態(tài)塊中為final實(shí)例變量指定初始值。
(3)在構(gòu)造器中為final實(shí)例變量指定初始值。
最終都會(huì)被提到構(gòu)造器中進(jìn)行初始化。
對(duì)于用final指定的類(lèi)變量:只能在指定的兩個(gè)地方進(jìn)行賦初始值。
(1)定義final類(lèi)變量的時(shí)候指定初始值。
(2)在靜態(tài)塊中為final類(lèi)變量指定初始值。
同樣經(jīng)過(guò)編譯器處理,不同于實(shí)例變量的是,類(lèi)變量都是提到靜態(tài)塊中進(jìn)行賦初始值,而實(shí)例變量是提到構(gòu)造器中完成。
   被final修飾的類(lèi)變量還有一種特性,就是“宏替換”,當(dāng)被修飾的類(lèi)變量滿(mǎn)足在定義該變量的時(shí)候就指定初始值,而且這個(gè)初始值在編譯的時(shí)候就能確定下來(lái)(比如:18、"aaaa"、16.78等一些直接量),那么該final修飾的類(lèi)變量不在是一個(gè)變量,系統(tǒng)就會(huì)當(dāng)成“宏變量”處理(就是我們常說(shuō)的常量),如果在編譯的時(shí)候就能確定初始值,則就不會(huì)被提到靜態(tài)塊中進(jìn)行初始化了,直接在類(lèi)定義中直接使該初始值代替掉final變量。我們還是舉那個(gè)年齡減去year的例子:

package com.zlc.array;
 
 class TestStatic {
  //類(lèi)成員 DEMO TestStatic實(shí)例
  final static TestStatic DEMO = new TestStatic(15);
  //類(lèi)成員 age
  final static int age = 20;
  //實(shí)例變量 curAge
  int curAge;
   
  public TestStatic(int years) {
    // TODO Auto-generated constructor stub
    curAge = age - years;
  }
}
 public class Test{
  public static void main(String[] args) {
    System.out.println(TestStatic.DEMO.curAge);
    TestStatic static1 = new TestStatic(15);
    System.out.println(static1.curAge);
  }
}

這個(gè)時(shí)候的age 被final修飾了,所以在編譯的時(shí)候,父類(lèi)中所有的age都變成了20,而不是一個(gè)變量,這樣輸出的結(jié)果就能達(dá)到我們的預(yù)期。
特別是在對(duì)字符串進(jìn)行比較的時(shí)候更能顯示出

package com.zlc.array;
 
public class TestString {
  static String static_name1 = "java";
  static String static_name2 = "me";
  static String statci_name3 = static_name1+static_name2;
   
  final static String final_static_name1 = "java";
  final static String final_static_name2 = "me";
  //加final 或者不加都行 前面兩個(gè)能被宏替換就行了
  final static String final_statci_name3 = final_static_name1+final_static_name2;
   
  public static void main(String[] args) {
    String name1 = "java";
    String name2 = "me";
    String name3 = name1+name2;
    //(1)
    System.out.println(name3 == "javame");
    //(2)
    System.out.println(TestString.statci_name3 == "javame");
    //(3)
    System.out.println(TestString.final_statci_name3 == "javame");
  }
}

用final修飾方法和類(lèi)沒(méi)有什么好說(shuō)的,只是一個(gè)不能被子類(lèi)重寫(xiě)(和private一樣),一個(gè)不能派生子類(lèi)。
用final修飾局部變量的時(shí)候,Java要求被內(nèi)部類(lèi)訪問(wèn)的局部變量都是用final修飾,這個(gè)是有原因的,對(duì)于普通局部變量而言,它的作用域就停留在該方法內(nèi),當(dāng)方法結(jié)束時(shí),該局部變量也就消失了,但內(nèi)部類(lèi)可能產(chǎn)生隱式的“閉包”,閉包使得局部變量脫離他所在的方法繼續(xù)存在。
有時(shí)候在會(huì)在一個(gè)方法里面new 一個(gè)線(xiàn)程,然后調(diào)用該方法的局部變量,這個(gè)時(shí)候需要把改變量聲明為final修飾的。

五、對(duì)象占用內(nèi)存的計(jì)算方法
使用system.gc()和java.lang.Runtime類(lèi)中的freeMemory(),totalMemory(),maxMemory()這幾個(gè)方法測(cè)量Java對(duì)象的大小。這種方法通常使用在需要對(duì)很多資源進(jìn)行精確確定對(duì)象的大小。這種方法幾乎無(wú)用等生產(chǎn)系統(tǒng)緩存的實(shí)現(xiàn)。這種方法的優(yōu)點(diǎn)是數(shù)據(jù)類(lèi)型大小無(wú)關(guān)的,不同的操作系統(tǒng),都可以得到占用的內(nèi)存。

它使用反射API用于遍歷對(duì)象的成員變量的層次結(jié)構(gòu)和計(jì)算所有原始變量的大小。這種方法不需要如此多的資源,可用于緩存的實(shí)現(xiàn)。缺點(diǎn)是原始類(lèi)型大小是不同的不同的JVM實(shí)現(xiàn)對(duì)應(yīng)有不同的計(jì)算方法。
JDK5.0之后Instrumentation API提供了 getObjectSize方法來(lái)計(jì)算對(duì)象占用的內(nèi)存大小。
默認(rèn)情況下并沒(méi)有計(jì)算到引用對(duì)象的大小,為了計(jì)算引用對(duì)象,可以使用反射獲取。下面這個(gè)方法是上面文章里面提供的一個(gè)計(jì)算包含引用對(duì)象大小的實(shí)現(xiàn):

public class SizeOfAgent {

  static Instrumentation inst;

  /** initializes agent */
  public static void premain(String agentArgs, Instrumentation instP) {
    inst = instP;      
  }

  /**
   * Returns object size without member sub-objects.
   * @param o object to get size of
   * @return object size
   */
  public static long sizeOf(Object o) {
    if(inst == null) {
      throw new IllegalStateException("Can not access instrumentation environment.\n" +
                    "Please check if jar file containing SizeOfAgent class is \n" +
                    "specified in the java's \"-javaagent\" command line argument.");
    }
    return inst.getObjectSize(o);
  }

  /**
   * Calculates full size of object iterating over
   * its hierarchy graph.
   * @param obj object to calculate size of
   * @return object size
   */
  public static long fullSizeOf(Object obj) {
    Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
    Stack<Object> stack = new Stack<Object>();

    long result = internalSizeOf(obj, stack, visited);
    while (!stack.isEmpty()) {
      result += internalSizeOf(stack.pop(), stack, visited);
    }
    visited.clear();
    return result;
  }        

  private static boolean skipObject(Object obj, Map<Object, Object> visited) {
    if (obj instanceof String) {
      // skip interned string
      if (obj == ((String) obj).intern()) {
        return true;
      }
    }
    return (obj == null) // skip visited object
        || visited.containsKey(obj);
  }

  private static long internalSizeOf(Object obj, Stack<Object> stack, Map<Object, Object> visited) {
    if (skipObject(obj, visited)){
      return 0;
    }
    visited.put(obj, null);

    long result = 0;
    // get size of object + primitive variables + member pointers 
    result += SizeOfAgent.sizeOf(obj);

    // process all array elements
    Class clazz = obj.getClass();
    if (clazz.isArray()) {
      if(clazz.getName().length() != 2) {// skip primitive type array
        int length = Array.getLength(obj);
        for (int i = 0; i < length; i++) {
          stack.add(Array.get(obj, i));
        } 
      }    
      return result;
    }

    // process all fields of the object
    while (clazz != null) {
      Field[] fields = clazz.getDeclaredFields();
      for (int i = 0; i < fields.length; i++) {
        if (!Modifier.isStatic(fields[i].getModifiers())) {
          if (fields[i].getType().isPrimitive()) {
            continue; // skip primitive fields
          } else {
            fields[i].setAccessible(true);
            try {
              // objects to be estimated are put to stack
              Object objectToAdd = fields[i].get(obj);
              if (objectToAdd != null) {            
                stack.add(objectToAdd);
              }
            } catch (IllegalAccessException ex) { 
              assert false; 
            }
            }
          }
      }
      clazz = clazz.getSuperclass();
    }
    return result;
  }
} 

相關(guān)文章

最新評(píng)論

武定县| 香港 | 池州市| 成安县| 容城县| 大丰市| 正镶白旗| 新巴尔虎左旗| 珲春市| 城步| 贵阳市| 普洱| 剑川县| 永城市| 余江县| 晋江市| 怀仁县| 南部县| 夏津县| 延寿县| 家居| 平江县| 贵溪市| 岳普湖县| 广丰县| 洛扎县| 沁水县| 镇远县| 宝山区| 林周县| 曲阳县| 松溪县| 湟源县| 锦州市| 胶州市| 东兰县| 新源县| 宁安市| 祁东县| 彝良县| 翼城县|