1.面向對象都有哪些特性 繼承、封裝、多態性、抽象 2.Java中實現多態的機制是什麼? 繼承與介面 3.Java中異常分為哪些種類 3.1按照異常需要處理的時機分為編譯時異常(CheckedException)和運行時異常(RuntimeException)。 3.2對於編譯時異常的處理方法有兩種 ...
1.面向對象都有哪些特性
繼承、封裝、多態性、抽象
2.Java中實現多態的機制是什麼?
繼承與介面
3.Java中異常分為哪些種類
3.1按照異常需要處理的時機分為編譯時異常(CheckedException)和運行時異常(RuntimeException)。
3.2對於編譯時異常的處理方法有兩種:
(1)當前方法知道如何處理該異常,則用try…catch塊來處理該異常。
(2)當前方法不知道如何處理,則在定義該方法時聲明拋出該異常。
3.3運行時異常,如果顯式聲明或者捕獲將會對程式的可讀性和運行效率影響很大,所以由系統自動檢測並將它們交給預設的異常處理程式,當然如果有處理要求也可以顯式捕獲它們。
4.Java的數據類型
4.1Java的基本數據類型都有哪些,各占幾個位元組
Byte 1 char 2 short 2 int 4 float 4 double 8 long 8 boolean 1
4.2ing是基本數據類型嗎?可以被繼承嗎?
String是引用類型,底層用char數組實現的。因為String是final類,在java中被final修飾的類不能被繼承,因此String當然不可以被繼承。
5.Java的IO
5.1.Java中有幾種類型的流
位元組流和字元流。位元組流繼承於InputStream和OutputStream,字元流繼承於InputStreamReader和OutputStreamWriter。
5.2位元組流如何轉為字元流
位元組輸入流轉字元輸入流通過InputStreamReader實現,該類的構造函數可以傳入InputStream對象。
位元組輸出流轉字元輸出流通過OutputSreamWriter實現,該類的構造函數可以傳入OutputStream對象。
6.Java的集合
6.1ArrayList、HashSet、HashMap是線程安全的嗎?如果不是想要的線程安全的集合怎麼辦?
每個方法都沒有加鎖,顯然都是線程不安全的。
在集合中Vector和HashTable是線程安全的。
Collections工具類提供了相關的API,可以讓上面3個不安全的集合變為安全,如下:
Collections.synchronizedCollection(c)
Collections.synchronizedList(list)
Collections.synchronizedMap(m)
Collections.synchronizedSet(s)
6.2併發集合和普通集合如何區別?
併發集合常見的有ConCurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque等。併發集合位於java.util.concurrent包下,是jdk1.5之後才有的,主要作者是Doug Lea(http://baike.baidu.com/view/3141057.http)完成的。
在java中有普通集合、同步(線程安全)的集合、併發集合。普通集合通常性能最高,但是不保證多線程的安全性和併發的可靠性。線程安全集合僅僅是給集合添加了synchronized同步鎖,嚴重犧牲了性能,而且對併發的效率就更低了,併發集合則通過複雜的策略不僅保證了多線程的安全又提高的併發時的效率。
ConcurrentHashMap是線程安全的HashMap的實現,預設構造同樣有initialCapacity和loadFactor屬性,不過還多了一個concurrencyLevel屬性,三屬性預設值分別為16、0.75及16。其內部使用鎖分段技術,維持著鎖Segment的數組,在Segment數組中又存放著Entity[]數組,內部hash演算法將數據較均勻分佈在不同鎖中。
Put操作:並沒有在此方法上加上synchronized,首先對key.hashcode進行hash操作,得到key的hash值。Hash操作的演算法和map不同,根據此hash值計算並獲取其對應的數組中的Segment對象(繼承自ReentrantLock),接著調用此Segment對象的put方法來完成當前操作。
ConcurrentHashMap基於concurrencyLevel劃分出了多個Segment來對key-value進行存儲,從而避免每次put操作都得鎖住整個數組。在預設的情況下,最佳情況下可允許16個線程併發無阻塞的操作集合對象,儘可能地減少併發時的阻塞現象。
Get(key)
首先對key.hashCode進行hash操作,基於其值找到對應的Segment對象,調用其get方法完成當前操作。而Segment的get操作首先通過hash值和對象數組大小減1的值進行按位與操作來獲取數組上對應位置的HashEntry。在這個步驟中,可能會因為對象數組大小的改變,以及數組上對應位置的HashEntry產生不一致性,那麼ConcurrentHashMap是如何保證的?
對象數組大小的改變只有在put操作時有可能發生,由於HashEntry對象數組對應的變數是volatile類型的,因此可以保證如HashEntry對象數組大小發生改變,讀操作可看到最新的對象數組大小。
在獲取到了HashEntry對象後,怎麼能保證它及其next屬性構成的鏈表上的對象不會改變呢?這點ConcurrentHashMap採用了一個簡單的方式,即HashEntry對象中的hash、key、next屬性都是final的,這也就意味著沒辦法插入一個HashEntry對象到基於next屬性構成的鏈表中間或末尾。這樣就可以保證當獲取到HashEntry對象後,其基於next屬性構建的鏈表是不會發生變化的。
ConcurrentHashMap預設情況下採用將數據分為16個段進行存儲,並且16個段分別持有各自不同的鎖Segment,鎖僅用於put和remove等改變集合對象的操作,基於volatile及HashEntry鏈表的不變性實現了讀取的不加鎖。這些方式使得ConcurrentHashMap能夠保持極好的併發支持,尤其是對於讀取比插入和刪除頻繁的Map而言,而它採用的這些方法也可謂是對於Java記憶體模型、併發機制深刻掌握的體現。
7.Java的多線程
7.1多線程的兩種創建方式
Java.lang.Thread類的實例就是一個線程但是它需要調用java.lang.Runnable介面來執行,由於線程類本身就是調用的Runnable介面所以可以繼承java.lang.Thread類或者直接實現Runnable介面來重寫run()方法實現線程。
7.2在java中wait和sleep方法的不同?
最大的不同是在等待時wait會釋放鎖,而sleep一直持有鎖。Wait通常被用於線程間交互,sleep通常被用於暫停執行。
7.3synchronized和volatile關鍵字的作用
一旦一個共用變數(類的成員變數、類的靜態成員變數)被volatile修飾之後,那麼就具備了兩層語義:
(1)保證了不同線程對這個變數進行操作時的可見性,即一個線程修改了某個變數的值,這新值對其他線程來說是立即可見的。
(2)禁止進行指令重排序。
Volatile本質是在告訴jvm當前變數在寄存器(工作記憶體)中的值是不確定的,需要從主存中讀取;
Synchronized則是鎖定當前變數,只有當前線程可以訪問該變數,其他線程被阻塞住。
(1)volatile僅能使用在變數級別;synchronized則可以使用在變數、方法和類級別的。
(2)volatile僅能實現變數的修改可見性,並不能保證原子性;synchronized則可以保證變數的修改可見性和原子性。
(3)volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
(4)volatile標記的變數不會被編譯器優化;synchronized標記的變數可以被編譯器優化。
7.4什麼是線程池,如何使用
線程池就是事先將多個線程對象放到一個容器中,當使用的時候就不用new線程而是直接去池中拿線程即可,節省了開闢子線程的時間,提高了代碼的執行效率。
在JDK的java.util.concurrent.Executors中提供了生成多種線程池的靜態方法:
ExecutorService newCanchedThreadPool = Excutors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Excutors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
然後調用他們的execute方法即可。
7.5線程池的理解
說一下線程池如何用、線程池的好處、線程池的啟動策略
合理利用線程池能夠帶來三個好處:
第一:降低資源消耗。通過重覆利用已創建的線程降低線程創建和銷毀造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配、調優和監控。
7.6線程池的啟動策略
(1)線程池剛創建時,裡面沒有一個線程。任務隊列是作為參數傳進來的。不過,就算隊列裡面有任務,線程池也不會馬上執行它們。
(2)當調用execute()方法添加一個任務時,線程池會做如下判斷:
a.如果正在執行的線程數量小於corePoolSize,那麼馬上創建線程運行這個任務;
b.如果正在執行的線程數量大於或等於corePoolSize,那麼將這個任務放入隊列;
c.如果這時候隊列滿了,而且正在執行的線程數量小於maximumPoolSize,那麼還是要創建線程運行這個任務;
d.如果隊列滿了,而且正在運行的線程數量大於或等於maximumPoolSize,那麼線程池會拋出異常,告訴調用者“不能再接受任務了”。
(3)當一個線程完成任務時,它會從隊列中取下一個任務來執行。
(4)當一個線程無事可做,超過一定的時間(keyAliveTime)時,線程池會判斷,如果當前運行的線程數大於corePoolSize,那麼這個線程就被停掉。所以線程池的所有任務完成後,它最終會收縮到coorPoolSize的大小。
7.7如何控制某個方法允許併發訪問線程的大小?
可以使用Semaphore控制,就是信號,初始化n個信號,線上程中,運行semaphore.acquire()申請請求,這樣最多只能有n個線程併發訪問,多餘n個線程時就排隊等待。線程完成後釋放信號,這樣新的線程就可以使用了。
8.Java中的反射
8.1Java中反射的理解
Java中的反射首先是能夠獲取到Java中要反射類的位元組碼,獲取位元組碼有三種方法,1.Class.forName(className)2.類名.Class3.this.getClass()。然後將位元組碼中的方法、變數、構造函數等映射成相應的Method、Filed、Contructor等類,這些類提供了豐富的方法可以被使用。
8.2動靜態代理的區別,什麼場景使用?
靜態代理通常只代理一個類,動態代理是代理一個介面下的多個實現類。
靜態代理事先知道要代理的是什麼,而動態代理不知道要代理什麼東西,只有在運行時才知道。
動態代理是顯示JDK里的InvocationHandler介面的invoke方法,但註意的是代理的是介面,也就是業務類必須要實現介面,通過Proxy里的newProxyInstance得到代理對象。
還有一種動態代理CGLIB,代理的是類,不需要業務類繼承介面,通過派生的子類來實現代理。通過在運行時,動態修改位元組碼達到修改類的目的。
AOP編程就是基於動態代理實現的,比如著名的Spring框架、Hibernate框架等等都是動態代理的使用例子。
9.Java中的回收機制
9.1.Java垃圾回收機制和常見演算法
Sun公司只定義了垃圾回收機制規則而不局限於其實現演算法,因此不同廠商生產的虛擬機採用的演算法也不盡相同。
GC(Garbage Collector)在回收對象前首先必鬚髮現那些無用的對象,如何去發現定位這些無用的對象?通常的搜素演算法如下:
(1)引用計數器演算法(廢棄)
引用計數器演算法是給每個對象設置一個計數器,當有地方引用這個對象的時候,計數器+1,當引用失效的時候,計算器-1,當計數器為0的時候,JVM就認為對象不再被使用,是“垃圾”了。
引用計數器實現簡單,效率高;但是不能解決迴圈引用問題(A對象引用B對象,B對象引用A對象,但是A,B對象已不再被任何其他對象引用),同時每次計數器的增加和減少都帶來了很多額外的開銷,所以在JDK1.1之後,這個演算法就不再使用了。
(2)根搜索演算法(使用)
根搜素演算法是通過一些“GC Roots”對象作為起點,從這些節點開始往下搜索,搜索通過的路徑成為引用鏈(Reference Chain),當一個對象沒有被GC Roots的引用鏈連接的時候,說明這個對象是不可用的。
GC Roots對象包括:
a.虛擬機棧(棧幀中的本地變數表)中的引用的對象。
b.方法區域中的類靜態屬性引用的對象。
c.方法區域中常量引用的對象。
d.本地方法棧中JNI(Native方法)的引用的對象。
通過上面的演算法搜索到無用對象之後,就是回收過程,回收演算法如下:
(1)標記-清除演算法(Mark-Sweep)(DVM使用的演算法)
標記-清除演算法包括連個階段:“標記”和“清除”。在標記階段,確定所有要回收的對象,並做標記。清除階段緊隨標記階段,將標記階段確定不可用的對象清除。標記-清除演算法是基礎的收集演算法,標記和清除階段的效率不高,而且清楚後回產生大量的不連續空間,這樣當程式需要分配大記憶體對象時,可能無法找到足夠的連續空間。
(2)複製演算法(Copying)
複製演算法是把記憶體分成大小相等的兩塊,每次使用其中一塊,當垃圾回收的時候,把存活的對象複製到另一塊上,然後把這塊記憶體整個清理掉。複製演算法實現簡單,運行效率高,但是由於每次只能使用其中的一半,造成記憶體的利用率不高。現在的JVM用複製方法收集新生代,由於新生代中大部分對象(98%)都是朝生夕死的,所以兩塊記憶體的比例不是1:1(大概是8:1)。
(3)標記-整理演算法(Mark-Compact)
標記-整理演算法和標記-清除演算法一樣,但是標記-整理演算法不是把存活對象複製到另一塊記憶體,而是把存活對象往記憶體的一端移動,然後直接回收邊界以外的記憶體。標記-整理演算法提高了記憶體的利用率,並且它適合在收集對象存活時間較長的老年代。
(4)分代收集(Generational Collection)
分代收集是根據對象的存活時間把記憶體分為新生代和老生代,根據各個代對象的存活特點,每個代採用不同的垃圾回收演算法。新生代採用複製演算法,老生代採用標記-整理演算法。垃圾演算法的實現涉及大量的程式細節,而且不同的虛擬機平臺實現的方法也各不相同。
9.2.JVM的記憶體結構和記憶體分配
(1)java記憶體模型
Java虛擬機將其管轄的記憶體大致分三個邏輯部分:方法區(Method Area)、Java棧和Java堆。
a.方法區是靜態分配的,編譯器將變數綁定在某個存儲位置上,而且這些綁定不會再運行時改變。常數池,源代碼中的命名常量、String常量和static變數保存在方法區。
b.Java Stack是一個邏輯概念,特點是後進先出。一個棧的空間可能是連續的,也可能是不連續的。最典型的Stack應用是方法的調用,Java虛擬機每調用一次方法就創建一個方法幀(frame),退出該方法則對應的方法幀被彈出(pop)。棧中存儲的數據也是運行時確定的。
c.Java堆分配(heap allocation)意味著以隨意的順序,在運行時進行存儲空間分配和回收的記憶體管理模型。堆中存儲的數據常常是大小、數量和生命期在編譯時無法確定的。Java對象的記憶體總是在heap中分配。
(2)Java記憶體分配
a.基礎數據類型直接在棧空間分配。
b.方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收。
c.引用數據類型,需要用new來創建,即在棧空間分配一個地址空間,又在堆空間分配對象的類變數。
d.方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完後從棧空間回收。
e.局部變數new出來時,在棧空間和堆空間中分配空間,當局部變數生命周期結束後,棧空間立即被回收,堆空間區域等待GC回收。
f.方法調用時傳入的實際參數,現在棧空間分配,在方法調用完成後從棧空間釋放。
g.字元串常量在DATA區域分配,this在堆空間分配。
h.數組即在棧空間分配數組名稱,又在堆空間分配數組實際的大小。
9.3.Java中引用類型都有哪些?
Java中對象的引用分為四種級別,這四種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。
(1)強引用
如果一個對象被人擁有強引用,那麼垃圾回收器絕不會回收它。當記憶體空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的對象阿裡解決記憶體不足問題。
Java的對象是位於heap中的,heap中對象有強可及對象、軟可及對象、弱可及對象、虛可及對象和不可到達對象。應用的強弱順序是強、軟、弱和虛。對於對象是屬於哪種可及的對象,由他的最強的引用決定。
String abc = new String(“abc”);//強引用,abc為強可及
SoftReference<String> softRef = new SoftReference<String>(abc);//軟引用
WeakReference<String> weakRef = new WeakReference<String>(abc);//弱引用
abc=null;//abc軟可及
softRef.clear();//abc變成弱可及
(2)軟引用
如果一個對象只具有軟引用,那麼如果記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,就會回收這些對象的記憶體。只要垃圾回收器沒有回收它,該對象就可以被程式使用。軟引用可用來實現記憶體敏感的高速緩存。
軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
軟引用主要用於記憶體敏感的高速緩存。在jvm報告記憶體不足之前會清除所有的軟引用,這樣以來gc就有可能手機軟可及的對象,可能解決記憶體吃緊問題,避免記憶體溢出。什麼時候會被收集取決於gc的演算法和gc運行時可用記憶體的大小。當gc決定要收集軟引用時執行步驟如下:(以上面的softRef為例)
a.首先將softRef的referent(abc)設置為null,不再引用heap中的new String(“abc”)對象。
b.將heap中的new String(“abc”)對象設置為可結束的(finalizable)。
c.當heap中的new String(“abc”)對象的finalize()方法被運行而且該對象占用的記憶體被釋放,softRef被添加到它的ReferenceQueue(如果有的話)中。
註意:對ReferenceQueue軟引用和弱引用可有可無,但虛引用必須有。
被Soft Reference指到的對象,即使沒有任何Direct Reference,也不會被清除。一直要到JVM記憶體不足且沒有Direct Reference時才會清除,SoftReference是用來設計objct-cache之用的。如此一來SoftReference不但可以把對象cache起來,也不會造成記憶體不足的錯誤(OutOfMemoryError)。
(3)弱引用
如果一個對象只具有弱引用,那該類就是可有可無的對象,因為只要該對象被gc掃描到隨時都會把它幹掉。
弱引用和軟引用的區別:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的對象,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於垃圾回收器是一個優先順序很低的線程,因此不一定會很快發現只具有弱引用的對象。
弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象唄垃圾回收器回收,Java虛擬機就會把這個弱引用加到與之關聯的引用隊列中。
(4)虛引用
“虛引用”與其他集中引用不同,虛引用並不決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
虛引用與軟引用和弱引用的區別:虛引用必須和引用隊列(ReferneceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還是虛引用,就會在回收對象的記憶體之前,把這個虛引用假如到與之關聯的引用隊列中。程式可以通過判斷引用隊列中是否加入了虛引用。來瞭解被引用的對象是否要被垃圾回收。程式如果發現某個虛引用已經被加入到引用隊列,那麼就可以在引用的對象的記憶體被回收之前採用必要的行動。
建立虛引用之後通過get方法返回結果始終為null,通過源代碼會發現,虛引用通常會把引用的對象寫進referent,只是get方法返回結果為null。和gc交互的過程:a.不把referent設置為null,直接把heap中的new String(“abc”)對象設置為可結束的(finalizable)。b.與軟引用和弱引用不同,先把PhantomReference對象添加到它的ReferencQueue中,然後在釋放虛可及的對象。
10.Java的類載入器
10.1.Java的類載入器的種類都有哪些?
(1)根類載入器(Bootstrap)---C++寫的,看不到源碼
(2)擴展類載入器(Extension)---載入位置:jre\lib\ext中
(3)系統(應用)類載入器(System\App)---載入位置:classpath中
(4)自定義載入器(必須繼承ClassLoader)
10.2.類什麼時候被初始化?
(1)創建類的實例,也就是new一個對象。
(2)訪問某個類或介面的靜態變數,或者對該靜態變數賦值。
(3)調用類的靜態方法。
(4)反射。
(5)初始化一個類的子類。
(6)JVM啟動時標明的啟動類,即文件名和類名相同的那個類。
只有這6種情況才會導致類的初始化。
10.3.類的初始化步驟
(1)如果這個類還沒有被載入和鏈接,那先進行載入和鏈接。
(2)假如這個類存在直接父類,並且這個類還沒有被初始化(註意:在一個類載入器中,類智能初始化一次),那就初始化直接的父類(不適用於介面)。
(3)假如類中存在初始化語句(如static變數和static塊),那就依次執行這些初始化語句。