Java進階8課

一.數組與內存控制java

java數組是靜態的
數組必須初始化以後才能使用,初始化就是分配內存空間。
 
1.數組必定要初始化嗎?
答:不是,能夠定義一個數組而後賦值給他。java程序中,全部的引用變量都不須要通過所謂的初始化操做,須要進行初始化的是引用變量所引用的對象。
 
2.在java中,聲明一個數組過程當中,是如何分配內存的。
答:定義一個數組則在內存堆裏開闢相對應的數組對象。而後進行賦值。最後指向所對應的數組變量。
數組變量都是放在棧內存裏,而變量引用的對象則存在堆內存中。 
 
3.java數組的初始化一共有哪幾種方式。
答:一共有三種。
靜態初始化:就是由程序員定義數組元素,由系統決定數組長度。 
例:String[] names={「老孫」,」老豬」,」老沙」};
例:String[] names = new String[]{「老孫」,」老豬」,」老沙」 };
動態初始化:就是由程序員定義數組長度,由系統分配初始值。 
例:String[] names =new String[3]; 
 
4.基本類型數組和引用類型數組之間在初始化時的內存分配機制有什麼區別嗎?
答:基本數據初始值爲0,引用類型初始值爲null。
數組擴展
1、數組特色:

1)java是靜態語言,所以java數組也是靜態的,當數組被初始化以後,數組長度是不可變的
2)java程序中數組必須通過初始化纔可以使用(即給數組對象的元素分配內存空間,並賦初始值)。
3)數組能夠存儲基本數據類型和引用數據類型。
4)數組相比集合最大的優勢就是隨機訪問速度很是快(通常狀況下建議仍是使用集合)。
5)多維數組能夠看作是一維數組對數組對象引用變量的存儲(多維數組效率較低)。程序員

2、數組的兩種初始化過程:正則表達式

1.靜態初始化:初始化時由程序員顯示的指定每一個數組元素的初始值,由系統決定數組長度。
2.動態初始化:初始化數組時由程序員指定數組長度,由系統爲數組元素分配初始值。算法

 String[] names = { "張三" , "李四" , "王五" , "趙六"   };
  //靜態初始化數組(通常形式)
  String[] names2 =  new String[]{   "劉二利" , "劉雙利" , "劉三利"     };
  //動態初始化數組
  String[] std_ids =  new String[ 5 ];

names、names一、std_ids這三個變量是對數組對象的引用,通常引用變量是在方法棧裏面聲明和初始化的,而這三個變量所引用的對象是在堆裏面進行內存分配的。 數組

3、java數組必須初始化才能使用的理解緩存

java數組必須初始化才能使用,一般指的是引用變量所指向的數組對象是否在堆內存中分配了一塊連續的內存空間,這快內存空間的長度就是數組長度,以下例子:安全

  int [] appIds = { 1 , 2 , 3 , 4 , 5};
   int [] copyAppIds ;
   copyAppIds = appIds;
   System.out.println(copyAppIds.length);

copyAppIds引用變量沒有被初始化,但輸出的copyAppIds的長度是5,也就是經過copyAppIds = appIds;讓兩個引用變量指向了同一個對象。多線程

四.數組棧和堆併發

基本類型變量的值存放在棧內存中,這句話是不正確的,實際上:全部局部變量都是存放在棧內存中保存的,無論其是基本類型的變量,仍是引用類型的變量,都是存儲在各自的方法棧區中,但引用類型變量所引用的對象則老是存放在堆內存中的,對於java而言,堆內存中的對象一般是不容許直接訪問的,爲了訪問堆內存中的對象,只能經過引用變量。
五.經常使用數組方法
 
1.聲明數組
String[] aArray = new String[5];  
String[] bArray = {"a","b","c", "d", "e"};  
String[] cArray = new String[]{"a","b","c","d","e"};
2. 輸出一個數組
String straArray = Arrays.toString(aArray);
3.從數組中建立列表
List<String> arrayList = new ArrayList<String>();
arrayList = Arrays.asList(aArray);
4.檢查數組中是否包含特定的值
boolean b = Arrays.asList(aArray).contains("a");
5.鏈接兩個數組
String[] newArray = ArrayUtils.addAll(aArray,bArray);
6.將數組元素加入一個獨立字符串中(經過自定義逗號分割形式)
String j = StringUtils.join(aArray,",");
7.將數組列表轉爲一個數組
  1. String[] stringArray = { "a", "b", "c", "d", "e" };  
  2. ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(stringArray));  
  3. String[] stringArr = new String[arrayList.size()];  
  4. arrayList.toArray(stringArr);  
  5. for (String s : stringArr)  
  6.     System.out.println(s); 
8.將數組轉爲一個集合
Set<String> set = new HashSet<String>(Arrays.asList(stringArray));
System.out.println(set);
9.反向數組
ArrayUtils.reverse(aArray);
10.刪除數組元素
int[] removed = ArrayUtils.removeElement(bArray, "a");
11.將整數轉爲byte數組
byte[] bytes = ByteBuffer.allocate(4).putInt(8).array();  
12.填充數組
int  demo[] =  new int [ 10 ];
Arrays.fill(demo,  3 );
13.複製數組
int [] demo2 =  new  int [ 11 ];
System.arraycopy(demo,  0 , demo2,  2 4 );
for ( int  i : demo2) {
     System.out.print(i);
}
14.數組排序
int [] a3 = { 3 2 5 4 1 };
System.out.println(Arrays.toString(a3));
Arrays.sort(a3);
System.out.println(Arrays.toString(a3));
15.一維數組數值檢索
int  index1 = Arrays.binarySearch(a3,  4 );
int  index2 = Arrays.binarySearch(a3, - 12 );
int  index3 = Arrays.binarySearch(a3,  15 );
System.out.println(index1 +  " " + index2 +  " " + index3);
 
二.對象與內存控制
1.能夠經過實例變量來修改靜態變量,以後靜態變量都被改變。
static int eyeNum;
Persion.esyNum = 22;
2.一個類被建立,先分配內存空間再賦值,分配內存的順序 靜態代碼-實例變量-構造器(功能是賦值)
(賦值順序,先靜態類變量再實例變量,再構造器變量賦值)
*new一個對象,首先在構造器引用以前內存已經分配了實例變量下來,構造器只是爲他們進行賦值。
 
4.子類調用父類時,父類構造器若是調用了子類重寫的方法,則是子類的方法,還沒賦值。(致使訪問不到子類變量值)
public class Derived extends Base{
    private int i = 22;
    public Derived(){
        i = 222;
    }
    public void display(){
        System.out.println("123");
        System.out.println(i);
    }
    public static void main(String[] args) {
        new Derived();
    }
 
}
 
class Base{
    private int i =2;
    public Base(){
        System.out.println(i);
        this.display();
    }
    public void display(){
        System.out.println("456");
        System.out.println(i);
    }
 
}
 
輸出:
2
123
0
 
5.繼承成員變量和繼承方法之間存在這樣的差別,訪問實例變量時,該實例變量的值取決於聲明該變量時類型,訪問對象方法時,該方法行爲取決於它所實際引用的對象的類型。
 
向上轉型的例子
  1. public class Demo16 {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         Base_16 b = new Base_16();  // 1  
  6.         System.out.println(b.count);  //2
  7.         b.display();  //2
  8.           
  9.         Drived_16 d = new Drived_16();  // 2  
  10.         System.out.println(d.count);  //20
  11.         d.display();  //20
  12.           
  13.         Base_16 bd = new Drived_16();  // 3  
  14.         System.out.println(bd.count);  //2
  15.         bd.display();  //20 //
  16.           
  17.         Base_16 b2d = d;  // 4  
  18.         System.out.println(b2d.count);  //2
  19.         b2d.display();  //20
  20.     }  
  21.   
  22. }  
  23. class Base_16{  
  24.     int count = 2;  
  25.     public void display(){  
  26.         System.out.println(this.count);  
  27.     }  
  28. }  
  29. class Drived_16 extends Base_16{  
  30.     int count = 20;  
  31.     @Override  
  32.     public void display(){  
  33.         System.out.println(this.count);  
  34.     }  
 
6.能夠用過super.變量或者方法來調用父類的變量或者方法。
 
7.須要獲取父類的屬性時,也能夠經過上轉型((Parent)d).tag獲取父類的屬性。
d.tag //獲取子類屬性
((Parent)d).tag//獲取父類屬性
 
8.宏變量是:編譯器會把程序全部用到該變量的地方直接替換成該變量的值,稱爲宏替換。
final
9.定義該變量時指定初始值纔有「宏替換」效果。
static final int num = 3; //常量定義
 
10.若是程序須要再匿名內部類中使用局部變量,那麼該變量必須使用final修飾符。
 
三.集合
 

1.set 元素無序,集合元素不可重複。 app

2.Map集合是Set集合的擴展,可是Set底層是Map集合,Map的key值就是Set集合 
3.map存儲是根據key的hash值和key的值來判斷。eq:(e.hash==hash && ((k=e.key) ==key)||key.equals(k)) 
4.HashMap如何取值原理:首先經過hashCode返回值肯定存儲位置則放入同一個bucket中,若是這兩個Entry的key經過equals爲true則,value覆蓋,key不覆蓋。不然將與原有的Entry造成Entry鏈,新增的位於鏈頭。 
5.threshold是該變量包含了HashMap能容納的極限,他的值是size*loadFactor(負載因子)默認0.75 
6.負載因子(load factor) 默認值爲0.75,增大負載因子能夠減小Hash表(Entry數組)所佔用的內存空間,但會增長查詢數據的時間開銷,而查詢是最頻繁的操做(get,put都要用到查詢); 減小負載因子會提升數據查詢的性能,但會增長Hash表所佔用的內存空間 

7.HashMap的實際容量是Capacity,是找出大於initialCapacity的,最小的2的n次方值,例如給初始值initialCapacity爲10,那麼HashMap的實際容量爲16;7.HashMap的實際容量是Capacity,是找出大於initialCapacity的,最小的2的n次方值,例如給初始值initialCapacity爲10,那麼HashMap的實際容量爲16

8.TreeSet和TreeMap是採用一課」黑樹「來保存,性能比Hash集合底,可是Entry老是按Key根據指定排序規
則保存有序狀態。
9.TreeSet和TreeMap是根據排序二叉樹的算法排序.
10.TreeMap和HashMap能夠返回values的一個conllection集合。不是list集合
11.Vector還有一個Stack子類,這個Stack子類僅再Vector父類基礎上增長了5個方法,這五個方法就將一個Vector拓展成Stack。
12.JDK1.6開始java不推薦使用Stack,推薦使用Deque接口,ArrayDeque類來實現它,代替Stack類。Deque接口表明雙端隊列。具備隊列的性質先進先出,也具備棧的性質,也就是雙端隊列便是隊列,也是棧
13.從序列化機制角度來看,ArrayList的實現比Vector的實現更安全,ArrayList能夠經過synchronizedList來將一個普通的ArrayList包裝成線程安全的ArrayList。
14.ArrayList再添加元素時,必須對底層數組元素進行「總體搬家」。若是添加元素致使集合長度將要超過底層數組長度,ArrayList必須建立一個長度爲原來1.5倍的數組,垃圾回收機制將回收原有數組。
15.LinkedList主要開銷在entry(int index)方法上,該方法必須一個個地搜索過去,知道找到index處的元素,而後再在該元素以前插入新的元素,即便這樣,執行add依舊比ArrayList的性能高。
16.程序把LinkedList當成雙端隊列,棧只用,調用addFirst(E e),addLast(E e),getFirst(E e),getLast(E e),offer(E e),offerFirst(),offerLast()等方法來操做該集合,
17,ArrayList在查詢元素比較快(添加和刪除元素須要總體搬家),LinkedList添加,刪除元素比較快。查詢比較慢(須要檢索整個集合)。
 
四.java的內存回收
1.對象在內存中的狀態,
可達狀態:有一個以上的引用變量引用它。
可恢復狀態:程序中某個對象再也不有任何引用變量引用它,他將進入可恢復狀態,系統垃圾回收機制準備回收該對象所佔用的內存,在回收該對象以前,系統回調用前,系統會調用可恢復狀態的對象的finalize方法進行資源清理,若是在調用finalize方法從新讓一個以上的引用變量引用該對象,則該變量再次變成可達狀態,不然該對象進入不可達狀態。
不可達狀態:當一個對象處於不可達狀態時,系統纔會真正回收該對象所佔有的資源。
2 .強引用:程序建立一個對象,並把這個對象賦給一個引用變量,這個引用變量就是強引用。JVM確定不會回收強引用所引用的對象,因此強引用時形成java內存泄漏的主要緣由之一。
3 .軟引用:軟引用須要經過SoftReference類來實現,當內存空間不足時,系統將會回收它。當內存充足時,它的做用和強引用同樣,當內存不足時,強引用會報錯,而軟引用則會自動回收對象不會報錯。這就是優點。
4 .弱引用:經過WeakReference類實現,但弱引用比軟引用級別更低。當系統垃圾回收機制運行時,無論系統內存是否足夠,總會回收該對象所佔用的內存。必須等到系統垃圾回收機制運行時纔會被回收。WeakHashMap是一個道理。
5. 虛引用:須要經過引用隊列ReferenceQueue類結合,經過PhantomReference類來實現。虛引用的主要做用就是跟蹤對象被垃圾回收的狀態,程序能夠經過檢查與虛引用關聯的引用隊列中是否已經包含指定的虛引用,從而瞭解虛引用所引用對象是否即將被回收。
 
虛引用的做用是,咱們能夠聲明虛引用來引用咱們感興趣的對象,在gc要回收的時候,gc收集器會把這個對象添加到referenceQueue,這樣咱們若是檢測到referenceQueue中有咱們感興趣的對象的時候,說明gc將要回收這個對象了,此時咱們能夠在gc回收以前作一些其餘事情,好比記錄日誌什麼的。
 
6.java內存泄漏:存在無用的內存沒有被回收回來,那就是內存泄漏。
ArrayList<String> arrayList = new ArrayList<String>();
arrayList[size -1]; 若是不將arrayList[size] = null;加上就會產生內存泄漏。
 
7.垃圾回收機制
垃圾回收器的設計算法:
串行回收和並行回收:串行回收就是無論系統有多少個CPU,始終只回收。bing'xing用一個CPU來執行垃圾回收操做;而並行回收就是把整個回收工做拆分紅多部分,妹各部分由一個CPU負責,從而讓多個CPU並行回收。並行稅後的執行效率很高,可是複雜度增長,另外也有一些反作用,好比內存碎片會增長。
併發執行和應用程序中止:Stop-the-world的垃圾回收方式在執行垃圾回收的同時會致使應用程序的暫停。併發執行的垃圾回收雖然不會致使應用程序的暫停,但因爲併發執行垃圾回收須要解決和應用程序的執行衝突,所以併發執行垃圾回收的系統開銷比Stop-the-world更高,並且執行時也須要更多的堆內存。
 
壓縮和不壓縮和複製:爲了減小內存碎片,支持壓縮的垃圾回收器會把全部的活對象變遷在一塊兒,而後將以前佔用的內存所有回收。 不壓縮式的垃圾回收器只是回收內存,這樣回收回來的內存不多是連續的,所以將會有較多的內存碎片。較之壓縮式的垃圾回收,不壓縮式的垃圾回收回收內存快,而分配內存時就會更慢,並且沒法解決內存碎片的問題。 複製式的垃圾回收會將全部可達對象複製到另外一塊相同的內存重,這種方式的優勢是垃圾回收過程不會產生內存碎片,但缺點很明顯,須要複製數據和額外的內存。
詳述以下:
複製:將堆內分紅兩個相同空間,從根開始訪問每個關聯的可達對象,將空間A的可達對象所有複製到空間B,而後一次性回收整個空間A.(遍歷空間的成本較小,但須要巨大的複製成本和較多的內存)
標記清除:就是不壓縮回收方式。垃圾回收器先從根開始訪問全部可達對象,將他們標記爲可達標記,而後再遍歷一次整個內存區域,把全部沒有標記爲可達的對象進行回收處理。(須要內存較少,但形成應用程序暫停的時間多和產生的內存碎片較多)
標記壓縮:這就是壓縮方式,垃圾回收器先從根開始訪問全部對象,將他們標記爲可達標記,再將活動對象搬遷在一塊兒,這個過程也被稱爲內存壓縮,而後垃圾回收機制再次回收不可達對象佔用的內存空間。
 
堆內存的分代回收:
1. young代:採用複製算法只需遍歷那些處於可達狀態的對象,並且這些對象的數量較少,可複製成本也不大,所以能夠充分發揮複製算法的優勢。由1個Eden和2個Survivor區構成(fromSurvivor,toSurvivor),每次複製就是將Eden和FromSurvivor的可達對象複製到ToSurvivor區,而後清空Eden和FromSurvivor,將ToSurvivor變成FromSurvivor。
2. Old代:採用標記壓縮算法,1.Old代垃圾回收的執行頻率無需過高,由於不多由對象會死掉;2.每次對Old代執行垃圾回收須要更長的時間來完成。這個算法有3個階段:mark(標識可達對象),sweep(清除),compact(壓縮).在mark階段,回收器會識別出哪些對象仍然時可達的,在sweep階段將會回收不可達對象所佔用的內存。在compact階段回收器執行sliding compaction,把活動對象往Old代的前段啓動,而在尾部保留一塊連續的空間,以便下次爲新對象分配內存空間。
3. Permanent代:不會被回收。
Young代的內存將要用完時,就會對Young代進行垃圾回收,會採用較高的頻率進行掃描和回收,由於這種回收的系統開銷小,所以被稱爲次要回收。
當Old代內存將要用完時,將會進行全回收,被稱爲主要回收。
 
與垃圾回收配置的參數:
-Xmx:設置Java虛擬機堆內存的最大容量,如java -Xmx256m XxxClass.
-Xms:設置Java虛擬機堆內存的初始容量,如java -Xms128m XxxClass.
垃圾回收附加選項:
-XX:MinHeapFreeRatio = 40:設置Java堆內存最小的空閒百分比,默認值爲40,如java -XX:MinHeapFreeRatio = 40 XxxClass.
-XX:MaxHeapFreeRatio = 70:設置Java堆內存最大的空閒百分比,默認值爲70,如java -XX:MaxHeapFreeRatio = 70 XxxClass.
-XXNewRatio = 2:設置Young/Old內存的比例,如java-Xx:NewRatio = 1 XxxClass.
-XXNewSize = size:設置Young代內存的默認容量,如java -XX:NewSize = 64m XxxClass.
-XX:SurvivorRatio = 8:設置Young代中edin/survivor的比例,如java -XX:SurvivorRatio = 8 XxxClass.
-XX:MaxNewSize = size:設置Young代內存的最大容量,如java -XX:MaxNewSize = 128m XxxClass.
-XX:PermSize = size:設置Permanent代內存的默認容量,如 java - XX:PermSize = 128m XxxClass.
-XX:MaxPermSize = size:設置Permanent代內存的默認Max最大容量,如 java - XX:MaxPermSize = 128m XxxClass.
 
常見垃圾回收器
1.串行回收器:經過運行java程序時使用-XX:+UseSerialGC附加選項啓用。

使用一個線程進行串行回收,新生代採用複製算法,老年代使用標記-壓縮算法,回收的時候程序所有暫停

串行回收器主要有兩個特色:第一,它僅僅使用單線程進行垃圾回收;第二,它獨佔式的垃圾回收

缺點是:停頓時間長

有點是:久經考驗,bug少

串行回收器的工做原理:使用堆內存的分代回收

 

 
2.並行回收器:經過運行java程序使用-XX:+UseParallelGC附加選項啓用,能夠充分利用計算機的多個CPU來提升垃圾回收吞吐量。
能夠用-XX:ParallelGCThreads = size來減小並行程序的數目。
特色:多個CPU並行的機器才能發揮其優點。
新生代ParNew回收器:
(1)特色:
            - 將串行回收器多線程化
            - 使用複製算法
            - 垃圾回收時,應用程序仍會暫停,只不過因爲是多線程回收,在多核CPU上,回收效率會高於串行回收器,反之在單核CPU,效率會不如串行回收器
(2)設置參數:
            -XX:+UseParNewGC新生代使用ParNew回收器,老年代使用串行回收器
            -XX:+UseConcMarkSweepGC新生代使用ParNew回收器,老年使用CMS回收器
            -XX:ParallelGCThreads = n指回ParNew回收器工做時的線程數量,cpu核數小時8時,其值等於cpu數量,高於8時,可使用公式(3+((5*CPU_count)/8))
 
新生代ParallelGC回收器:

(1)特色: 
  –同ParNew回收器同樣, 不一樣的地方在於,它很是關注系統的吞吐量(經過參數控制) 
  –使用複製算法 
  –支持自適應的GC調節策略

(3)設置參數:

-XX:+UseParallelGC  新生代用ParallelGC回收器, 老年代使用串行回收器 
-XX:+UseParallelOldGC  新生代用ParallelGC回收器, 老年代使用ParallelOldGC回收器系統吞吐量的控制: 
-XX:MaxGCPauseMillis=n(單位ms)   設置垃圾回收的最大停頓時間, 
-XX:GCTimeRatio=n(n在0-100之間)  設置吞吐量的大小, 假設值爲n, 那系統將花費不超過1/(n+1)的時間用於垃圾回收 
-XX:+UseAdaptiveSizePolicy  打開自適應GC策略, 在這種模式下, 新生代的大小, eden,survivior的比例, 晉升老年代的對象年齡等參數會被自動調整,以達到堆大小, 吞吐量, 停頓時間之間的平衡點

老年代ParallelOldGC回收器

(1)特色: 
  –同新生代的ParallelGC回收器同樣, 是屬於老年代的關注吞吐量的多線程併發回收器 
  –使用標記壓縮算法, 
(2)設置參數: 
-XX:+UseParallelOldGC  新生代用ParallelGC回收器, 老年代使用ParallelOldGC回收器, 是很是關注系統吞吐量的回收器組合, 適合用於對吞吐量要求較高的系統 
-XX:ParallelGCThreads=n   指回ParNew回收器工做時的線程數量, cpu核數小時8時, 其值等於cpu數量, 高於8時, 可使用公式(3+((5*CPU_count)/8))
 
併發標識-清理(Mark-Sweep)回收器(CMS)
 

(1)特色: 
  –是併發回收, 非獨佔式的回收器, 大部分時候應用程序不會中止運行 
  –針對年老代的回收器, 
  –使用併發標記清除算法, 所以回收後會有內存碎片, 可使參數設置進行內存碎片的壓縮整理 
  –與ParallelGC和ParallelOldGC不一樣, CMS主要關注系統停頓時間 
(2)CMS主要步驟: 
  1. 初始標記 
  2. 併發標記 
  3. 預清理 
  4. 從新標記 
  5. 併發清理 
  6. 併發重置

–>注:初始標記與理新標記是獨佔系統資源的,不能與用戶線程一塊兒執行,而其它階段則能夠與用戶線程一塊兒執行 
(3)設置參數: 
-XX:-CMSPrecleaningEnabled  關閉預清理, 不進行預清理, 默認在併發標記後, 會有一個預清理的操做,可減小停頓時間 
-XX:+UseConcMarkSweepGC  老年代使用CMS回收器, 新生代使用ParNew回收器 
-XX:ConcGCThreads=n  設置併發線程數量, 
-XX:ParallelCMSThreads=n  同上, 設置併發線程數量, 
-XX:CMSInitiatingOccupancyFraction=n  指定老年代回收閥值, 即當老年代內存使用率達到這個值時, 會執行一次CMS回收,默認值爲68, 設置技巧: (Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100)>=Xmn 
-XX:+UseCMSCompactAtFullCollection  開啓內存碎片的整理, 即當CMS垃圾回收完成後, 進行一次內存碎片整理, 要注意內存碎片的整理並非併發進行的, 所以可能會引發程序停頓 
-XX:CMSFullGCsBeforeCompation=n  用於指定進行多少次CMS回收後, 再進行一次內存壓縮 
-XX:+CMSParallelRemarkEnabled  在使用UseParNewGC 的狀況下, 儘可能減小 mark 的時間 
-XX:+UseCMSInitiatingOccupancyOnly  表示只有達到閥值時才進行CMS回收

當一個URL被訪問時,內存申請過程以下
A. JVM會試圖爲相關Java對象在Eden中初始化一塊內存區域
B. 當Eden空間足夠時,內存申請結束。不然到下一步
C. JVM試圖釋放在Eden中全部不活躍的對象(這屬於1或更高級的垃圾回收), 釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區
D. Survivor區被用來做爲Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,不然會被保留在Survivor區
E. 當OLD區空間不夠時,JVM會在OLD區進行徹底的垃圾收集(0級)
F. 徹底垃圾收集後,若Survivor及OLD區仍然沒法存放從Eden複製過來的部分對象,致使JVM沒法在Eden區爲新對象建立內存區域,則出現」out of memory錯誤」
 
內存管理的小技巧
1.儘可能使用直接量,String str = "hello" 而不是String str = new String("hello");
2.使用StringBuilder和StringBuffer進行字符串鏈接
3.儘可能早釋放無用的對象的引用
4.儘可能少用靜態變量
5.避免在常常調用的方法,循環中建立java對象
6.緩存常用的對象,使用HashMap緩存。
7.儘可能不要用finalize方法,會致使過多頻繁的進行資源清理。
8.考慮使用SoftReference軟引用。獲取對象可能爲空,若是爲空則從新獲取對象。
 
五.表達式中的陷阱
1. JVM對字符串的處理
Java程序中建立對象的常見方式有以下4中
經過new調用構造器建立java對象
經過Class對象的newInstance()方法條用構造器建立java對象
經過java反序列化機制從IO流中恢復Java對象
經過java對象提供的clone()方法複製一個新的java對象
 
Java程序中的字符直接量,JVM會使用一個字符串池來保存它們,當第一次使用某個字符串直接量時,JVM會將它放入字符串池進行緩存。
例: String str1 = "Hello Java";
        String str2 = "Hello Java";
        str1 == str2;//true
例: String str1 = "Hello Java 的長度:10";
        String str2 = "Hello" + "Java" + "的長度:" + 10;
        str1 == str2;//true
例:    String str1 = "Hello Java的長度:10"
        String str2 = "Hello"+"Java" + "的長度:"+"Hello java".length();
        int len = 10;
        String str3 = "Hello"+"Java"+"的長度:"+len;
        str1 == str2;//false 由於包含了方法調用,不能在編譯時肯定
        str1 == str3;//false 包含了變量,不能在編譯時肯定
例:    String str1 = "Hello Java 的長度:10";
        final String s1 = "Hello";
        String str2 = s1+"Java" + "的長度:10";
        final int len = 10;
        String str3 = "Hello"+"Java"+"的長度:"+len;
        str1 == str2;//true 若是變量是宏變量則能夠,由於宏替換
        str1 == str3;//true 若是變量是宏變量則能夠,由於宏替換
String str = "Hello" + "Java," + 「crazyit.org";//建立了一個對象
 
2. 不可變的字符串
例:    String str = "Hello";
        System.identityHashCode(str);
        str = str + "Java";//建立另外一個對象指向它,字符串不變,引用改變
        System.identityHashCode(str);
        str = str + ",crazyit.org";//建立另外一個對象指向它
        System.identityHashCode(str);
        //三個HashCode都不相等,因此它們不是引用同一個對象。
        // 第一個和第二個對象將會一直在字符串池中,造成Java內存泄漏的緣由之一
         StringBuilder str = new StringBuilder("Hello");
        System.identityHashCode(str);
        str.append("Java");
        System.identityHashCode(str);
        str.append(,crazyit.org);
        System.identityHashCode(str);
        //三個HashCode都相等,它們是同一個StringBuilder對象。
       
3. 字符串比較
equals來比較是否相等
compareTo來比較兩個字符串的大小
例: "abc" .compareTo("ax");
abc
ax
程序先將它們左對齊,而後從左向右比較每一個字符。因此ax > abc
 
表達式類型的陷阱
4. 表達式類型的自動提高
當一個算數表達式中包含多個基本類型的值時,整個算數表達式的數據類型將發生自動提高。
全部byte型,short型和char型將被提高到int型
整個算術表達式的數據類型自動提高到與表達式中最高等級操做數一樣的類型。操做數的等級排列以下圖所示,位於箭頭右邊類型的等級高於位於箭頭左邊類型的等級
例: short sValue = 5;
        sValue = sValue -2;//會將sValue轉爲int類型,因此會發生錯誤
例: byte b = 40;
        char c = 'a';
        int i = 23;
        double d = 3.314;
        double result = b+c+i*d;//正確,最後轉爲double類型
例: int val = 3;
        int intResult =23/val;//正確輸出7,不能除盡。取整數位。
例: "Hello!"+'a'+7; //Hello!a7 會轉成字符串計算
        'a'+7+"Hello!";//104Hello! char會被轉成int計算,變成a對應的ASCII值:97。
 
5. 複合賦值運算符的陷阱
複合運算符:+=,-=,*=,/=,%=,<<=,>>=,>>>=,&=,^=,|=等。
例:short sValue = 5;
         sValue -=2; //這就是複合運算符,也不會存在問題。
        等價於 short sValue = (short ) sValue -2;
例: short st = 5;
        st+=90000;//出現 高位截斷,由於short只能-32768~32767之間。
例: Object he = new CompositeAssign2();
        String crazy = "crazyit.org ,";
        crazy +=he;//會轉爲字符串運算,因此正確
        he +=crazy;//由於he不是字符串類型,因此報錯
        換爲he = he + crazy;//系統會將he轉爲String類型後與crazy進行鏈接運算
6. 輸入法致使的陷阱
非法字符\12288 是全角空格。編譯出錯!
非法字符\xxxxx的錯誤提示,都是有全角字符的錯。
7. 註釋的字符必須合法
<br/>源碼位置:G:\codes\unit5\5.4\Hello.java
不符合Java對Unicode轉義字符的要求
8.轉義字符的陷阱
慎用字符Unicode轉義形式
例: "abc\u000a".length();//將會編譯不過,變成換行。
泛型可能引發的錯誤
9.原始類型變量的賦值
當程序試圖訪問帶泛型聲明的集合的集合元素時,編譯器老是把集合元素當初泛型類型處理。致使若是類型不對會報錯。
10.原始類型帶來擦除
例: Apple<Integer> a = new Apple<Integer>();
        Integer as = a.getSize();
         Apple b = a;
        Number size1 = b.getSize();
        Integer size2 = b.getSize();
11.建立泛型數組的陷阱
例: JDK雖然支持泛型,但不容許建立泛型數組。
List<String>[] lsa = new List<String>[10];// 不被容許
例: public class GenericArray<T>{
            class aA {T foo;}
            public GenericArray(){
                A[] as = new A[10];// 建立內部類A的數組
            }
        }
   
正則表達式的陷阱
正則表達式中的點號(.)可匹配任意字符.
String[] strArr = str.split(".");//由於點號(.)可匹配任意字符
String[] strArr = str.split("\\.");//須要將點號分割成多個字符
多線程的陷阱
不要調用run方法,應該使用start方法啓動線程。
//Todo
電子郵箱:/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/
()表示一個組
[ ]表示能夠出現其中的任意一個字符
?表示前面的條目能夠出現0次或者1次
?後面使用\w+表示.號後面必須接字符
()後面的*號表示前面的條目能夠出現0次或1次
@後面使用\w+表示後面必須接字符
 
 
六.流程控制的陷阱
 
switch語句陷阱
例: char score = 'c';
switch (score){
    case 'a':{system.out.println(成績優秀); break;}
    case 'b':{system.out.println(成績良好); break;}
    case 'c':{system.out.println(成績中等); break;}
    case 'd':{system.out.println(成績差勁); break;}
    default : system.out.println("輸入成績錯誤");
}
1.default分支的執行條件是:表達式的值與前面分支的值都不相等。
2.break的重要性:case 後面不跟break語句,則會出現繼續往下執行的狀況。
3.switch表達式的類型:byte,short,int,char,enum.
if語句的陷阱
1.else隱含的條件:前面條件都不符合
For循環語句:for循環裏有且只能有兩個分號做爲分隔符。第一個分號以前的是初始化條件,兩個分號中間的部分是一個返回boolean的邏輯表達式,當它返回true時for循環纔會執行下一次循環;第二個分號以後的是循環迭代部分,每次循環結束後會執行循環迭代部分。
2.循環體的值是有最大值的。若是運用很差會陷入死循環。
 
七.面向對象的陷阱
instanceof運算符的陷阱
定義:它用於判斷前面的對象是不是後面的類或其子類,實現類的實例。
例: Object hello = "Hello";
        (hello instanceof Object);//返回true
例: Object str = "Hello";
        Math math = (Math)str;
        (math instanceof String);//不可轉換的類型
在編譯階段,強制轉型要求被轉型變了的編譯時類型必須是以下3種狀況之一:
    被轉型變量的編譯時類型與目標類型相同;
    被轉型變量的編譯時類型是目標類型的父類;
    被轉型變量的編譯時類型是目標類型的子類,這種狀況下能夠自動向上轉型,無須強制轉換。
例: Object obj = new Integer(5);
        String str = (String)obj;
 
在運行階段,被轉型變量所引用對象的實際類型必須是目標類型的實例,或者是目標類型的子類,實現類的實例,不然在運行時將引起ClassCastException異常。
例: String s = null;
        (s instanceof String);//返回false
instanceof運算符除了能夠保證某個引用變量是特定類型的實例外,還能夠保證該變量沒有引用一個null。這樣就能夠將該引用變量轉型爲該類型,並調用該類型的方法,而不用擔憂會引起ClassCastException或NullPointerException。
構造器的陷阱
在單例中提供readResolve方法則能夠避免反序列化Java對象時不是同一個對象。
1.使用反序列化得方式恢復Java對象
讓該Java類實現Cloneable接口;爲該Java類提供clone()方法,該方法負責進行負責;
2.使用clone方法複製Java對象
無限遞歸的構造器
1.在定義實例變量時指定實例變量的值是當前類的實例;
2.初始化塊中建立當前類的實例;
3.構造器內調用本構造器建立Java對象;
持有當前類的實例
例: public InstanceTest(){}
        public InstanceTest(String name){
            instance = new InstanceTest();
            instance.name = name;
        }
這樣能夠避免無限遞歸構造器,但避免不了無限遞歸toString();
到底調用哪一個重載的方法
例: 方法:public void info(String name,double count){}
        調用:info("crazyit.org",5);
        輸出:crazyit.org   5.0
例: 方法:public void info(String name,double count){}
                    public void info(String name,int count){}
        調用:info("crazyit.org",5);        輸出:crazyit.org   5
例: 方法:public void info(Object name,double count){}
                    public void info(Object[] name,double count){}
        調用:info(null,5);
        輸出:null   5
例: 方法:public void info(Object name,double count){}
                    public void info(Object[] name,int count){}
        調用:info(null,5);
       輸出:報錯
方法重寫的陷阱
重寫private方法:不是重寫父類的方法,而是從新定義了一個方法。
非靜態內部類的陷阱
系統在編譯階段總會爲非靜態內部類的構造器增長一個參數,非靜態內部類的構造器的第一個形參老是外部類。所以調用非靜態內部類的構造器時必須傳入一個外部類對象做爲參數,不然程序將會引起運行時異常。
非靜態內部類不能擁有靜態成員
非靜態內部類的子類:應提供一個無參構造器外部類.this.super();
Static關鍵字:屬於類
靜態內部類的限制:當程序使用靜態內部類時,外部類至關於靜態內部類的一個包,所以使用起來比較方便;但不能訪問外部類的非靜態成員。
native方法的陷阱:native方法不能跨平臺,Thread.sleep(2);是native方法,所以不許確。
 
異常捕捉陷阱
1.正確關閉資源的方式
例: if(os != null){
            try{
                os.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
2.finally塊的陷阱
System.exit(0);被調用時,虛擬機退出前要執行兩項清理工做:
執行系統中註冊的全部關閉鉤子,
例: Runtime.getRuntime().addShutdownHook{//爲系統註冊一個關閉鉤子
            //執行關閉操做
        }
若是程序調用了Syatem.runFinalizerOnExit(true);那麼JVM會對全部還未結束的對象調用Finalizer。
finally塊的方法返回值
當程序執行try,catch塊時遇到throw語句,會致使該方法當即結束,但不會馬上拋異常,先執行finally塊,若是finally塊使用return來結束方法,系統將不會跳回去執行try塊,catch塊去拋出異常。
 
3.catch塊的用法
進行異常捕獲時,必定要記住先捕獲小的異常,再捕獲大的異常。
不要用catch代替流程控制
程序使用catch捕捉異常時,其實並不能爲所欲爲地捕捉全部異常。程序能夠在任意想捕捉的地方捕捉RuntimeException異常,Exception,但對於其餘Checked異常,只有當try塊可能拋出該異常時,catch塊才能捕捉該Checked異常。
不管如何不要在finally塊或catch塊中遞歸調用可能引發異常的方法。
相關文章
相關標籤/搜索