程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 解析Java法式中對象內存的分派和掌握的根本辦法

解析Java法式中對象內存的分派和掌握的根本辦法

編輯:關於JAVA

解析Java法式中對象內存的分派和掌握的根本辦法。本站提示廣大學習愛好者:(解析Java法式中對象內存的分派和掌握的根本辦法)文章只能為提供參考,不一定能成為您想要的結果。以下是解析Java法式中對象內存的分派和掌握的根本辦法正文


1、對象與內存掌握的常識點

1.java變量的初始化進程,包含部分變量,成員變量(實例變量和類變量)。
2.繼續關系中,當應用的對象援用變量編譯時類型和運轉時類型分歧時,拜訪該對象的屬性和辦法是有差別的。
3.final潤飾符特征。

2、java變量的劃分與初始化進程

java法式的變量年夜體可以分為成員變量和部分變量,成員變量可以分為實例變量(非靜態變量)和類變量(靜態變量),普通我們碰到的部分變量會鄙人列幾種情形中湧現:
(1)形參:在辦法簽名中界說的部分變量,由挪用方為其賦值,跟著辦法停止滅亡。
(2)辦法內的部分變量:在辦法內界說的部分變量必需在辦法內顯示的初始化(賦初始值),跟著變量初始化完成開端,到辦法停止而滅亡。
(3)代碼塊內的部分變量:在代碼塊內界說的部分變量必需在代碼塊內顯示的初始化(賦初始值),跟著初始化完成開端失效,跟著代碼塊的停止而滅亡。

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

應用static潤飾的成員變量是類變量,屬於類自己,沒有效static潤飾的成員變量是實例變量,屬於該類的實例,在統一個JVM外面,每一個類只能對應一個Class對象,但每一個類可以創立多個java對象。(也就是說一個類變量只需一塊內存空間,而該類每創立一次實例,就須要為實例變量分派一塊空間)
   
實例變量的初始化進程:從語法角度來講,法式可以在三個處所對實例變量履行初始化:
(1)界說實例變量時指定初始值。
(2)非靜態塊中對實例變量指定初始值。
(3)結構器中對實例變量指定初始值。
個中(1)和(2)這兩種方法初始化時光都比(3)在結構器中要早,(1)和(2)兩種初始化次序是依照他們在源碼中的分列次序決議的。 
 

package com.zlc.array;
 
public class TestField {
  public TestField(int age){
    System.out.println("結構函數中初始化 this.age = "+this.age);
    this.age = age;
  }
  {
    System.out.println("非靜態塊中初始化");
    age = 22;
  }
  //界說的時刻初始化
  int age = 15;
  public static void main(String[] args) {
    TestField field = new TestField(24);
    System.out.println("終究 age = "+field.age);
  }
}

 

運轉成果為:非靜態塊中初始化
結構函數中初始化 this.age = 15
終究 age = 24
假如會應用javap的話,可以經由過程javap -c XXXX(class文件)看下改java類是若何編譯的。
界說實例變量時指定初始值,初始化塊中為實例變量指定初始值語句位置是對等的,當經由編譯器編譯處置以後,他們都被提到結構器中,下面所說的 int age = 15;會劃分上面兩個步調履行:
1)int age;創立java對象時體系依據該語句為該對象分派內存。
2)age = 15;這條語句會被提取到java類的結構器中履行。
   
類變量的初始化進程:從語法角度來講,法式可以從兩個處所對類變量停止初始化賦值。
(1)界說類變量時指定初始值。
(2)靜態塊中對類變量指定初始值。
兩種履行次序和他們在源碼中的分列次序雷同,我們舉個小失常的例子:

package com.zlc.array;
 
 class TestStatic {
  //類成員 DEMO TestStatic實例
  final static TestStatic DEMO = new TestStatic(15);
  //類成員 age
  static int age = 20;
  //實例變量 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);
  }
}

輸入成果有兩行打印,一個是打印TestStatic類屬性DEMO的實例變量,第二個經由過程java對象staticDemo輸入TestStatic的實例屬性,依據我們下面剖析的實例變量和類變量的初始化流程可以停止揣摸:
1)初始化第一階段,加載類的時刻為類變量DEMO、age分派內存空間,此時DEMO和age的默許值分離是null和0。
2)初始化第二階段,法式按次序順次給DEMO、age賦初始值,TestStatic(15)須要挪用TestStatic的結構器,此時age = 0 所以打印成果為 -15,而當staticDemo被初始化的時刻,age曾經被賦值等於20了,所以輸入成果為5。

3、在繼續關系中繼續成員變量和繼續成員辦法的差別

當創立任何java對象時,法式總會先挪用父類的非靜態塊、父類結構器,最初才挪用本類的非靜態塊和結構器。經由過程子類的結構器挪用父類的結構器普通分為兩種情形,一個是隱式挪用,一個經由過程super顯示挪用父類的結構器。
子類的辦法可以挪用父類的實例變量,這是由於子類繼續了父類就會獲得父類的成員變量和辦法,但父類的辦法不克不及拜訪子類的實例變量,由於父類不曉得它將被哪一個類繼續,它的子類將會增長甚麼樣的成員變量,固然在一些極真個例子外面照樣可以完成父類挪用子類變量的,好比:子類重寫了父類的辦法,普通都邑打印出默許值,由於這個時刻子類的實例變量還沒有初始化。

package com.zlc.array;
class Father{
  int age = 50;
  public Father() {
    // TODO Auto-generated constructor stub
        System.out.println(this.getClass());
        //this.sonMethod();沒法挪用
    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);
  }
    //子類獨有的辦法
    public void sonMethod(){
         System.out.println("Son method");
    }
}

依照我們正常揣摸,經由過程子類隱式的挪用父類的結構器,而在父類的結構器中挪用了info()辦法(留意:我這裡沒有說挪用父類的),按事理來講是輸入了父類的age實例變量,打印成果估計是50,但現實輸入的成果為0,剖析緣由:
1)java對象的內存分派不是在結構器中完成的,結構器只是完成了初始化賦值的進程,也就是在挪用父類的結構器之前,jvm曾經給這個Son對象分類好了內存空間,這個空間寄存了兩個age屬性,一個是子類的age,一個是父類的age。
2)在挪用new Son(28)的時刻,以後的this對象代表著是子類Son的對象,我們可以經由過程把對象.getClass()打印出來就會獲得class com.zlc.array.Son的成果,然則以後初始化進程又是在父類的結構器中停止的,經由過程this.sonMethod()又沒法被挪用,這是由於this的編譯類型是Father的原因。
3)在變量的編譯時類型和運轉時類型分歧時,經由過程該變量拜訪它的援用對象的實例變量時,該實例變量的值由聲明該變量的類型決議,但經由過程該變量挪用它援用的對象的實例辦法時,該辦法的行動由它現實援用的對象決議,所以這裡挪用的是子類的info辦法,所以打印的是子類的age,因為age還沒來得急初始化所以打印默許值0。
淺顯的來講也就是,當聲明的類型和真正new的類型紛歧致的時刻,應用的屬性是父類的,挪用的辦法是子類的。
經由過程javap -c我們更能直接的領會為何繼續屬性和辦法會有很年夜的差別,假如我們把下面例子外面,子類Son的info重寫辦法去失落,這個時刻挪用的會是父類的info辦法,是由於在停止編譯的時刻會把父類的info辦法編譯轉移到子類外面去,而申明的成員變量會留在父類中不停止轉移,如許子類和父類具有了同名的實例變量,而假如子類重寫了父類的同名辦法,則子類的辦法會完整籠罩失落父類的辦法(至於為何java要這麼設計,小我也不太清晰)。同名變量能同時存在不籠罩,同名辦法子類會完全籠罩父類同名辦法。
總的來講關於一個援用變量而言,當經由過程該變量拜訪它所援用的對象的實例變量時,該實例變量的值取決於聲明該變量時類型,當經由過程該變量拜訪它所援用的對象的辦法時,該辦法行動取決於它所現實援用的對象的類型。
最初拿個小case溫習下:

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();
  }
}

