Android 面試之 Java 篇二

本文出自 Eddy Wiki ,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-java.html

本文收集整理了 Android 面試中會(huì)遇到與 Java 知識(shí)相關(guān)的簡(jiǎn)述題。

容器

Java 集合框架

img
img

參考:

Java集合框架

列舉 Java 的集合和它們的繼承關(guān)系

Collection包結(jié)構(gòu),與Collections的區(qū)別。

Collection是一個(gè)接口,它是Set、List等容器的父接口;

Collections是一個(gè)工具類(lèi),提供了一系列的靜態(tài)方法來(lái)輔助容器操作,這些方法包括對(duì)容器的搜索、排序、線程安全化等等。

List, Set, Map是否繼承自Collection接口?

List和Set是,Map不是。

Set和List的區(qū)別

  1. Set接口存儲(chǔ)的是無(wú)序的、不重復(fù)的數(shù)據(jù)。
  2. List接口存儲(chǔ)的是有序的、可以重復(fù)的數(shù)據(jù)。
  3. Set檢索效率低下,刪除和插入效率高,插入和刪除不會(huì)引起元素位置改變 ,實(shí)現(xiàn)類(lèi)有HashSet、TreeSet。
  4. List查找元素效率高,插入刪除效率低,因?yàn)闀?huì)引起其他元素位置改變,實(shí)現(xiàn)類(lèi)有ArrayList、LinkedList、Vector。List和數(shù)組類(lèi)似,可以動(dòng)態(tài)增長(zhǎng),根據(jù)實(shí)際存儲(chǔ)的數(shù)據(jù)的長(zhǎng)度自動(dòng)增長(zhǎng)List的長(zhǎng)度。

hashCode方法的作用

對(duì)于包含容器類(lèi)型的程序設(shè)計(jì)語(yǔ)言來(lái)說(shuō),基本上都會(huì)涉及到hashCode。在Java中也一樣,hashCode方法的主要作用是為了配合基于散列的集合一起正常運(yùn)行,這樣的散列集合包括HashSet、HashMap以及HashTable。

為什么這么說(shuō)呢?考慮一種情況,當(dāng)向集合中插入對(duì)象時(shí),如何判別在集合中是否已經(jīng)存在該對(duì)象了?(注意:集合中不允許重復(fù)的元素存在)

也許大多數(shù)人都會(huì)想到調(diào)用equals方法來(lái)逐個(gè)進(jìn)行比較,這個(gè)方法確實(shí)可行。但是如果集合中已經(jīng)存在一萬(wàn)條數(shù)據(jù)或者更多的數(shù)據(jù),如果采用equals方法去逐一比較,效率必然是一個(gè)問(wèn)題。此時(shí)hashCode方法的作用就體現(xiàn)出來(lái)了,當(dāng)集合要添加新的對(duì)象時(shí),先調(diào)用這個(gè)對(duì)象的hashCode方法,得到對(duì)應(yīng)的hashcode值,實(shí)際上在HashMap的具體實(shí)現(xiàn)中會(huì)用一個(gè)table保存已經(jīng)存進(jìn)去的對(duì)象的hashcode值,如果table中沒(méi)有該hashcode值,它就可以直接存進(jìn)去,不用再進(jìn)行任何比較了;如果存在該hashcode值, 就調(diào)用它的equals方法與新元素進(jìn)行比較,相同的話就不存了,不相同就散列其它的地址,所以這里存在一個(gè)沖突解決的問(wèn)題,這樣一來(lái)實(shí)際調(diào)用equals方法的次數(shù)就大大降低了,說(shuō)通俗一點(diǎn):Java中的hashCode方法就是根據(jù)一定的規(guī)則將與對(duì)象相關(guān)的信息(比如對(duì)象的存儲(chǔ)地址,對(duì)象的字段等)映射成一個(gè)數(shù)值,這個(gè)數(shù)值稱(chēng)作為散列值。下面這段代碼是java.util.HashMap的中put方法的具體實(shí)現(xiàn):

public V put(K key, V value) {
  if (key == null)
    return putForNullKey(value);
  int hash = hash(key.hashCode());
  int i = indexFor(hash, table.length);
  for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    Object k;
    if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
      V oldValue = e.value;
      e.value = value;
      e.recordAccess(this);
      return oldValue;
    }
  }

  modCount++;
  addEntry(hash, key, value, i);
  return null;
}

put方法是用來(lái)向HashMap中添加新的元素,從put方法的具體實(shí)現(xiàn)可知,會(huì)先調(diào)用hashCode方法得到該元素的hashCode值,然后查看table中是否存在該hashCode值,如果存在則調(diào)用equals方法重新確定是否存在該元素,如果存在,則更新value值,否則將新的元素添加到HashMap中。從這里可以看出,hashCode方法的存在是為了減少equals方法的調(diào)用次數(shù),從而提高程序效率。

hashCode返回的就是對(duì)象的存儲(chǔ)地址,事實(shí)上這種看法是不全面的,確實(shí)有些JVM在實(shí)現(xiàn)時(shí)是直接返回對(duì)象的存儲(chǔ)地址,但是大多時(shí)候并不是這樣,只能說(shuō)可能存儲(chǔ)地址有一定關(guān)聯(lián)。

因此有人會(huì)說(shuō),可以直接根據(jù)hashcode值判斷兩個(gè)對(duì)象是否相等嗎?肯定是不可以的,因?yàn)椴煌膶?duì)象可能會(huì)生成相同的hashcode值。雖然不能根據(jù)hashcode值判斷兩個(gè)對(duì)象是否相等,但是可以直接根據(jù)hashcode值判斷兩個(gè)對(duì)象不等,如果兩個(gè)對(duì)象的hashcode值不等,則必定是兩個(gè)不同的對(duì)象。如果要判斷兩個(gè)對(duì)象是否真正相等,必須通過(guò)equals方法。

  • 如果equals方法得到的結(jié)果為true,則兩個(gè)對(duì)象的hashcode值必定相等;
  • 如果equals方法得到的結(jié)果為false,則兩個(gè)對(duì)象的hashcode值不一定不同;
  • 如果兩個(gè)對(duì)象的hashcode值不等,則equals方法得到的結(jié)果必定為false;
  • 如果兩個(gè)對(duì)象的hashcode值相等,則equals方法得到的結(jié)果未知。

當(dāng)x.equals(y)等于true時(shí),x.hashCode()與y.hashCode()可以不相等,這句話對(duì)不對(duì)?

如果equals方法得到的結(jié)果為true,則兩個(gè)對(duì)象的hashcode值必定相等;

