淺談Java中的初始化和清理


引言

  這篇文章我們主要介紹Java初始化和清理的相關內容,這些內容雖然比較基礎,但是還是在這邊做一個簡單的總結,方便以后查閱。

初始化過程

  Java盡力保證:所有變量在使用之前都會得到恰當的初始化(對於方法的局部變量,Java會以編譯時錯誤的形式來提醒程序員進行初始化)。

  1、類數據成員的初始化

  類成員初始化過程是這樣的:當我們實例化一個對象時,編譯器會首先執行數據成員的初始化過程,然后在執行構造器。下面我們通過一個簡單的例子來介紹下類數據成員的初始化過程。代碼如下:

 1 class Sample {
 2     int value;
 3     
 4     public Sample() {
 5         this(0);
 6     }
 7     
 8     public Sample(int value) {
 9         this.value=value;
10         System.out.println(this.value);
11     }
12     
13     @Override
14     public String toString() {
15         return String.valueOf(this.value);
16     }
17 }
18 
19 public class PracClass {
20     private int id;
21     private String name;
22     private static Sample sample=new Sample();
23     
24     public PracClass() {
25         this(1,"defaltName");
26     }
27     
28     public PracClass(int id,String name){
29         this.id=id;
30         this.name=name;
31         System.out.println(this.id+" "+this.name);
32     }
33     
34     @Override
35     public String toString() {
36         return this.id+" "+this.name+" SampleValue: "+sample;
37     }
38     
39     public static void main(String[] args) {
40         PracClass pracClass=new PracClass();
41         System.out.println(pracClass);
42     }
43 }

  輸出結果如下:

1 0
2 1 defaltName
3 1 defaltName SampleValue: 0

  我們看到main函數實例化了一個PracClass對象。在PracClass類中其中有一個static對象sample。我們從輸出結果中,可以看到編譯器先執行了類成員數據的初始化過程(靜態的和非靜態的都是)。然后再執行對象的構造器。

  2、繼承關系下的初始化過程

  我們來看一下存在繼承關系下,類成員數據是如何初始化的,下面我們還是通過簡單的例子來看一下:

 1 class Insect {
 2     private int i=9;
 3     protected int j;
 4     
 5     public Insect() {
 6         System.out.println("i="+i+","+j);
 7         j=20;
 8     }
 9     private static int x1=printInit("static Insect.x1 init");
10     
11     static int printInit(String s){
12         System.out.println(s);
13         return 16;
14     }
15 }
16 
17 
18 public class PracClass extends Insect {
19     private int k=printInit("Beetle.k init");
20     public PracClass() {
21         System.out.println("k="+k);
22         System.out.println("j="+j);
23     }
24     private static int x2=printInit("Beetle.x2 init");
25     
26     public static void main(String[] args) {
27         System.out.println("Beetle contructor");
28         PracClass pracClass=new PracClass();
29     }
30 }

  我們來看一下運行結果:

1 static Insect.x1 init
2 Beetle.x2 init
3 Beetle contructor
4 i=9,0
5 Beetle.k init
6 k=16
7 j=20

  當程序運行時,第一步就是去訪問main方法,這時候加載器開始啟動並且找出PracClass類的編譯代碼么。在這個加載過程中,編譯器注意到它有一個基類,於是它繼續進行加載基類(如果該基類還有其自身的基類,那么第二個基類就會被加載,如此類推)。這時候根基類中的static初始化,然后是下一個導出類。為什么要這樣的?因為導出類的static初始化可能會依賴於基類成員是否被正確初始化。

  這時候,必要的類都已經加載完畢。下面開始創建對象,這時候基類的實例變量初始化過程,然后執行基類構造器,然后執行導出類的實例變量初始化過程,然后是導出類的構造器。

清理過程

  我們知道Java相比C++最大的一個差異之處在於Java是提供垃圾收集器的。但是垃圾收集器只回收那些使用new關鍵字分配的內存。對於不是使用new關鍵字來獲取的內存,垃圾收集器是不知道如何釋放的。Java為了應對這種情況允許在類中定義一個finalize的方法。它的工作原理是這樣的:一旦垃圾回收器准備好釋放對象占用的存儲空間,將首先調用其finalize方法,並且在下一次垃圾回收時,才會真正的回收對象占用的內存。

  通過上面的描述,我們知道finalize方法是不能作為通用的清理方法的。我們建議寫一個顯示的終止方法(類似於Stream中的close、Timer中的cancle等)那么在什么情況下才能使用finalize方法呢?下面兩種情況可以使用finalize方法。

1、finalize方法作為安全網

  我們顯示的終結方法一般是使用try-finally結構來使用的。在finally方法內部調用顯示的終止方法,可以保證即使在使用對象過程中發生異常,該終結方法還是會執行。

  當對象的所有者忘記調用前面段落中建議的顯示的終止方法時,finalize方法可以充當“安全網”的角色。雖然這樣做不能保證終止方法會被及時的調用。但至少在無法通過調用顯示終止方法來正常結束操作的情況下,遲一點釋放關鍵資源總比不釋放要好。如果我們在終結方法中發現資源還未被終止,那么應該在日志中記錄一條警告,這樣表示顯示的終止方法可能沒有被調用,此處需要進行修復。

2、與本地方法有關

  如果在Java中使用JNI技術(即使用C/C++進行內存分配)。那么Java的垃圾收集器是無法進行內存回收的。這時候推薦使用顯示的終止方法來釋放這部分內存(你可以調用C/C++方法釋放)。

簡單總結

  finalize方法不推薦使用,如果你想釋放內存/資源,推薦顯示的方法來釋放分配的內存。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM