目錄
1、Java內存分配
棧: 局部變量中的基本數據類型直接存入棧中
局部變量中對對象的引用(指針)存在棧中
堆: 成員變量全部存在堆中(包括:基本數據類型、對象引用和對象引用的實例)
【成員變量屬於類,類最終都是要被new 出來的,所以成員變量全部存儲在堆中】
public class Test_Subject {
//成員變量
private String hello; //存儲於堆中
//成員變量
private Person per=new Person(); //per是對Person對象的引用,存儲於堆中,new出來的Person對象也存儲於堆中
class Person{
//實例變量
String name; //name存儲在Person對象所屬的堆空間中
}
public void showAge(int a){ //參數a也是局部變量 存儲於棧中
//局部變量
int age=23; //age存儲在棧中
//p也是局部變量
Person p=new Person(); //p存儲在棧, new Person()這個對象存儲在堆中
}
}
2、方法棧
存放方法的棧中保存了方法的狀態,包括執行到了那一行代碼,還有所有的局部變量。
下面是各個方法互相調用時,它們存放於棧中的狀態。(正在執行的方法都會被放在棧頂。)
3、一個對象的內存
一個對象的內存=所有實例變量使用的內存+對象本身的開銷(一般是16字節)
對象本身的開銷=一個指向對象類的引用+垃圾回收信息+同步信息
另外一般內存的使用都會被填充爲8字節(64位計算機中的機器字)的倍數。不夠8的倍數,填充爲8的倍數。
Integer對象內存(24字節)= 對象開銷(16字節)+int(4字節)+填充字節(4字節)
Date對象內存(32字節)=對象開銷(16字節)+3*int(12字節)+填充字節(4字節)
Counter對象內存(32字節)=對象開銷(16)+String(8)+int(4)+填充字節(4)、
Node內存(40)=對象開銷(16)+額外開銷(8)+Item的引用(8)+Node的引用(8)
因爲Node是一個非靜態的內部類,所以它持有對外部類的引用,額外開銷就是來之於對外部類的引用。
對對象的引用,其實就是一個機器地址,一個機器地址所需內存一般是8字節(64位架構的計算機,32爲架構的是4字節)
4、構造函數
- 構造函數會在new的時候被調用,它快於賦值給引用它的變量的賦值過程。
- 構造函數不會有返回類型。
- 構造函數不會被繼承。
- 構造函數只有在你沒有寫任何構造函數的時候,纔會給你創建一個沒有參數的構造函數。
- 如果你寫了有參數的構造函數,如果你還想要有一個沒有參數的構造函數,就必須自己寫出來,編譯器不會給你創建。
4.1、繼承與構造函數
- 一個子類佔用的內存大小除了自身的對象開銷+所有實例變量的大小以外,還要加上父類的內存。
- 當你在new一個子類的時候,第一件事會調用父類的構造函數,而且一定會調用(因爲完整的對象也需要完整的父類,你想要調用父類的所有方法和狀態,就必須構造父類)。
- 在構造函數中,通過super()函數來調用父類的構造函數。
- 抽象的類也有構造函數,雖然你不能new一個抽象類,但是當你在new它的子類的時候會調用抽象類的構造函數。
4.2、如果我們沒調用super()函數會怎樣?
編譯器會幫我們加上super()的調用。添加的方式會有兩種:
1、如果你沒有寫構造函數,它會默認會給你添加一個無參的構造函數,並添加上super()函數。
2、如果你有構造函數,那麼它會對每個重載版本的構造函數上添加上super()函數。
注意:編譯器添加的一定是沒有參數的構造函數和super()函數,假如父類有多個版本的構造函數,那麼只有無參的版本會被調用。
對於super()的調用必須是構造函數的第一條語句。
public class Test_Constructor {
static class Animal{
public String name;
public final int age=1;
public Animal() {
System.out.println("你好");
}
}
static class Dog extends Animal{
public Dog() {
System.out.println("汪汪");
}
public Dog(String name) {
System.out.println("汪汪");
}
}
public static void main(String[] args) {
Dog a=new Dog();
Dog b=new Dog("小花");
}
}
//輸出結果
你好
汪汪
你好
汪汪
4.3、this()函數
- this()函數是從某個構造函數調用同一個類的另外一個構造函數,只需要改變this()的參數就可以調用;
- this()函數只能在構造函數中,且只能是第一條語句;
- super()與this()不能兼得,只能出現一個。
5、對象的存活
public class Test_Object {
static class Person {
String name; // 實例變量的壽命與Person對象相同,如果該對象存活,該變量也會存活
}
public void showAge(int a) {
int age = 23; // 局部變量只會存活在該方法調用的時候,當該方法執行完畢,那麼該變量消失
showName();
}
/**
* 1、當showAge方法在調用ShowName方法的時候,showAge方法以及它的變量age都是存活的,只是不處於棧頂;
* 2、這個時候的age變量是無法被使用的,只有在棧頂的showName函數中的name變量可以被使用;
* 3、當這兩個函數都執行完成之後,showAge函數和它的變量纔會消失。
*/
public void showName() {
String name = "zhangSan";
System.out.println("name");
}
public static void main(String[] args) {
/**
* Perosn對象的生命週期取決於它的引用變量p,只要p一直引用着Person對象,那麼該對象就會一直存活。
*/
Person p=new Person();
}
}
6、釋放對象的3種方式
public class Test_Clear_Object {
class Person {
String name;
}
/**
* 方式一:將對象的引用變量放在方法中,這樣在方法執行完成之後,p1引用變量就會消失,
* 這樣對Person對象的引用就沒有了,該對象就可以被GC回收了。
*/
public void clear1(){
Person p1=new Person();
}
/**
* 方式二:引用被賦值到其他對象上,這樣new的第一個Person對象就沒有了引用,可以被回收。
*/
public void clear2(){
Person p2=new Person();
p2=new Person();
}
/**
* 方式三:直接將引用置空,這樣Person對象就沒被引用了。
*/
public void clear3(){
Person p3=new Person();
p3=null;
}
}