Java 中 == 和 equals 和 hashCode 的區(qū)別

  1. '=='是用來(lái)比較兩個(gè)變量(基本類(lèi)型和對(duì)象類(lèi)型)的值是否相等的, 如果兩個(gè)變量是基本類(lèi)型的,那很容易,直接比較值就可以了。如果兩個(gè)變量是對(duì)象類(lèi)型的,那么它還是比較值,只是它比較的是這兩個(gè)對(duì)象在棧中的引用(即地址)。 對(duì)象是放在堆中的,棧中存放的是對(duì)象的引用(地址)。由此可見(jiàn)'=='是對(duì)棧中的值進(jìn)行比較的。如果要比較堆中對(duì)象的內(nèi)容是否相同,那么就要重寫(xiě)equals方法了。
  2. Object類(lèi)中的equals方法就是用'=='來(lái)比較的,所以如果沒(méi)有重寫(xiě)equals方法,equals和==是等價(jià)的。 通常我們會(huì)重寫(xiě)equals方法,讓equals比較兩個(gè)對(duì)象的內(nèi)容,而不是比較對(duì)象的引用(地址)因?yàn)橥覀冇X(jué)得比較對(duì)象的內(nèi)容是否相同比比較對(duì)象的引用(地址)更有意義。
  3. Object類(lèi)中的hashCode是返回對(duì)象在內(nèi)存中地址轉(zhuǎn)換成的一個(gè)int值(可以就當(dāng)做地址看)。所以如果沒(méi)有重寫(xiě)hashCode方法,任何對(duì)象的hashCode都是不相等的。通常在集合類(lèi)的時(shí)候需要重寫(xiě)hashCode方法和equals方法,因?yàn)槿绻枰o集合類(lèi)(比如:HashSet)添加對(duì)象,那么在添加之前需要查看給集合里是否已經(jīng)有了該對(duì)象,比較好的方式就是用hashCode。
  4. 注意的是String、Integer、Boolean、Double等這些類(lèi)都重寫(xiě)了equals和hashCode方法,這兩個(gè)方法是根據(jù)對(duì)象的內(nèi)容來(lái)比較和計(jì)算hashCode的。(詳細(xì)可以查看jdk下的String.java源代碼),所以只要對(duì)象的基本類(lèi)型值相同,那么hashcode就一定相同。
  5. equals()相等的兩個(gè)對(duì)象,hashcode()一般是相等的,最好在重寫(xiě)equals()方法時(shí),重寫(xiě)hashcode()方法; equals()不相等的兩個(gè)對(duì)象,卻并不能證明他們的hashcode()不相等。換句話說(shuō),equals()方法不相等的兩個(gè)對(duì)象,hashcode()有可能相等。 反過(guò)來(lái):hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。在object類(lèi)中,hashcode()方法是本地方法,返回的是對(duì)象的引用(地址值),而object類(lèi)中的equals()方法比較的也是兩個(gè)對(duì)象的引用(地址值),如果equals()相等,說(shuō)明兩個(gè)對(duì)象地址值也相等,當(dāng)然hashcode()也就相等了。

有許多人學(xué)了很長(zhǎng)時(shí)間的Java,但一直不明白hashCode方法的作用,我來(lái)解釋一下吧。首先,想要明白hashCode的作用,你必須要先知道Java中的集合。

總的來(lái)說(shuō),Java中的集合(Collection)有兩類(lèi),一類(lèi)是List,再有一類(lèi)是Set。你知道它們的區(qū)別嗎?前者集合內(nèi)的元素是有序的,元素可以重復(fù);后者元素?zé)o序,但元素不可重復(fù)。

那么這里就有一個(gè)比較嚴(yán)重的問(wèn)題了:要想保證元素不重復(fù),可兩個(gè)元素是否重復(fù)應(yīng)該依據(jù)什么來(lái)判斷呢?這就是Object.equals方法了。但是,如果每增加一個(gè)元素就檢查一次,那么當(dāng)元素很多時(shí),后添加到集合中的元素比較的次數(shù)就非常多了。也就是說(shuō),如果集合中現(xiàn)在已經(jīng)有1000個(gè)元素,那么第1001個(gè)元素加入集合時(shí),它就要調(diào)用1000次equals方法。這顯然會(huì)大大降低效率。

于是,Java采用了哈希表的原理。哈希(Hash)實(shí)際上是個(gè)人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也稱(chēng)為散列算法,是將數(shù)據(jù)依特定算法直接指定到一個(gè)地址上。如果詳細(xì)講解哈希算法,那需要更多的文章篇幅,我在這里就不介紹了。初學(xué)者可以這樣理解,hashCode方法實(shí)際上返回的就是對(duì)象存儲(chǔ)的物理地址(實(shí)際可能并不是)。

這樣一來(lái),當(dāng)集合要添加新的元素時(shí),先調(diào)用這個(gè)元素的hashCode方法,就一下子能定位到它應(yīng)該放置的物理位置上。如果這個(gè)位置上沒(méi)有元素,它就可以直接存儲(chǔ)在這個(gè)位置上,不用再進(jìn)行任何比較了;如果這個(gè)位置上已經(jīng)有元素了,就調(diào)用它的equals方法與新元素進(jìn)行比較,相同的話就不存了,不相同就散列其它的地址。所以這里存在一個(gè)沖突解決的問(wèn)題。這樣一來(lái)實(shí)際調(diào)用equals方法的次數(shù)就大大降低了,幾乎只需要一兩次。 所以,Java對(duì)于eqauls方法和hashCode方法是這樣規(guī)定的:

  1. 如果兩個(gè)對(duì)象相同,那么它們的hashCode值一定要相同;
  2. 如果兩個(gè)對(duì)象的hashCode相同,它們并不一定相同。

上面說(shuō)的對(duì)象相同指的是用eqauls方法比較。 你當(dāng)然可以不按要求去做了,但你會(huì)發(fā)現(xiàn),相同的對(duì)象可以出現(xiàn)在Set集合中。同時(shí),增加新元素的效率會(huì)大大下降。

Set里的元素是不能重復(fù)的,那么用什么方法來(lái)區(qū)分重復(fù)與否呢? 是用==還是equals()? 它們有何區(qū)別?

使用equals()區(qū)分。

==是用來(lái)判斷兩者是否是同一對(duì)象(同一事物),而equals是用來(lái)判斷是否引用同一個(gè)對(duì)象。

set里面存放的是對(duì)象的引用,所以當(dāng)兩個(gè)元素只要滿足了equals()時(shí)就已經(jīng)指向同一個(gè)對(duì)象,也就出現(xiàn)了重復(fù)元素。所以應(yīng)該用equals()來(lái)判斷。

多線程環(huán)境中安全使用集合API

在集合API中,最初設(shè)計(jì)的Vector和Hashtable是多線程安全的。例如:對(duì)于Vector來(lái)說(shuō),用來(lái)添加和刪除元素的方法是同步的。如果只有一個(gè)線程與Vector的實(shí)例交互,那么,要求獲取和釋放對(duì)象鎖便是一種浪費(fèi),另外在不必要的時(shí)候如果濫用同步化,也有可能會(huì)帶來(lái)死鎖。因此,對(duì)于更改集合內(nèi)容的方法,沒(méi)有一個(gè)是同步化的。集合本質(zhì)上是非多線程安全的,當(dāng)多個(gè)線程與集合交互時(shí),為了使它多線程安全,必須采取額外的措施。

在Collections類(lèi)中有多個(gè)靜態(tài)方法,它們可以獲取通過(guò)同步方法封裝非同步集合而得到的集合:

public static Collection synchronizedCollention(Collection c)

public static List synchronizedList(list l)

public static Map synchronizedMap(Map m)

public static Set synchronizedSet(Set s)

public static SortedMap synchronizedSortedMap(SortedMap sm)

public static SortedSet synchronizedSortedSet(SortedSet ss)

這些方法基本上返回具有同步集合方法版本的新類(lèi)。比如,為了創(chuàng)建多線程安全且由ArrayList支持的List,可以使用如下代碼:

List list = Collection.synchronizedList(new ArrayList());

注意,ArrayList實(shí)例馬上封裝起來(lái),不存在對(duì)未同步化ArrayList的直接引用(即直接封裝匿名實(shí)例)。這是一種最安全的途徑。如果另一個(gè)線程要直接引用ArrayList實(shí)例,它可以執(zhí)行非同步修改。

下面給出一段多線程中安全遍歷集合元素的示例。我們使用Iterator逐個(gè)掃描List中的元素,在多線程環(huán)境中,當(dāng)遍歷當(dāng)前集合中的元素時(shí),一般希望阻止其他線程添加或刪除元素。安全遍歷的實(shí)現(xiàn)方法如下:

import java.util.*;  

public class SafeCollectionIteration extends Object {  
    public static void main(String[] args) {  
        //為了安全起見(jiàn),僅使用同步列表的一個(gè)引用,這樣可以確??刂屏怂性L問(wèn)  
        //集合必須同步化,這里是一個(gè)List  
        List wordList = Collections.synchronizedList(new ArrayList());  

        //wordList中的add方法是同步方法,會(huì)獲取wordList實(shí)例的對(duì)象鎖  
        wordList.add("Iterators");  
        wordList.add("require");  
        wordList.add("special");  
        wordList.add("handling");  

        //獲取wordList實(shí)例的對(duì)象鎖,  
        //迭代時(shí),阻塞其他線程調(diào)用add或remove等方法修改元素  
        synchronized ( wordList ) {  
            Iterator iter = wordList.iterator();  
            while ( iter.hasNext() ) {  
                String s = (String) iter.next();  
                System.out.println("found string: " + s + ", length=" +s.length());  
            }  
        }  
    }  
}  

這里需要注意的是:在Java語(yǔ)言中,大部分的線程安全類(lèi)都是相對(duì)線程安全的,它能保證對(duì)這個(gè)對(duì)象單獨(dú)的操作時(shí)線程安全的,我們?cè)谡{(diào)用的時(shí)候不需要額外的保障措施,但是對(duì)于一些特定的連續(xù)調(diào)用,就可能需要在調(diào)用端使用額外的同步手段來(lái)保證調(diào)用的正確性。例如Vector、HashTable、Collections的synchronizedXxxx()方法包裝的集合等。

集合類(lèi)的源碼分析

參考:

ArrayList源碼剖析

LinkedList源碼剖析

Vector源碼剖析

HashMap源碼剖析

LinkedHashMap源碼剖析

HashTable源碼剖析

HashMap的底層實(shí)現(xiàn)

參考:

https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/HashMap%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90.md

HashMap 的實(shí)現(xiàn)原理

  • HashMap概述:HashMap是基于哈希表的Map接口的非同步實(shí)現(xiàn)。此實(shí)現(xiàn)提供所有可選的映射操作,并允許使用null值和null鍵。此類(lèi)不保證映射的順序,特別是它不保證該順序恒久不變。
  • HashMap的數(shù)據(jù)結(jié)構(gòu):在java編程語(yǔ)言中,最基本的結(jié)構(gòu)就是兩種,一個(gè)是數(shù)組,另外一個(gè)是模擬指針(引用),所有的數(shù)據(jù)結(jié)構(gòu)都可以用這兩個(gè)基本結(jié)構(gòu)來(lái)構(gòu)造的,HashMap也不例外。HashMap實(shí)際上是一個(gè)“鏈表散列”的數(shù)據(jù)結(jié)構(gòu),即數(shù)組和鏈表的結(jié)合體。

從上圖中可以看出,HashMap底層就是一個(gè)數(shù)組結(jié)構(gòu),數(shù)組中的每一項(xiàng)又是一個(gè)鏈表。當(dāng)新建一個(gè)HashMap的時(shí)候,就會(huì)初始化一個(gè)數(shù)組。

容器類(lèi)之間的區(qū)別

HashMap 和 HashTable 的區(qū)別

  1. 繼承不同。
public class Hashtable extends Dictionary implements Map
public class HashMap  extends AbstractMap implements Map
  1. Hashtable 中的方法是同步的,而HashMap中的方法在缺省情況下是非同步的。在多線程并發(fā)的環(huán)境下,可以直接使用Hashtable,但是要使用HashMap的話就要自己增加同步處理了。
  2. Hashtable中,key和value都不允許出現(xiàn)null值。在HashMap中,null可以作為鍵,這樣的鍵只有一個(gè);可以有一個(gè)或多個(gè)鍵所對(duì)應(yīng)的值為null。當(dāng)get()方法返回null值時(shí),即可以表示 HashMap中沒(méi)有該鍵,也可以表示該鍵所對(duì)應(yīng)的值為null。因此,在HashMap中不能由get()方法來(lái)判斷HashMap中是否存在某個(gè)鍵, 而應(yīng)該用containsKey()方法來(lái)判斷。
  3. 兩個(gè)遍歷方式的內(nèi)部實(shí)現(xiàn)上不同。Hashtable、HashMap都使用了 Iterator。而由于歷史原因,Hashtable還使用了Enumeration的方式 。
  4. 哈希值的使用不同,HashTable直接使用對(duì)象的hashCode。而HashMap重新計(jì)算hash值。
  5. Hashtable和HashMap它們兩個(gè)內(nèi)部實(shí)現(xiàn)方式的數(shù)組的初始大小和擴(kuò)容的方式。HashTable中hash數(shù)組默認(rèn)大小是11,增加的方式是 old*2+1。HashMap中hash數(shù)組的默認(rèn)大小是16,而且一定是2的指數(shù)。

ArrayMap 和 HashMap 的區(qū)別

  • 存儲(chǔ)方式不一樣。ArrayMap 使用兩個(gè)數(shù)組來(lái)存儲(chǔ),HashMap 則使用一個(gè)鏈表數(shù)組來(lái)存儲(chǔ)。
  • 擴(kuò)容處理方法不一樣。ArrayMap 使用 System.arraycopy() copy 數(shù)組,而 HashMap 使用 new HashMapEntry 重新創(chuàng)建數(shù)組。
  • ArrayMap 提供了數(shù)組收縮的功能,在clear或remove后,會(huì)重新收縮數(shù)組,節(jié)省空間。
  • ArrayMap 采用二分法查找。

ArrayList Vector LinkedList 三者的區(qū)別

  • 三者都實(shí)現(xiàn)了List 接口;但 LinkedList 還實(shí)現(xiàn)了 Queue 接口。
  • ArrayList 和 Vector 使用數(shù)組存儲(chǔ)數(shù)據(jù);LinkedList 使用雙向鏈表存儲(chǔ)數(shù)據(jù)。
  • ArrayList 和 LinkedList 是非線程安全的;Vector 是線程安全的。
  • ArrayList 數(shù)據(jù)增長(zhǎng)時(shí)空間增長(zhǎng)50%;而 Vector 是增長(zhǎng)1倍;
  • LinkedList 在添加、刪除元素時(shí)具有更好的性能,但讀取性能要低一些。
  • LinkedList 與 ArrayList 最大的區(qū)別是 LinkedList 更加靈活,并且部分方法的效率比 ArrayList 對(duì)應(yīng)方法的效率要高很多,對(duì)于數(shù)據(jù)頻繁出入的情況下,并且要求操作要足夠靈活,建議使用 LinkedList;對(duì)于數(shù)組變動(dòng)不大,主要是用來(lái)查詢(xún)的情況下,可以使用 ArrayList。

TreeMap、HashMap、LinkedHashMap的區(qū)別。

Map主要用于存儲(chǔ)健值對(duì),根據(jù)鍵得到值,因此不允許鍵重復(fù)(重復(fù)了覆蓋了),但允許值重復(fù)。

Hashmap 是一個(gè)最常用的Map,它根據(jù)鍵的HashCode 值存儲(chǔ)數(shù)據(jù),根據(jù)鍵可以直接獲取它的值,具有很快的訪問(wèn)速度,遍歷時(shí),取得數(shù)據(jù)的順序是完全隨機(jī)的。HashMap最多只允許一條記錄的鍵為Null,允許多條記錄的值為Null。HashMap不支持線程的同步,即任一時(shí)刻可以有多個(gè)線程同時(shí)寫(xiě)HashMap,可能會(huì)導(dǎo)致數(shù)據(jù)的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。

Hashtable與 HashMap類(lèi)似,它繼承自Dictionary類(lèi),不同的是:它不允許記錄的鍵或者值為空;它支持線程的同步,即任一時(shí)刻只有一個(gè)線程能寫(xiě)Hashtable,因此也導(dǎo)致了 Hashtable在寫(xiě)入時(shí)會(huì)比較慢。

LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時(shí),先得到的記錄肯定是先插入的。也可以在構(gòu)造時(shí)用帶參數(shù),按照應(yīng)用次數(shù)排序。在遍歷的時(shí)候會(huì)比HashMap慢,不過(guò)有種情況例外,當(dāng)HashMap容量很大,實(shí)際數(shù)據(jù)較少時(shí),遍歷起來(lái)可能會(huì)比LinkedHashMap慢,因?yàn)長(zhǎng)inkedHashMap的遍歷速度只和實(shí)際數(shù)據(jù)有關(guān),和容量無(wú)關(guān),而HashMap的遍歷速度和他的容量有關(guān)。

TreeMap實(shí)現(xiàn)SortMap接口,能夠把它保存的記錄根據(jù)鍵排序,默認(rèn)是按鍵值的升序排序,也可以指定排序的比較器,當(dāng)用Iterator 遍歷TreeMap時(shí),得到的記錄是排過(guò)序的。

一般情況下,我們用的最多的是HashMap。HashMap里面存入的鍵值對(duì)在取出的時(shí)候是隨機(jī)的,它根據(jù)鍵的HashCode值存儲(chǔ)數(shù)據(jù),根據(jù)鍵可以直接獲取它的值,具有很快的訪問(wèn)速度。在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。

TreeMap取出來(lái)的是排序后的鍵值對(duì)。但如果您要按自然順序或自定義順序遍歷鍵,那么TreeMap會(huì)更好。

LinkedHashMap 是HashMap的一個(gè)子類(lèi),如果需要輸出的順序和輸入的相同,那么用LinkedHashMap可以實(shí)現(xiàn),它還可以按讀取順序來(lái)排列,像連接池中可以應(yīng)用。

參考:

HashMap,LinkedHashMap,TreeMap的區(qū)別

Map、Set、List、Queue、Stack的特點(diǎn)與用法。

Collection 是對(duì)象集合

Collection 有兩個(gè)子接口 List 和 Set

List 可以通過(guò)下標(biāo) (1,2..) 來(lái)取得值,值可以重復(fù)

而 Set 只能通過(guò)游標(biāo)來(lái)取值,并且值是不能重復(fù)的

ArrayList , Vector , LinkedList 是 List 的實(shí)現(xiàn)類(lèi)

ArrayList 是線程不安全的, Vector 是線程安全的,這兩個(gè)類(lèi)底層都是由數(shù)組實(shí)現(xiàn)的

LinkedList 是線程不安全的,底層是由鏈表實(shí)現(xiàn)的

Map 是鍵值對(duì)集合

HashTable 和 HashMap 是 Map 的實(shí)現(xiàn)類(lèi)

HashTable 是線程安全的,不能存儲(chǔ) null 值

HashMap 不是線程安全的,可以存儲(chǔ) null 值

Stack類(lèi):繼承自Vector,實(shí)現(xiàn)一個(gè)后進(jìn)先出的棧。提供了幾個(gè)基本方法,push、pop、peak、empty、search等。

Queue接口:提供了幾個(gè)基本方法,offer、poll、peek等。已知實(shí)現(xiàn)類(lèi)有LinkedList、PriorityQueue等。

參考:

Map、Set、List、Queue、Stack的特點(diǎn)與用法

內(nèi)存相關(guān)知識(shí)

參考:

Android內(nèi)存泄漏總結(jié)

Java 內(nèi)存分配策略

Java 程序運(yùn)行時(shí)的內(nèi)存分配策略有三種,分別是靜態(tài)分配、棧式分配和堆式分配,對(duì)應(yīng)的,三種存儲(chǔ)策略使用的內(nèi)存空間主要分別是靜態(tài)存儲(chǔ)區(qū)(也稱(chēng)方法區(qū))、棧區(qū)和堆區(qū)。

  • 靜態(tài)存儲(chǔ)區(qū)(方法區(qū)):主要存放靜態(tài)數(shù)據(jù)、全局 static 數(shù)據(jù)和常量。這塊內(nèi)存在程序編譯時(shí)就已經(jīng)分配好,并且在程序整個(gè)運(yùn)行期間都存在。
  • 棧區(qū) :當(dāng)方法被執(zhí)行時(shí),方法體內(nèi)的局部變量(其中包括基礎(chǔ)數(shù)據(jù)類(lèi)型、對(duì)象的引用)都在棧上創(chuàng)建,并在方法執(zhí)行結(jié)束時(shí)這些局部變量所持有的內(nèi)存將會(huì)自動(dòng)被釋放。因?yàn)闂?nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。
  • 堆區(qū) : 又稱(chēng)動(dòng)態(tài)內(nèi)存分配,通常就是指在程序運(yùn)行時(shí)直接 new 出來(lái)的內(nèi)存,也就是對(duì)象的實(shí)例。這部分內(nèi)存在不使用時(shí)將會(huì)由 Java 垃圾回收器來(lái)負(fù)責(zé)回收。

棧與堆的區(qū)別

在方法體內(nèi)定義的(局部變量)一些基本類(lèi)型的變量和對(duì)象的引用變量都是在方法的棧內(nèi)存中分配的。當(dāng)在一段方法塊中定義一個(gè)變量時(shí),Java 就會(huì)在棧中為該變量分配內(nèi)存空間,當(dāng)超過(guò)該變量的作用域后,該變量也就無(wú)效了,分配給它的內(nèi)存空間也將被釋放掉,該內(nèi)存空間可以被重新使用。

堆內(nèi)存用來(lái)存放所有由 new 創(chuàng)建的對(duì)象(包括該對(duì)象其中的所有成員變量)和數(shù)組。在堆中分配的內(nèi)存,將由 Java 垃圾回收器來(lái)自動(dòng)管理。在堆中產(chǎn)生了一個(gè)數(shù)組或者對(duì)象后,還可以在棧中定義一個(gè)特殊的變量,這個(gè)變量的取值等于數(shù)組或者對(duì)象在堆內(nèi)存中的首地址,這個(gè)特殊的變量就是我們上面說(shuō)的引用變量。我們可以通過(guò)這個(gè)引用變量來(lái)訪問(wèn)堆中的對(duì)象或者數(shù)組。

舉個(gè)例子:

public class Sample {
    int s1 = 0;
    Sample mSample1 = new Sample();

    public void method() {
        int s2 = 1;
        Sample mSample2 = new Sample();
    }
}

Sample mSample3 = new Sample();

Sample 類(lèi)的局部變量 s2 和引用變量 mSample2 都是存在于棧中,但 mSample2 指向的對(duì)象是存在于堆上的。mSample3 指向的對(duì)象實(shí)體存放在堆上,包括這個(gè)對(duì)象的所有成員變量 s1 和 mSample1,而它自己存在于棧中。

結(jié)論:

  1. 局部變量的基本數(shù)據(jù)類(lèi)型和引用存儲(chǔ)于棧中,引用的對(duì)象實(shí)體存儲(chǔ)于堆中。因?yàn)樗鼈儗儆诜椒ㄖ械淖兞?,生命周期隨方法而結(jié)束。
  2. 成員變量全部存儲(chǔ)于堆中(包括基本數(shù)據(jù)類(lèi)型,引用和引用的對(duì)象實(shí)體)。因?yàn)樗鼈儗儆陬?lèi),類(lèi)對(duì)象終究是要被new出來(lái)使用的。

heap(堆) 和 stack(棧) 有什么區(qū)別。

Java 的堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),類(lèi)的(對(duì)象從中分配空間。這些對(duì)象通過(guò)new、newarray、anewarray和multianewarray等指令建立,它們不需要程序代碼來(lái)顯式的釋放。堆是由垃圾回收來(lái)負(fù)責(zé)的,堆的優(yōu)勢(shì)是可以動(dòng)態(tài)地分配內(nèi)存大小,生存期也不必事先告訴編譯器,因?yàn)樗窃谶\(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存的,Java的垃圾收集器會(huì)自動(dòng)收走這些不再使用的數(shù)據(jù)。但缺點(diǎn)是,由于要在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存,存取速度較慢。

棧的優(yōu)勢(shì)是,存取速度比堆要快,僅次于寄存器,棧數(shù)據(jù)可以共享。但缺點(diǎn)是,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類(lèi)型的變量(,int, short, long, byte, float, double, boolean, char)和對(duì)象句柄。棧有一個(gè)很重要的特殊性,就是存在棧中的數(shù)據(jù)可以共享。

  • Stack存取速度僅次于寄存器,存儲(chǔ)效率比heap高,可共享存儲(chǔ)數(shù)據(jù),但是其中數(shù)據(jù)的大小和生存期必須在運(yùn)行前確定。
  • Heap是運(yùn)行時(shí)可動(dòng)態(tài)分配的數(shù)據(jù)區(qū),從速度看比Stack慢,Heap里面的數(shù)據(jù)不共享,大小和生存期都可以在運(yùn)行時(shí)再確定。

Java是如何管理內(nèi)存

Java的內(nèi)存管理就是對(duì)象的分配和釋放問(wèn)題。在 Java 中,程序員需要通過(guò)關(guān)鍵字 new 為每個(gè)對(duì)象申請(qǐng)內(nèi)存空間 (基本類(lèi)型除外),所有的對(duì)象都在堆 (Heap)中分配空間。另外,對(duì)象的釋放是由 GC 決定和執(zhí)行的。在 Java 中,內(nèi)存的分配是由程序完成的,而內(nèi)存的釋放是由 GC 完成的,這種收支兩條線的方法確實(shí)簡(jiǎn)化了程序員的工作。但同時(shí),它也加重了JVM的工作。這也是 Java 程序運(yùn)行速度較慢的原因之一。因?yàn)?,GC 為了能夠正確釋放對(duì)象,GC 必須監(jiān)控每一個(gè)對(duì)象的運(yùn)行狀態(tài),包括對(duì)象的申請(qǐng)、引用、被引用、賦值等,GC 都需要進(jìn)行監(jiān)控。監(jiān)視對(duì)象狀態(tài)是為了更加準(zhǔn)確地、及時(shí)地釋放對(duì)象,而釋放對(duì)象的根本原則就是該對(duì)象不再被引用。

為了更好理解 GC 的工作原理,我們可以將對(duì)象考慮為有向圖的頂點(diǎn),將引用關(guān)系考慮為圖的有向邊,有向邊從引用者指向被引對(duì)象。另外,每個(gè)線程對(duì)象可以作為一個(gè)圖的起始頂點(diǎn),例如大多程序從 main 進(jìn)程開(kāi)始執(zhí)行,那么該圖就是以 main 進(jìn)程頂點(diǎn)開(kāi)始的一棵根樹(shù)。在這個(gè)有向圖中,根頂點(diǎn)可達(dá)的對(duì)象都是有效對(duì)象,GC將不回收這些對(duì)象。如果某個(gè)對(duì)象 (連通子圖)與這個(gè)根頂點(diǎn)不可達(dá)(注意,該圖為有向圖),那么我們認(rèn)為這個(gè)(這些)對(duì)象不再被引用,可以被 GC 回收。以下,我們舉一個(gè)例子說(shuō)明如何用有向圖表示內(nèi)存管理。對(duì)于程序的每一個(gè)時(shí)刻,我們都有一個(gè)有向圖表示JVM的內(nèi)存分配情況。以下右圖,就是左邊程序運(yùn)行到第6行的示意圖。

Java使用有向圖的方式進(jìn)行內(nèi)存管理,可以消除引用循環(huán)的問(wèn)題,例如有三個(gè)對(duì)象,相互引用,只要它們和根進(jìn)程不可達(dá)的,那么GC也是可以回收它們的。這種方式的優(yōu)點(diǎn)是管理內(nèi)存的精度很高,但是效率較低。另外一種常用的內(nèi)存管理技術(shù)是使用計(jì)數(shù)器,例如COM模型采用計(jì)數(shù)器方式管理構(gòu)件,它與有向圖相比,精度行低(很難處理循環(huán)引用的問(wèn)題),但執(zhí)行效率很高。

GC引用計(jì)數(shù)法循環(huán)引用會(huì)發(fā)生什么情況

什么是Java中的內(nèi)存泄露

在Java中,內(nèi)存泄漏就是存在一些被分配的對(duì)象,這些對(duì)象有下面兩個(gè)特點(diǎn),首先,這些對(duì)象是可達(dá)的,即在有向圖中,存在通路可以與其相連;其次,這些對(duì)象是無(wú)用的,即程序以后不會(huì)再使用這些對(duì)象。如果對(duì)象滿足這兩個(gè)條件,這些對(duì)象就可以判定為Java中的內(nèi)存泄漏,這些對(duì)象不會(huì)被GC所回收,然而它卻占用內(nèi)存。

在C++中,內(nèi)存泄漏的范圍更大一些。有些對(duì)象被分配了內(nèi)存空間,然后卻不可達(dá),由于C++中沒(méi)有GC,這些內(nèi)存將永遠(yuǎn)收不回來(lái)。在Java中,這些不可達(dá)的對(duì)象都由GC負(fù)責(zé)回收,因此程序員不需要考慮這部分的內(nèi)存泄露。

通過(guò)分析,我們得知,對(duì)于C++,程序員需要自己管理邊和頂點(diǎn),而對(duì)于Java程序員只需要管理邊就可以了(不需要管理頂點(diǎn)的釋放)。通過(guò)這種方式,Java提高了編程的效率。

因此,通過(guò)以上分析,我們知道在Java中也有內(nèi)存泄漏,但范圍比C++要小一些。因?yàn)镴ava從語(yǔ)言上保證,任何對(duì)象都是可達(dá)的,所有的不可達(dá)對(duì)象都由GC管理。

對(duì)于程序員來(lái)說(shuō),GC基本是透明的,不可見(jiàn)的。雖然,我們只有幾個(gè)函數(shù)可以訪問(wèn)GC,例如運(yùn)行GC的函數(shù)System.gc(),但是根據(jù)Java語(yǔ)言規(guī)范定義, 該函數(shù)不保證JVM的垃圾收集器一定會(huì)執(zhí)行。因?yàn)?,不同的JVM實(shí)現(xiàn)者可能使用不同的算法管理GC。通常,GC的線程的優(yōu)先級(jí)別較低。JVM調(diào)用GC的策略也有很多種,有的是內(nèi)存使用到達(dá)一定程度時(shí),GC才開(kāi)始工作,也有定時(shí)執(zhí)行的,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC。但通常來(lái)說(shuō),我們不需要關(guān)心這些。除非在一些特定的場(chǎng)合,GC的執(zhí)行影響應(yīng)用程序的性能,例如對(duì)于基于Web的實(shí)時(shí)系統(tǒng),如網(wǎng)絡(luò)游戲等,用戶不希望GC突然中斷應(yīng)用程序執(zhí)行而進(jìn)行垃圾回收,那么我們需要調(diào)整GC的參數(shù),讓GC能夠通過(guò)平緩的方式釋放內(nèi)存,例如將垃圾回收分解為一系列的小步驟執(zhí)行,Sun提供的HotSpot JVM就支持這一特性。

同樣給出一個(gè) Java 內(nèi)存泄漏的典型例子,

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
    Object o = new Object();
    v.add(o);
    o = null;   
}

在這個(gè)例子中,我們循環(huán)申請(qǐng)Object對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè) Vector 中,如果我們僅僅釋放引用本身,那么 Vector 仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì) GC 來(lái)說(shuō)是不可回收的。因此,如果對(duì)象加入到Vector 后,還必須從 Vector 中刪除,最簡(jiǎn)單的方法就是將 Vector 對(duì)象設(shè)置為 null。

詳解Java中的內(nèi)存泄漏

Java內(nèi)存回收機(jī)制

不論哪種語(yǔ)言的內(nèi)存分配方式,都需要返回所分配內(nèi)存的真實(shí)地址,也就是返回一個(gè)指針到內(nèi)存塊的首地址。Java中對(duì)象是采用new或者反射的方法創(chuàng)建的,這些對(duì)象的創(chuàng)建都是在堆(Heap)中分配的,所有對(duì)象的回收都是由Java虛擬機(jī)通過(guò)垃圾回收機(jī)制完成的。GC為了能夠正確釋放對(duì)象,會(huì)監(jiān)控每個(gè)對(duì)象的運(yùn)行狀況,對(duì)他們的申請(qǐng)、引用、被引用、賦值等狀況進(jìn)行監(jiān)控,Java會(huì)使用有向圖的方法進(jìn)行管理內(nèi)存,實(shí)時(shí)監(jiān)控對(duì)象是否可以達(dá)到,如果不可到達(dá),則就將其回收,這樣也可以消除引用循環(huán)的問(wèn)題。在Java語(yǔ)言中,判斷一個(gè)內(nèi)存空間是否符合垃圾收集標(biāo)準(zhǔn)有兩個(gè):一個(gè)是給對(duì)象賦予了空值null,以下再?zèng)]有調(diào)用過(guò),另一個(gè)是給對(duì)象賦予了新值,這樣重新分配了內(nèi)存空間。

Java內(nèi)存泄漏引起的原因

內(nèi)存泄漏是指無(wú)用對(duì)象(不再使用的對(duì)象)持續(xù)占有內(nèi)存或無(wú)用對(duì)象的內(nèi)存得不到及時(shí)釋放,從而造成內(nèi)存空間的浪費(fèi)稱(chēng)為內(nèi)存泄漏。內(nèi)存泄露有時(shí)不嚴(yán)重且不易察覺(jué),這樣開(kāi)發(fā)者就不知道存在內(nèi)存泄露,但有時(shí)也會(huì)很?chē)?yán)重,會(huì)提示你Out of memory。

Java內(nèi)存泄漏的根本原因是什么呢?長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用就很可能發(fā)生內(nèi)存泄漏,盡管短生命周期對(duì)象已經(jīng)不再需要,但是因?yàn)殚L(zhǎng)生命周期持有它的引用而導(dǎo)致不能被回收,這就是Java中內(nèi)存泄漏的發(fā)生場(chǎng)景。具體主要有如下幾大類(lèi):

  1. 靜態(tài)集合類(lèi)引起內(nèi)存泄漏。

像HashMap、Vector等的使用最容易出現(xiàn)內(nèi)存泄露,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對(duì)象Object也不能被釋放,因?yàn)樗麄円矊⒁恢北籚ector等引用著。 例如:

Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
  Object o = new Object();
  v.add(o);
  o = null;
}

在這個(gè)例子中,循環(huán)申請(qǐng)Object 對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè)Vector 中,如果僅僅釋放引用本身(o=null),那么Vector 仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì)GC 來(lái)說(shuō)是不可回收的。因此,如果對(duì)象加入到Vector 后,還必須從Vector 中刪除,最簡(jiǎn)單的方法就是將Vector對(duì)象設(shè)置為null。

  1. 當(dāng)集合里面的對(duì)象屬性被修改后,再調(diào)用remove()方法時(shí)不起作用。例如:
public static void main(String[] args) {
  Set<Person> set = new HashSet<Person>();
  Person p1 = new Person("唐僧","pwd1",25);
  Person p2 = new Person("孫悟空","pwd2",26);
  Person p3 = new Person("豬八戒","pwd3",27);
  set.add(p1);
  set.add(p2);
  set.add(p3);
  System.out.println("總共有:"+set.size()+" 個(gè)元素!"); //結(jié)果:總共有:3 個(gè)元素!
  p3.setAge(2); //修改p3的年齡,此時(shí)p3元素對(duì)應(yīng)的hashcode值發(fā)生改變

  set.remove(p3); //此時(shí)remove不掉,造成內(nèi)存泄漏

  set.add(p3); //重新添加,居然添加成功
  System.out.println("總共有:"+set.size()+" 個(gè)元素!"); //結(jié)果:總共有:4 個(gè)元素!
  for (Person person : set) {
    System.out.println(person);
  }
}
  1. 監(jiān)聽(tīng)器

在java 編程中,我們都需要和監(jiān)聽(tīng)器打交道,通常一個(gè)應(yīng)用當(dāng)中會(huì)用到很多監(jiān)聽(tīng)器,我們會(huì)調(diào)用一個(gè)控件的諸如addXXXListener()等方法來(lái)增加監(jiān)聽(tīng)器,但往往在釋放對(duì)象的時(shí)候卻沒(méi)有記住去刪除這些監(jiān)聽(tīng)器,從而增加了內(nèi)存泄漏的機(jī)會(huì)。

  1. 各種連接

比如數(shù)據(jù)庫(kù)連接(dataSourse.getConnection()),網(wǎng)絡(luò)連接(socket)和io連接,除非其顯式的調(diào)用了其close()方法將其連接關(guān)閉,否則是不會(huì)自動(dòng)被GC 回收的。對(duì)于Resultset 和Statement 對(duì)象可以不進(jìn)行顯式回收,但Connection 一定要顯式回收,因?yàn)镃onnection 在任何時(shí)候都無(wú)法自動(dòng)回收,而Connection一旦回收,Resultset 和Statement 對(duì)象就會(huì)立即為NULL。但是如果使用連接池,情況就不一樣了,除了要顯式地關(guān)閉連接,還必須顯式地關(guān)閉Resultset Statement 對(duì)象(關(guān)閉其中一個(gè),另外一個(gè)也會(huì)關(guān)閉),否則就會(huì)造成大量的Statement 對(duì)象無(wú)法釋放,從而引起內(nèi)存泄漏。這種情況下一般都會(huì)在try里面去的連接,在finally里面釋放連接。

  1. 內(nèi)部類(lèi)和外部模塊的引用

內(nèi)部類(lèi)的引用是比較容易遺忘的一種,而且一旦沒(méi)釋放可能導(dǎo)致一系列的后繼類(lèi)對(duì)象沒(méi)有釋放。此外程序員還要小心外部模塊不經(jīng)意的引用,例如程序員A 負(fù)責(zé)A 模塊,調(diào)用了B 模塊的一個(gè)方法如:public void registerMsg(Object b);這種調(diào)用就要非常小心了,傳入了一個(gè)對(duì)象,很可能模塊B就保持了對(duì)該對(duì)象的引用,這時(shí)候就需要注意模塊B 是否提供相應(yīng)的操作去除引用。

  1. 單例模式

不正確使用單例模式是引起內(nèi)存泄漏的一個(gè)常見(jiàn)問(wèn)題,單例對(duì)象在初始化后將在JVM的整個(gè)生命周期中存在(以靜態(tài)變量的方式),如果單例對(duì)象持有外部的引用,那么這個(gè)對(duì)象將不能被JVM正?;厥眨瑢?dǎo)致內(nèi)存泄漏,考慮下面的例子:

class A {
  public A(){
    B.getInstance().setA(this);
  }
  // ...
}

//B類(lèi)采用單例模式
class B {
  private A a;
  private static B instance=new B();
  public B(){
    
  }
  public static B getInstance(){
    return instance;
  }
  public void setA(A a){
    this.a=a;
  }
  // ...
} 

顯然B采用singleton模式,它持有一個(gè)A對(duì)象的引用,而這個(gè)A類(lèi)的對(duì)象將不能被回收。想象下如果A是個(gè)比較復(fù)雜的對(duì)象或者集合類(lèi)型會(huì)發(fā)生什么情況。

Java內(nèi)存回收機(jī)制,GC 垃圾回收機(jī)制,垃圾回收的優(yōu)點(diǎn)和原理,并說(shuō)出3種回收機(jī)制。

  1. java語(yǔ)言最顯著的特點(diǎn)就是引入了垃圾回收機(jī)制,它使java程序員在編寫(xiě)程序時(shí)不再考慮內(nèi)存管理的問(wèn)題。
  2. 由于有這個(gè)垃圾回收機(jī)制,java中的對(duì)象不再有“作用域”的概念,只有引用的對(duì)象才有“作用域”。
  3. 垃圾回收機(jī)制有效的防止了內(nèi)存泄露,可以有效的使用可使用的內(nèi)存。
  4. 垃圾回收器通常作為一個(gè)單獨(dú)的低級(jí)別的線程運(yùn)行,在不可預(yù)知的情況下對(duì)內(nèi)存堆中已經(jīng)死亡的或很長(zhǎng)時(shí)間沒(méi)有用過(guò)的對(duì)象進(jìn)行清除和回收。
  5. 程序員不能實(shí)時(shí)的對(duì)某個(gè)對(duì)象或所有對(duì)象調(diào)用垃圾回收器進(jìn)行垃圾回收。

垃圾回收機(jī)制有:分代復(fù)制垃圾回收、標(biāo)記垃圾回收、增量垃圾回收。

垃圾回收算法

  1. 引用計(jì)數(shù)法:缺點(diǎn)是無(wú)法處理循環(huán)引用問(wèn)題
  2. 標(biāo)記-清除法:標(biāo)記所有從根結(jié)點(diǎn)開(kāi)始的可達(dá)對(duì)象,缺點(diǎn)是會(huì)造成內(nèi)存空間不連續(xù),不連續(xù)的內(nèi)存空間的工作效率低于連續(xù)的內(nèi)存空間,不容易分配內(nèi)存
  3. 標(biāo)記-壓縮算法:標(biāo)記-清除的改進(jìn),清除未標(biāo)記的對(duì)象時(shí)還將所有的存活對(duì)象壓縮到內(nèi)存的一端,之后,清理邊界所有空間既避免碎片產(chǎn)生,又不需要兩塊同樣大小的內(nèi)存快,性?xún)r(jià)比高。適用于老年代。
  4. 復(fù)制算法:將內(nèi)存空間分成兩塊,每次將正在使用的內(nèi)存中存活對(duì)象復(fù)制到未使用的內(nèi)存塊中,之后清除正在使用的內(nèi)存塊。算法效率高,但是代價(jià)是系統(tǒng)內(nèi)存折半。適用于新生代(存活對(duì)象少,垃圾對(duì)象多)
  5. 分代

Java 虛擬機(jī)的特性

Java 語(yǔ)言的一個(gè)非常重要的特點(diǎn)就是與平臺(tái)的無(wú)關(guān)性,而 Java 虛擬機(jī)就是實(shí)現(xiàn)這一特點(diǎn)的關(guān)鍵。一般的高級(jí)語(yǔ)言如果要在不同的平臺(tái)上運(yùn)行,至少需要編譯成不同的目標(biāo)代碼。而引入Java語(yǔ)言虛擬機(jī)后,Java語(yǔ)言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯。Java語(yǔ)言使用模式Java虛擬機(jī)屏蔽了與具體平臺(tái)相關(guān)的信息,使得Java語(yǔ)言編譯程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行。Java虛擬機(jī)在執(zhí)行字節(jié)碼時(shí),把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行。

參考:

JVM基礎(chǔ)知識(shí)

JVM類(lèi)加載機(jī)制

Java內(nèi)存區(qū)域與內(nèi)存溢出

那些情況下的對(duì)象會(huì)被垃圾回收機(jī)制處理掉

???Java 垃圾回收機(jī)制最基本的做法是分代回收。內(nèi)存中的區(qū)域被劃分成不同的世代,對(duì)象根據(jù)其存活的時(shí)間被保存在對(duì)應(yīng)的世代的區(qū)域中。一般的實(shí)現(xiàn)是劃分為3個(gè)世代:年輕、年老和永久。內(nèi)存的分配是發(fā)生在年輕世代中的。當(dāng)一個(gè)對(duì)象存活時(shí)間足夠長(zhǎng)的時(shí)候,它就會(huì)被復(fù)制到年老世代中。對(duì)于不同的世代可以使用不同的垃圾回收算法。進(jìn)行世代劃分的出發(fā)點(diǎn)是對(duì)應(yīng)用中對(duì)象存活時(shí)間進(jìn)行研究之后得出的統(tǒng)計(jì)規(guī)律。一般來(lái)說(shuō),一個(gè)應(yīng)用中的大部分對(duì)象的存活時(shí)間都很短。比如局部變量的存活時(shí)間就只在方法的執(zhí)行過(guò)程中?;谶@一點(diǎn),對(duì)于年輕世代的垃圾回收算法就可以很有針對(duì)性。

狀態(tài)機(jī)

Java的四種引用,強(qiáng)弱軟虛,用到的場(chǎng)景。

JDK1.2之前只有強(qiáng)引用,其他幾種引用都是在JDK1.2之后引入的。

  1. 強(qiáng)引用(Strong Reference)最常用的引用類(lèi)型,如Object obj = new Object(); 。只要強(qiáng)引用存在則GC時(shí)則必定不被回收。
  2. 軟引用(Soft Reference)用于描述還有用但非必須的對(duì)象,當(dāng)堆將發(fā)生OOM(Out Of Memory)時(shí)則會(huì)回收軟引用所指向的內(nèi)存空間,若回收后依然空間不足才會(huì)拋出OOM。一般用于實(shí)現(xiàn)內(nèi)存敏感的高速緩存。當(dāng)真正對(duì)象被標(biāo)記finalizable以及的finalize()方法調(diào)用之后并且內(nèi)存已經(jīng)清理, 那么如果SoftReference object還存在就被加入到它的 ReferenceQueue.只有前面幾步完成后,Soft Reference和Weak Reference的get方法才會(huì)返回null
  3. 弱引用(Weak Reference)發(fā)生GC時(shí)必定回收弱引用指向的內(nèi)存空間。和軟引用加入隊(duì)列的時(shí)機(jī)相同
  4. 虛引用(Phantom Reference)又稱(chēng)為幽靈引用或幻影引用,虛引用既不會(huì)影響對(duì)象的生命周期,也無(wú)法通過(guò)虛引用來(lái)獲取對(duì)象實(shí)例,僅用于在發(fā)生GC時(shí)接收一個(gè)系統(tǒng)通知。當(dāng)一個(gè)對(duì)象的finalize方法已經(jīng)被調(diào)用了之后,這個(gè)對(duì)象的幽靈引用會(huì)被加入到隊(duì)列中。通過(guò)檢查該隊(duì)列里面的內(nèi)容就知道一個(gè)對(duì)象是不是已經(jīng)準(zhǔn)備要被回收了。虛引用和軟引用、弱引用都不同,它會(huì)在內(nèi)存沒(méi)有清理的時(shí)候被加入引用隊(duì)列。虛引用的建立必須要傳入引用隊(duì)列,其他可以沒(méi)有。

數(shù)組復(fù)制

請(qǐng)使用 System.arrayCopy 或 Arrays.copyOf 實(shí)現(xiàn),且在 Java 中后者基于前者實(shí)現(xiàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容