想要挪用父類的辦法:可以經由過程super來挪用,但super症結字沒有援用任何對象,它不克不及當作真實的援用變量來應用,有興致的同伙可以本身研討下。
下面引見的都是實例變量和辦法,類變量和類辦法要簡略多了,直接應用類名.辦法就便利了許多,也不會碰到那末多費事。

4、final潤飾符的應用(特殊是宏調換)

(1)inal可以潤飾變量,被final潤飾的變量被賦初始值以後,不克不及對他從新賦值。

(2)inal可以潤飾辦法,被final潤飾的辦法不克不及被重寫。

(3)inal可以潤飾類,被final潤飾的類不克不及派生子類。


被final潤飾的變量必需顯示的指定初始值:
關於是final潤飾的是實例變量,則只能鄙人列三個指定地位賦初始值。
(1)界說final實例變量時指定初始值。
(2)在非靜態塊中為final實例變量指定初始值。
(3)在結構器中為final實例變量指定初始值。
終究都邑被提到結構器中停止初始化。
關於用final指定的類變量:只能在指定的兩個處所停止賦初始值。
(1)界說final類變量的時刻指定初始值。
(2)在靜態塊中為final類變量指定初始值。
異樣經由編譯器處置,分歧於實例變量的是,類變量都是提到靜態塊中停止賦初始值,而實例變量是提到結構器中完成。
   被final潤飾的類變量還有一種特征,就是“宏調換”,當被潤飾的類變量知足在界說該變量的時刻就指定初始值,並且這個初始值在編譯的時刻就可以肯定上去(好比:18、"aaaa"、16.78等一些直接量),那末該final潤飾的類變量不在是一個變量,體系就會當做“宏變量”處置(就是我們常說的常量),假如在編譯的時刻就可以肯定初始值,則就不會被提到靜態塊中停止初始化了,直接在類界說中直接使該初始值取代失落final變量。我們照樣舉誰人年紀減去year的例子:

package com.zlc.array;
 
 class TestStatic {
  //類成員 DEMO TestStatic實例
  final static TestStatic DEMO = new TestStatic(15);
  //類成員 age
  final static int age = 20;
  //實例變量 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);
  }
}

這個時刻的age 被final潤飾了,所以在編譯的時刻,父類中一切的age都釀成了20,而不是一個變量,如許輸入的成果就可以到達我們的預期。
特殊是在對字符串停止比擬的時刻更能顯示出

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 或許不加都行 後面兩個能被宏替換就好了
  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潤飾辦法和類沒有甚麼好說的,只是一個不克不及被子類重寫(和private一樣),一個不克不及派生子類。
用final潤飾部分變量的時刻,Java請求被外部類拜訪的部分變量都是用final潤飾,這個是有緣由的,關於通俗部分變量而言,它的感化域就逗留在該辦法內,當辦法停止時,該部分變量也就消逝了,但外部類能夠發生隱式的“閉包”,閉包使得部分變量離開他地點的辦法持續存在。
有時刻在會在一個辦法外面new 一個線程,然後挪用該辦法的部分變量,這個時刻須要把轉變量聲明為final潤飾的。

5、對象占用內存的盤算辦法
應用system.gc()和java.lang.Runtime類中的freeMemory(),totalMemory(),maxMemory()這幾個辦法丈量Java對象的年夜小。這類辦法平日應用在須要對許多資本停止准確肯定對象的年夜小。這類辦法簡直無用等臨盆體系緩存的完成。這類辦法的長處是數據類型年夜小有關的,分歧的操作體系,都可以獲得占用的內存。

它應用反射API用於遍歷對象的成員變量的條理構造和盤算一切原始變量的年夜小。這類辦法不須要如斯多的資本,可用於緩存的完成。缺陷是原始類型年夜小是分歧的分歧的JVM完成對應有分歧的盤算辦法。
JDK5.0以後Instrumentation API供給了 getObjectSize辦法來盤算對象占用的內存年夜小。
默許情形下並沒有盤算到援用對象的年夜小,為了盤算援用對象,可使用反射獲得。上面這個辦法是下面文章外面供給的一個盤算包括援用對象年夜小的完成:

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;
  }
} 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved