欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java面試必備八股文整理

 更新時(shí)間:2023年03月30日 09:41:45   作者:ztono  
這篇文章主要介紹了Java面試必備八股文整理,小伙伴們出去面試的時(shí)候會(huì)被問到很多java專業(yè)性的知識(shí),那么八股文就是為此而出現(xiàn)的,需要的朋友可以參考下

JAVA基礎(chǔ)八股文

Switch能支持哪些類型?

jdk5之前,switch能夠作用在byte,short,char,int(實(shí)際上都是提升為int)等四個(gè)基本類型,jdk5引入了enum(也是int),jdk7引入了字符串(實(shí)際上是調(diào)用了string的hashcode,因此本質(zhì)上還是int),但是不能使用long,因?yàn)閟witch 對(duì)應(yīng)的 JVM 字節(jié)碼 lookupswitch、tableswitch 指令只支持 int 類型,而long沒法轉(zhuǎn)換為int。

內(nèi)部類有哪幾種?有什么優(yōu)點(diǎn)?

成員內(nèi)部類,局部?jī)?nèi)部類,匿名內(nèi)部類和靜態(tài)內(nèi)部類。

  1. 靜態(tài)內(nèi)部類。 類內(nèi)部的靜態(tài)類,可以訪問外部類的所有靜態(tài)變量,可以通過new 外部類.靜態(tài)內(nèi)部類()來創(chuàng)建。
  2. 成員內(nèi)部類。 類內(nèi)部的,成員位置上的非靜態(tài)類。可以訪問外部類所有變量和方法。創(chuàng)建方式為外部類實(shí)例.new 內(nèi)部類()。
  3. 局部?jī)?nèi)部類。定義在方法內(nèi)的內(nèi)部類,就是局部?jī)?nèi)部類。定義在實(shí)例方法中的局部類可以訪問外部類所有的變量和方法,定義在靜態(tài)方法中的局部類只能訪問外部類的靜態(tài)變量和方法。在對(duì)應(yīng)方法中new 內(nèi)部類()來創(chuàng)建。
  4. 匿名內(nèi)部類。沒有名字的內(nèi)部類。其必須繼承一個(gè)抽象類或者實(shí)現(xiàn)一個(gè)接口;不能定義任何靜態(tài)成員和靜態(tài)方法;匿名內(nèi)部類不能訪問外部類未加final修飾的變量(注意:JDK1.8即使沒有用final修飾也可以訪問);匿名內(nèi)部類不能是抽象的。

內(nèi)部類的優(yōu)點(diǎn):

  • 內(nèi)部類可以訪問外部類的內(nèi)容(即使是private)。
  • 內(nèi)部類可以模擬出一種“多重繼承”。
  • 匿名內(nèi)部類可以方便的定義回調(diào)。

為什么匿名內(nèi)部類不能訪問外部類未加final的變量

匿名內(nèi)部類在生成字節(jié)碼階段,會(huì)把涉及的變量作為構(gòu)造函數(shù)的參數(shù),這樣使得在匿名內(nèi)部類中修改的數(shù)據(jù)無(wú)法傳遞到外部,因此不能是final。

String,StringBuffer,StringBuilder

  1. String底層是final char value[]。因此其不可變。
  2. StringBuffer和StringBuilder都是繼承自AbstractStringBuilder,其底層使用的是char value[](無(wú)final),所以可變。StringBuffer是線程安全的(使用synchronized保證),StringBuilder不是。
  3. 每次對(duì)String類型進(jìn)行改變時(shí),會(huì)生成一個(gè)新的String對(duì)象,然后把指針指向新的String,但是其他兩個(gè)都是操作自身。

Java中的異常處理簡(jiǎn)介

在這里插入圖片描述

所有的異常類都有個(gè)共同祖先 Throwable。
Error一般是指程序無(wú)法處理的錯(cuò)誤或者不應(yīng)該處理的錯(cuò)誤。
Exception是程序可以處理的異常。
Exception分為runtime exception和非運(yùn)行時(shí)異常;或者分為受檢異常和非受檢異常。受檢異常是指編譯器必須處理的異常

JAVA反射獲取類的三種方式

  1. 調(diào)用對(duì)象的.getClass()方法
  2. 調(diào)用某個(gè)類的.class屬性
  3. 使用Class.forName+類的全路徑

JAVA的四種標(biāo)準(zhǔn)元注解

  1. @Target 修飾對(duì)象范圍,例如types, packages
  2. @Retention 定義保留的時(shí)間長(zhǎng)短,例如SOURCE,RUNTIME
  3. @Document描述javadoc
  4. @Inherited闡述某個(gè)被標(biāo)注的類型是被繼承的。作用:如果一個(gè)類用上了@Inherited修飾的注解,那么其子類也會(huì)繼承這個(gè)注解。但是如果接口用上個(gè)@Inherited修飾的注解,其實(shí)現(xiàn)類不會(huì)繼承這個(gè)注解;或者父類的方法用了@Inherited修飾的注解,子類也不會(huì)繼承這個(gè)注解。這樣操作后,子類可以獲取父類的注解,字段等信息。

collection和map的關(guān)系

Java容器分為collection和map兩大類。

collection里面的子類

主要包括list*包括ArrayList,LinkedList,Vector)和set(主要有HashSet,LinkedSet和TreeSet)

java 容器的快速失?。╢ast-fail)機(jī)制

這是通過modCount實(shí)現(xiàn)的。遍歷器在遍歷訪問集合中的內(nèi)容時(shí),會(huì)維護(hù)modCount和expectedModCount。當(dāng)modCount不等于expoectedModCount時(shí),就會(huì)拋出異常。迭代器在調(diào)用next()、remove()方法時(shí)都是調(diào)用checkForComodification()方法來進(jìn)行檢測(cè)modCount是否等于expectedModCount。具體來說:在創(chuàng)建迭代器的時(shí)候會(huì)把對(duì)象的modCount的值傳遞給迭代器的expectedModCount,如果創(chuàng)建多個(gè)迭代器對(duì)一個(gè)集合對(duì)象進(jìn)行修改的話,那么就會(huì)有一個(gè)modCount和多個(gè)expectedModCount,且modCount的值之間也會(huì)不一樣,這就導(dǎo)致了moCount和expectedModCount的值不一致,從而產(chǎn)生異常。
那么為什么在下列代碼中即使單線程,也會(huì)出現(xiàn)快速失敗呢:

for( Integer i:list){
	list.remove(i);
}

因?yàn)楫?dāng)使用for each時(shí),會(huì)生成一個(gè)iterator來來遍歷該list,同時(shí)這個(gè)list正在被iterator.remove修改。

解決辦法:

1.在遍歷過程中,所有改變modCount值的地方都加上synchronized。

2.使用CopyOnWriteArrayList來替換ArrayList。CopyOnWriteArrayList是在有寫操作的時(shí)候會(huì)copy一份數(shù)據(jù),然后寫完再設(shè)置成新的數(shù)據(jù)。

 ArrayList擴(kuò)容機(jī)制

簡(jiǎn)單來說就是當(dāng)前容量*1.5+1

Vector和ArrayList

Vector與ArrayList一樣,也是通過數(shù)組實(shí)現(xiàn)的,不同的是它支持線程的同步,即某一時(shí)刻只有一個(gè)線程能夠?qū)慥ector,避免多線程同時(shí)寫而引起的不一致性,但實(shí)現(xiàn)同步需要很高的花費(fèi),因此,訪問它比訪問ArrayList慢。
###LinkedList
LinkedList是用雙向鏈表結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)的,并且通過first和last引用分別指向鏈表的第一個(gè)和最后一個(gè)元素,很適合數(shù)據(jù)的動(dòng)態(tài)插入和刪除,隨機(jī)訪問和遍歷速度比較慢。另外,他還提供了List接口中沒有定義的方法,專門用于操作表頭和表尾元素,可以當(dāng)作堆棧、隊(duì)列和雙向隊(duì)列使用。

HashSet判斷元素相等性的方法

HashSet首先判斷兩個(gè)元素的哈希值,如果哈希值一樣,接著會(huì)比較equals方法 如果 equls結(jié)果為true ,HashSet就視為同一個(gè)元素。如果equals 為false就不是同一個(gè)元素。

什么時(shí)候需要復(fù)寫hashcode()和compartTo方法

HashMap中實(shí)現(xiàn)了一個(gè)Entry[]數(shù)組,數(shù)組的每個(gè)item是一個(gè)單項(xiàng)鏈表的結(jié)構(gòu),當(dāng)我們put(key, value)的時(shí)候,HashMap首先會(huì)newItem.key.hashCode()作為該newItem在Entry[]中存儲(chǔ)的下標(biāo),要是對(duì)應(yīng)的下標(biāo)的位置上沒有任何item,則直接存儲(chǔ)上去,要是已經(jīng)有oldItem存儲(chǔ)在了上面,那就會(huì)判斷oldItem.key.equals(newItem.key),那么要是我們把上面的Person作為key進(jìn)行存儲(chǔ)的時(shí)候,重寫了equals()方法,但沒重寫hashCode()方法,當(dāng)我們?nèi)ut()的時(shí)候,首先會(huì)通過hashCode() 計(jì)算下標(biāo),由于沒有重寫hashCode(),那么實(shí)質(zhì)是完整的Object的hashCode(),會(huì)受到Object多個(gè)屬性的影響,本來equals的兩個(gè)Person對(duì)象,反而得到了兩個(gè)不同的下標(biāo)。
同樣的,HashMap在get(key)的過程中,也是首先調(diào)用hashCode()計(jì)算item的下標(biāo),然后在對(duì)應(yīng)下標(biāo)的地方找,要是為null,就返回null,要是 != null,會(huì)去調(diào)用equals()方法,比較key是否一致,只有當(dāng)key一致的時(shí)候,才會(huì)返回value,要是我們沒有重寫hashCode()方法,本來有的item,反而會(huì)找不到,返回null結(jié)果。
所以,要是你重寫了equals()方法,而你的對(duì)象可能會(huì)放入到散列(HashMap,HashTable或HashSet等)中,那么還必須重寫hashCode(), 如果你的對(duì)象有可能放到有序隊(duì)列(實(shí)現(xiàn)了Comparable)中,那么還需要重寫compareTo()的方法。

TreeSet是什么

TreeSet()是使用二叉樹的原理對(duì)新add()的對(duì)象按照指定的順序排序(升序、降序),每增加一個(gè)對(duì)象都會(huì)進(jìn)行排序,將對(duì)象插入的二叉樹指定的位置。Integer和String對(duì)象都可以進(jìn)行默認(rèn)的TreeSet排序,而自定義類的對(duì)象是不可以的,自己定義的類必須實(shí)現(xiàn)Comparable接口,并且覆寫相應(yīng)的compareTo()函數(shù),才可以正常使用。在覆寫compare()函數(shù)時(shí),要返回相應(yīng)的值才能使TreeSet按照一定的規(guī)則來排序。比較此對(duì)象與指定對(duì)象的順序。如果該對(duì)象小于、等于或大于指定對(duì)象,則分別返回負(fù)整數(shù)、零或正整數(shù)。

TreeMap

與HashSet和HashMap關(guān)系類似,TreeSet是基于TreeMap實(shí)現(xiàn)的。他采用紅黑樹來保存map的每一個(gè)Entry。

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

HashMap根據(jù)鍵的hashCode值存儲(chǔ)數(shù)據(jù),大多數(shù)情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。 HashMap最多只允許一條記錄的鍵為null,允許多條記錄的值為null。HashMap非線程安全,即任一時(shí)刻可以有多個(gè)線程同時(shí)寫HashMap,可能會(huì)導(dǎo)致數(shù)據(jù)的不一致。如果需要滿足線程安全,可以用 Collections的synchronizedMap方法使HashMap具有線程安全的能力,或者使用ConcurrentHashMap。我們用下面這張圖來介紹 HashMap 的結(jié)構(gòu)。

JDK7:

在這里插入圖片描述

大方向上,HashMap 里面是一個(gè)數(shù)組,然后數(shù)組中每個(gè)元素是一個(gè)單向鏈表。上圖中,每個(gè)綠色的實(shí)體是嵌套類 Entry 的實(shí)例,Entry 包含四個(gè)屬性:key, value, hash 值和用于單向鏈表的 next。
1. capacity:當(dāng)前數(shù)組容量,始終保持 2^n,可以擴(kuò)容,擴(kuò)容后數(shù)組大小為當(dāng)前的 2 倍。
2. loadFactor:負(fù)載因子,默認(rèn)為 0.75(負(fù)載因子設(shè)置為0.75是為了在大量空間浪費(fèi)和大量hash沖突之間取得一個(gè)平衡)。計(jì)算HashMap的實(shí)時(shí)裝載因子的方法為:size/capacity
3. threshold:擴(kuò)容的閾值,等于 capacity * loadFactor
4. size:size表示HashMap中存放KV的數(shù)量(為鏈表和樹中的KV的總和)

JDK8:

Java8 對(duì) HashMap 進(jìn)行了一些修改,最大的不同就是利用了紅黑樹,所以其由 數(shù)組+鏈表+紅黑樹 組成。 根據(jù) Java7 HashMap 的介紹,我們知道,查找的時(shí)候,根據(jù) hash 值我們能夠快速定位到數(shù)組的具體下標(biāo),但是之后的話,需要順著鏈表一個(gè)個(gè)比較下去才能找到我們需要的,時(shí)間復(fù)雜度取決于鏈表的長(zhǎng)度,為 O(n)。為了降低這部分的開銷,在 Java8 中,當(dāng)鏈表中的元素超過了 8 個(gè)以后,會(huì)將鏈表轉(zhuǎn)換為紅黑樹 (將鏈表轉(zhuǎn)換成紅黑樹前會(huì)判斷,如果當(dāng)前數(shù)組的長(zhǎng)度小于 64,那么會(huì)選擇先進(jìn)行數(shù)組擴(kuò)容,而不是轉(zhuǎn)換為紅黑樹),在這些位置進(jìn)行查找的時(shí)候可以降低時(shí)間復(fù)雜度為 O(logN)。

在這里插入圖片描述

HashMap是線程不安全的,其主要體現(xiàn):

1.在jdk1.7中,在多線程環(huán)境下,擴(kuò)容時(shí)會(huì)造成環(huán)形鏈或數(shù)據(jù)覆蓋。

2.在jdk1.8中,在多線程環(huán)境下,會(huì)發(fā)生數(shù)據(jù)覆蓋的情況。

HashMap擴(kuò)容的時(shí)候?yàn)槭裁词?的n次冪?

數(shù)組下標(biāo)的計(jì)算方法是(n - 1) & hash,取余(%)操作中如果除數(shù)是2的冪次則等價(jià)于與其除數(shù)減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二進(jìn)制位操作 &,相對(duì)于%能夠提高運(yùn)算效率,這就解釋了 HashMap 的長(zhǎng)度為什么是2的冪次方。
 

HashMap的put方法:

1.根據(jù)key通過哈希算法與與運(yùn)算得出數(shù)組下標(biāo)
2.如果數(shù)組下標(biāo)元素為空,則將key和value封裝為Entry對(duì)象(JDK1.7是Entry對(duì)象,JDK1.8是Node對(duì)象)并放入該位置。
3.如果數(shù)組下標(biāo)位置元素不為空,則要分情況
? (i)如果是在JDK1.7,則首先會(huì)判斷是否需要擴(kuò)容,如果要擴(kuò)容就進(jìn)行擴(kuò)容,如果不需要擴(kuò)容就生成Entry對(duì)象,并使用頭插法添加到當(dāng)前鏈表中。
(ii)如果是在JDK1.8中,則會(huì)先判斷當(dāng)前位置上的TreeNode類型,看是紅黑樹還是鏈表Node
? (a)如果是紅黑樹TreeNode,則將key和value封裝為一個(gè)紅黑樹節(jié)點(diǎn)并添加到紅黑樹中去,在這個(gè)過程中會(huì)判斷紅黑樹中是否存在當(dāng)前key,如果存在則更新value。
? (b)如果此位置上的Node對(duì)象是鏈表節(jié)點(diǎn),則將key和value封裝為一個(gè)Node并通過尾插法插入到鏈表的最后位置去,因?yàn)槭俏膊宸ǎ孕枰闅v鏈表,在遍歷過程中會(huì)判斷是否存在當(dāng)前key,如果存在則更新其value,當(dāng)遍歷完鏈表后,將新的Node插入到鏈表中,插入到鏈表后,會(huì)看當(dāng)前鏈表的節(jié)點(diǎn)個(gè)數(shù),如果大于8,則會(huì)將鏈表轉(zhuǎn)為紅黑樹
? (c)將key和value封裝為Node插入到鏈表或紅黑樹后,在判斷是否需要擴(kuò)容,如果需要擴(kuò)容,就結(jié)束put方法。

HashMap源碼中在計(jì)算hash值的時(shí)候?yàn)槭裁匆乙?6位?

當(dāng)數(shù)組的長(zhǎng)度很短時(shí),只有低位數(shù)的hashcode值能參與運(yùn)算。而讓高16位參與運(yùn)算可以更好的均勻散列,減少碰撞,進(jìn)一步降低hash沖突的幾率。并且使得高16位和低16位的信息都被保留了。
而在這里采用異或運(yùn)算而不采用& ,| 運(yùn)算的原因是 異或運(yùn)算能更好的保留各部分的特征,如果采用&運(yùn)算計(jì)算出來的值的二進(jìn)制會(huì)向1靠攏,采用|運(yùn)算計(jì)算出來的值的二進(jìn)制會(huì)向0靠攏。
計(jì)算方式:hash實(shí)際上是object.hashCode() ^ object.hashCode() >> 16。數(shù)組下標(biāo)index = (table.length-1)&hash。table.length-1的目的是把結(jié)果限制在0-table.length-1里面,因?yàn)檫@個(gè)值通常不大,所以會(huì)丟失高位信息(因?yàn)槭?amp;操作)。

ConcurrentHashMap

Segment段:
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因?yàn)樗С植l(fā)操作,所以要復(fù)雜一些。整個(gè) ConcurrentHashMap 由一個(gè)個(gè) Segment 組成,Segment 代表”部分“或”一段“的意思,所以很多地方都會(huì)將其描述為分段鎖。注意,行文中,我很多地方用了“槽”來代表一個(gè) segment。
線程安全(Segment 繼承 ReentrantLock 加鎖)
簡(jiǎn)單理解就是,ConcurrentHashMap 是一個(gè) Segment 數(shù)組,Segment 通過繼承 ReentrantLock 來進(jìn)行加鎖,所以每次需要加鎖的操作鎖住的是一個(gè) segment,這樣只要保證每個(gè) Segment 是線程安全的,也就實(shí)現(xiàn)了全局的線程安全。
concurrencyLevel: 并行級(jí)別、并發(fā)數(shù)、Segment 數(shù)。默認(rèn)是 16,也就是說 ConcurrentHashMap 有 16 個(gè) Segments,所以理論上,這個(gè)時(shí)候,最多可以同時(shí)支持 16 個(gè)線程并發(fā)寫,只要它們的操作分別分布在不同的 Segment 上。這個(gè)值可以在初始化的時(shí)候設(shè)置為其他值,但是一旦初始化以后,它是不可以擴(kuò)容的。再具體到每個(gè) Segment 內(nèi)部,其實(shí)每個(gè) Segment 很像之前介紹的 HashMap,不過它要保證線程安全,所以處理起來要麻煩些。
和HashMap一樣,ConcurrentHashMap的JDK7和JDK8實(shí)現(xiàn)也有區(qū)別。如下圖所示:

在這里插入圖片描述

在這里插入圖片描述

可以看到Java8中的ConcurrentHashMap和Java8的HashMap很像,那么他是怎么保證線程安全的呢。java8實(shí)現(xiàn)了粒度更細(xì)的加鎖,去掉了segment數(shù)組,直接使用synchronized鎖住hash后得到的數(shù)組下標(biāo)位置中的第一個(gè)元素 ,如下圖,這樣加鎖比segment加鎖能支持更高的并發(fā)量。

HashTable

Hashtable是遺留類,很多映射的常用功能與HashMap類似,不同的是它承自Dictionary類,并且是線程安全的,任一時(shí)間只有一個(gè)線程能寫Hashtable,并發(fā)性不如ConcurrentHashMap,因?yàn)镃oncurrentHashMap引入了分段鎖。Hashtable不建議在新代碼中使用,不需要線程安全的場(chǎng)合可以用HashMap替換,需要線程安全的場(chǎng)合可以用ConcurrentHashMap替換。

各種集合實(shí)現(xiàn)的擴(kuò)容倍數(shù)

名稱倍數(shù)備注
HashMap2倍jdk1.7中,擴(kuò)容后重新計(jì)算hash,1.8中,根據(jù),是否仍然在同一個(gè)桶中判斷,即e.hash()& oldCap,oldCap為舊容量,當(dāng)你給定了初始容量值時(shí),會(huì)將其擴(kuò)充到2的冪
HashTable2倍+1無(wú)

哪些Map支持key為null

HashMap 、LinkedHashMap 的 key 和 value 都允許為 null。
而ConcurrentHashMap、ConcurrentSkipListMap、Hashtable、TreeMap 的 key 不允許為 null。

select和epoll的區(qū)別

當(dāng)需要讀兩個(gè)以上的I/O的時(shí)候,如果使用阻塞式的I/O,那么可能長(zhǎng)時(shí)間的阻塞在一個(gè)描述符上面,另
外的描述符雖然有數(shù)據(jù)但是不能讀出來,這樣實(shí)時(shí)性不能滿足要求,大概的解決方案有以下幾種:

1.使用多進(jìn)程或者多線程,但是這種方法會(huì)造成程序的復(fù)雜,而且對(duì)與進(jìn)程與線程的創(chuàng)建維護(hù)也需要
很多的開銷(Apache服務(wù)器是用的子進(jìn)程的方式,優(yōu)點(diǎn)可以隔離用戶);

2.用一個(gè)進(jìn)程,但是使用非阻塞的I/O讀取數(shù)據(jù),當(dāng)一個(gè)I/O不可讀的時(shí)候立刻返回,檢查下一個(gè)是否
可讀,這種形式的循環(huán)為輪詢(polling),這種方法比較浪費(fèi)CPU時(shí)間,因?yàn)榇蠖鄶?shù)時(shí)間是不可
讀,但是仍花費(fèi)時(shí)間不斷反復(fù)執(zhí)行read系統(tǒng)調(diào)用;

3.異步I/O,當(dāng)一個(gè)描述符準(zhǔn)備好的時(shí)候用一個(gè)信號(hào)告訴進(jìn)程,但是由于信號(hào)個(gè)數(shù)有限,多個(gè)描述符
時(shí)不適用;

4.一種較好的方式為I/O多路復(fù)用,先構(gòu)造一張有關(guān)描述符的列表(epoll中為隊(duì)列),然后調(diào)用一個(gè)
函數(shù),直到這些描述符中的一個(gè)準(zhǔn)備好時(shí)才返回,返回時(shí)告訴進(jìn)程哪些I/O就緒。select和epoll這
兩個(gè)機(jī)制都是多路I/O機(jī)制的解決方案,select為POSIX標(biāo)準(zhǔn)中的,而epoll為L(zhǎng)inux所特有的。

它們的區(qū)別主要有三點(diǎn):

  1. select的句柄數(shù)目受限,在linux/posix_types.h頭文件有這樣的聲明: #define __FD_SETSIZE1024 表示select最多同時(shí)監(jiān)聽1024個(gè)fd。而epoll沒有,它的限制是最大的打開文件句柄數(shù)目;
  2. epoll的最大好處是不會(huì)隨著FD的數(shù)目增長(zhǎng)而降低效率,在selec中采用輪詢處理,其中的數(shù)據(jù)結(jié)構(gòu)類似一個(gè)數(shù)組的數(shù)據(jù)結(jié)構(gòu),而epoll是維護(hù)一個(gè)隊(duì)列,直接看隊(duì)列是不是空就可以了。epoll只會(huì)對(duì)“活躍”的socket進(jìn)行操作—這是因?yàn)樵趦?nèi)核實(shí)現(xiàn)中epoll是根據(jù)每個(gè)fd上面的callback函數(shù)實(shí)現(xiàn)的。那么,只有”活躍”的socket才會(huì)主動(dòng)的去調(diào)用 callback函數(shù)(把這個(gè)句柄加入隊(duì)列),其他idle狀態(tài)句柄則不會(huì),在這點(diǎn)上,epoll實(shí)現(xiàn)了一個(gè)”偽”AIO。但是如果絕大部分的I/O都是“活躍的”,每個(gè)I/O端口使用率很高的話,epoll效率不一定比select高(可能是要維護(hù)隊(duì)列復(fù)雜);
  3. 使用mmap加速內(nèi)核與用戶空間的消息傳遞。無(wú)論是select,poll還是epoll都需要內(nèi)核把FD消息通知給用戶空間,如何避免不必要的內(nèi)存拷貝就很重要,在這點(diǎn)上,epoll是通過內(nèi)核于用戶空間mmap同一塊內(nèi)存實(shí)現(xiàn)的。

wait和sleep的共同點(diǎn)和區(qū)別

共同點(diǎn) :

他們都是在多線程的環(huán)境下,都可以在程序的調(diào)用處阻塞指定的毫秒數(shù),并返回。wait()和sleep()都可以通過interrupt()方法 打斷線程的暫停狀態(tài) ,從而使線程立刻拋出InterruptedException。
如果線程A希望立即結(jié)束線程B,則可以對(duì)線程B對(duì)應(yīng)的Thread實(shí)例調(diào)用interrupt方法。如果此刻線程B正在wait/sleep/join,則線程B會(huì)立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結(jié)束線程。
需要注意的是,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的。對(duì)某一線程調(diào)用 interrupt()時(shí),如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會(huì)拋出InterruptedException。但是,一旦該線程進(jìn)入到 wait()/sleep()/join()后,就會(huì)立刻拋出InterruptedException 。

不同點(diǎn) :

1.每個(gè)對(duì)象都有一個(gè)鎖來控制同步訪問。Synchronized關(guān)鍵字可以和對(duì)象的鎖交互,來實(shí)現(xiàn)線程的同步。
sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
2.wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用
3.sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常
4.sleep是線程類(Thread)的方法,導(dǎo)致此線程暫停執(zhí)行指定時(shí)間,給執(zhí)行機(jī)會(huì)給其他線程,但是監(jiān)控狀態(tài)依然保持,到時(shí)后會(huì)自動(dòng)恢復(fù)。調(diào)用sleep不會(huì)釋放對(duì)象鎖。
5.wait是Object類的方法,對(duì)此對(duì)象調(diào)用wait方法導(dǎo)致本線程放棄對(duì)象鎖,進(jìn)入等待此對(duì)象的等待鎖定池,只有針對(duì)此對(duì)象發(fā)出notify方法(或notifyAll)后本線程才進(jìn)入對(duì)象鎖定池準(zhǔn)備獲得對(duì)象鎖進(jìn)入就緒狀態(tài)。

threadLocal簡(jiǎn)介

ThreadLocal為變量在每個(gè)線程都創(chuàng)建了一個(gè)副本。每個(gè)線程可以訪問自己內(nèi)部的副本變量。首先,在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個(gè)threadLocals就是用來存儲(chǔ)實(shí)際的變量副本的,鍵值為當(dāng)前ThreadLocal變量,value為變量副本(即T類型的變量)。
初始時(shí),在Thread里面,threadLocals為空,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法,就會(huì)對(duì)Thread類中的threadLocals進(jìn)行初始化,并且以當(dāng)前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。
然后在當(dāng)前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。

threadLocaMap為什么是弱引用呢?

每個(gè)Thread內(nèi)部都維護(hù)一個(gè)ThreadLocalMap字典數(shù)據(jù)結(jié)構(gòu),字典的Key值是ThreadLocal,那么當(dāng)某個(gè)ThreadLocal對(duì)象不再使用(沒有其它地方再引用)時(shí),每個(gè)已經(jīng)關(guān)聯(lián)了此ThreadLocal的線程怎么在其內(nèi)部的ThreadLocalMap里做清除此資源呢?JDK中的ThreadLocalMap又做了一次精彩的表演,它沒有繼承java.util.Map類,而是自己實(shí)現(xiàn)了一套專門用來定時(shí)清理無(wú)效資源的字典結(jié)構(gòu)。其內(nèi)部存儲(chǔ)實(shí)體結(jié)構(gòu)Entry<ThreadLocal, T>繼承自java.lan.ref.WeakReference,這樣當(dāng)ThreadLocal不再被引用時(shí),因?yàn)槿跻脵C(jī)制原因,當(dāng)jvm發(fā)現(xiàn)內(nèi)存不足時(shí),會(huì)自動(dòng)回收弱引用指向的實(shí)例內(nèi)存,即其線程內(nèi)部的ThreadLocalMap會(huì)釋放其對(duì)ThreadLocal的引用從而讓jvm回收ThreadLocal對(duì)象。這里是重點(diǎn)強(qiáng)調(diào)下,是回收對(duì)ThreadLocal對(duì)象,而非整個(gè)Entry,所以線程變量中的值T對(duì)象還是在內(nèi)存中存在的,所以內(nèi)存泄漏的問題還沒有完全解決。接著分析JDK的實(shí)現(xiàn),會(huì)發(fā)現(xiàn)在調(diào)用ThreadLocal.get()或者ThreadLocal.set(T)時(shí)都會(huì)定期執(zhí)行回收無(wú)效的Entry操作。

ThreadLocal是如何做到為每一個(gè)線程維護(hù)變量的副本的呢?

其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單:在ThreadLocal類中有一個(gè)static聲明的Map,用于存儲(chǔ)每一個(gè)線程的變量副本,Map中元素的鍵為線程對(duì)象,而值對(duì)應(yīng)線程的變量副本。

synchronized和volatile區(qū)別

1.volatile主要應(yīng)用在多個(gè)線程對(duì)實(shí)例變量更改的場(chǎng)合,刷新主內(nèi)存共享變量的值從而使得各個(gè)線程可以獲得最新的值,線程讀取變量的值需要從主存中讀??;synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問該變量,其他線程被阻塞住。另外,synchronized還會(huì)創(chuàng)建一個(gè)內(nèi)存屏障,內(nèi)存屏障指令保證了所有CPU操作結(jié)果都會(huì)直接刷到主存中(即釋放鎖前),從而保證了操作的內(nèi)存可見性,同時(shí)也使得先獲得這個(gè)鎖的線程的所有操作

2.volatile僅能使用在變量級(jí)別;synchronized則可以使用在變量、方法、和類級(jí)別的。 volatile不會(huì)造成線程的阻塞;synchronized可能會(huì)造成線程的阻塞,比如多個(gè)線程爭(zhēng)搶 synchronized鎖對(duì)象時(shí),會(huì)出現(xiàn)阻塞。

3.volatile僅能實(shí)現(xiàn)變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性,因?yàn)榫€程獲得鎖才能進(jìn)入臨界區(qū),從而保證臨界區(qū)中的所有語(yǔ)句全部得到 執(zhí)行。

4.volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化,可以禁止進(jìn)行指令重排;synchronized標(biāo)記的變量可以被編譯器優(yōu)化。

java8的新特性

  1. 接口的默認(rèn)實(shí)現(xiàn)
  2. lambda表達(dá)式
  3. 注意lambda表達(dá)式不能訪問接口的默認(rèn)方法。
  4. 函數(shù)式接口 常見的函數(shù)式接口有Predicate,F(xiàn)unction,Optional,Stream,F(xiàn)ilter,Sort,Map,Match,Count,Reduce等
  5. 方法與構(gòu)造函數(shù)引用
  6. Date的API
  7. annotation

java9的新特性

  1. 模塊系統(tǒng)
  2. java.net.http,提供了良好的http1和http2的支持
  3. jshell
  4. 不可變集合工廠方法
  5. 私有接口方法
  6. I/O流新特性(增加了新的方法來讀取和復(fù)制InputStream的內(nèi)容)
  7. 多版本兼容jar
  8. 統(tǒng)一JVM日志
  9. 默認(rèn)垃圾處理器從Parallel GC切換到了G1

compareTo接口規(guī)范

小于返回<0,等于返回0,大于返回大于0

為什么要用元空間來代替永久代

1、字符串存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出。
2、類及方法的信息等比較難確定其大小,因此對(duì)于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出。
3、永久代會(huì)為 GC 帶來不必要的復(fù)雜度,并且回收效率偏低。

Java I/O

1.堵塞IO模型。
最傳統(tǒng)的一種 IO 模型,即在讀寫數(shù)據(jù)過程中會(huì)發(fā)生阻塞現(xiàn)象。當(dāng)用戶線程發(fā)出 IO 請(qǐng)求之后,內(nèi)核會(huì)去查看數(shù)據(jù)是否就緒,如果沒有就緒就會(huì)等待數(shù)據(jù)就緒,而用戶線程就會(huì)處于阻塞狀態(tài),用戶線程交出 CPU。當(dāng)數(shù)據(jù)就緒之后,內(nèi)核會(huì)將數(shù)據(jù)拷貝到用戶線程,并返回結(jié)果給用戶線程,用戶線程才解除block 狀態(tài)。典型的阻塞 IO 模型的例子為:data = socket.read();如果數(shù)據(jù)沒有就緒,就會(huì)一直阻塞在 read 方法。

2.非堵塞IO模型。
當(dāng)用戶線程發(fā)起一個(gè) read 操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。如果結(jié)果是一個(gè)error 時(shí),它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是它可以再次發(fā)送 read 操作。一旦內(nèi)核中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶線程的請(qǐng)求,那么它馬上就將數(shù)據(jù)拷貝到了用戶線程,然后返回。所以事實(shí)上,在非阻塞 IO 模型中,用戶線程需要不斷地詢問內(nèi)核數(shù)據(jù)是否就緒,也就說非阻塞 IO不會(huì)交出 CPU,而會(huì)一直占用 CPU。但是對(duì)于非阻塞 IO 就有一個(gè)非常嚴(yán)重的問題,在 while 循環(huán)中需要不斷地去詢問內(nèi)核數(shù)據(jù)是否就緒,這樣會(huì)導(dǎo)致 CPU 占用率非常高,因此一般情況下很少使用 while 循環(huán)這種方式來讀取數(shù)據(jù)。

3.多路復(fù)用 IO 模型
多路復(fù)用 IO 模型是目前使用得比較多的模型。Java NIO 實(shí)際上就是多路復(fù)用 IO。在多路復(fù)用 IO模型中,會(huì)有一個(gè)線程不斷去輪詢多個(gè) socket 的狀態(tài),只有當(dāng) socket 真正有讀寫事件時(shí),才真正調(diào)用實(shí)際的 IO 讀寫操作。因?yàn)樵诙嗦窂?fù)用 IO 模型中,只需要使用一個(gè)線程就可以管理多個(gè)socket,系統(tǒng)不需要建立新的進(jìn)程或者線程,也不必維護(hù)這些線程和進(jìn)程,并且只有在真正有socket 讀寫事件進(jìn)行時(shí),才會(huì)使用 IO 資源,所以它大大減少了資源占用。在 Java NIO 中,是通過 selector.select()去查詢每個(gè)通道是否有到達(dá)事件,如果沒有事件,則一直阻塞在那里,因此這種方式會(huì)導(dǎo)致用戶線程的阻塞。多路復(fù)用 IO 模式,通過一個(gè)線程就可以管理多個(gè) socket,只有當(dāng)socket 真正有讀寫事件發(fā)生才會(huì)占用資源來進(jìn)行實(shí)際的讀寫操作。因此,多路復(fù)用 IO 比較適合連接數(shù)比較多的情況。
另外多路復(fù)用 IO 為何比非阻塞 IO 模型的效率高?因?yàn)樵诜亲枞?IO 中,不斷地詢問 socket 狀態(tài)時(shí)通過用戶線程去進(jìn)行的,而在多路復(fù)用 IO 中,輪詢每個(gè) socket 狀態(tài)是內(nèi)核在進(jìn)行的,這個(gè)效率要比用戶線程要高的多。
不過要注意的是,多路復(fù)用 IO 模型是通過輪詢的方式來檢測(cè)是否有事件到達(dá),并且對(duì)到達(dá)的事件逐一進(jìn)行響應(yīng)。因此對(duì)于多路復(fù)用 IO 模型來說,一旦事件響應(yīng)體很大,那么就會(huì)導(dǎo)致后續(xù)的事件遲遲得不到處理,并且會(huì)影響新的事件輪詢。
一般的,IO多路復(fù)用機(jī)制都依賴于一個(gè)事件多路分離器(Event Demultiplexer)。分離器對(duì)象可將來自事件源的IO事件分離出來,并分發(fā)到對(duì)應(yīng)的read/write事件處理器。

Reactor模型和Proactor模型

Reactor模型和Proactor模型都是用來處理IO復(fù)用的模型。Reactor采用同步IO,Proactor采用異步IO。
在Reactor模式中,事件分離器負(fù)責(zé)風(fēng)帶文件描述符或者socket為讀寫操作準(zhǔn)備就緒,并將就緒事件傳遞給對(duì)應(yīng)的處理器,最后由處理器負(fù)責(zé)完成實(shí)際的讀寫工作。
在Proactor模式中,處理器或者兼任處理器的事件分離器,只負(fù)責(zé)發(fā)起異步讀寫操作。IO是由系統(tǒng)完成的。
以讀操作為例,介紹兩者差異:
在reactor中實(shí)現(xiàn)讀:
step1: 注冊(cè)讀就緒事件和相應(yīng)的事件處理器。
step2:事件分離器等待事件。
step3:事件到來,激活分離器,分離器調(diào)用時(shí)間對(duì)應(yīng)的處理器。
step4:處理器完成讀操作,注冊(cè)新的事件,返還控制權(quán)。
在Proactor中實(shí)現(xiàn)讀:
step1:處理器發(fā)出異步讀請(qǐng)求,這時(shí)候處理器無(wú)視IO就緒事件,關(guān)注的是完成事件。
step2:事件分離器等待操作完成事件。
step3:在分離器等待過程中,操作系統(tǒng)完成讀操作,并將結(jié)果存入用戶自定義緩沖區(qū),通知時(shí)間分離器讀操作完成。
step4:事件分離器呼喚處理器。
step5:事件處理器處理用戶自定義緩沖區(qū)中的數(shù)據(jù),然后啟動(dòng)一個(gè)新的異步操作,并將控制權(quán)返回事件分類器。

4.信號(hào)驅(qū)動(dòng)IO模型
在信號(hào)驅(qū)動(dòng) IO 模型中,當(dāng)用戶線程發(fā)起一個(gè) IO 請(qǐng)求操作,會(huì)給對(duì)應(yīng)的 socket 注冊(cè)一個(gè)信號(hào)函數(shù),然后用戶線程會(huì)繼續(xù)執(zhí)行,當(dāng)內(nèi)核數(shù)據(jù)就緒時(shí)會(huì)發(fā)送一個(gè)信號(hào)給用戶線程,用戶線程接收到信號(hào)之后,便在信號(hào)函數(shù)中調(diào)用 IO 讀寫操作來進(jìn)行實(shí)際的 IO 請(qǐng)求操作。

5.異步 IO 模型
異步 IO 模型才是最理想的 IO 模型,在異步 IO 模型中,當(dāng)用戶線程發(fā)起 read 操作之后,立刻就可以開始去做其它的事。而另一方面,從內(nèi)核的角度,當(dāng)它受到一個(gè) asynchronous read 之后,它會(huì)立刻返回,說明 read 請(qǐng)求已經(jīng)成功發(fā)起了,因此不會(huì)對(duì)用戶線程產(chǎn)生任何 block。然后,內(nèi)核會(huì)等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶線程,當(dāng)這一切都完成之后,內(nèi)核會(huì)給用戶線程發(fā)送一個(gè)信號(hào),告訴它 read 操作完成了。也就說用戶線程完全不需要實(shí)際的整個(gè) IO 操作是如何進(jìn)行的,只需要先發(fā)起一個(gè)請(qǐng)求,當(dāng)接收內(nèi)核返回的成功信號(hào)時(shí)表示 IO 操作已經(jīng)完成,可以直接去使用數(shù)據(jù)了。
也就說在異步 IO 模型中,IO 操作的兩個(gè)階段都不會(huì)阻塞用戶線程,這兩個(gè)階段都是由內(nèi)核自動(dòng)完成,然后發(fā)送一個(gè)信號(hào)告知用戶線程操作已完成。用戶線程中不需要再次調(diào)用 IO 函數(shù)進(jìn)行具體的讀寫。這點(diǎn)是和信號(hào)驅(qū)動(dòng)模型有所不同的,在信號(hào)驅(qū)動(dòng)模型中,當(dāng)用戶線程接收到信號(hào)表示數(shù)據(jù)已經(jīng)就緒,然后需要用戶線程調(diào)用 IO 函數(shù)進(jìn)行實(shí)際的讀寫操作;而在異步 IO 模型中,收到信號(hào)表示 IO 操作已經(jīng)完成,不需要再在用戶線程中調(diào)用 IO 函數(shù)進(jìn)行實(shí)際的讀寫操作。

JAVA NIO

NIO基于Reactor。
NIO主要有三大核心部分:Channel(通道),Buffer(緩沖區(qū)), Selector。傳統(tǒng)IO基于字節(jié)流和字符流進(jìn)行操作,而NIO基于Channel和Buffer(緩沖區(qū))進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。Selector(選擇區(qū))用于監(jiān)聽多個(gè)通道的事件(比如:連接打開,數(shù)據(jù)到達(dá))。因此,單個(gè)線程可以監(jiān)聽多個(gè)數(shù)據(jù)通道。如下圖所示:

在這里插入圖片描述

NIO和傳統(tǒng)IO之間第一個(gè)最大的區(qū)別是,IO是面向流的,NIO是面向緩沖區(qū)的。

Java IO面向流意味著每次從流中讀一個(gè)或多個(gè)字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。此外,它不能前后移動(dòng)流中的數(shù)據(jù)。如果需要前后移動(dòng)從流中讀取的數(shù)據(jù),需要先將它緩存到一個(gè)緩沖區(qū)。NIO的緩沖導(dǎo)向方法不同。數(shù)據(jù)讀取到一個(gè)它稍后處理的緩沖區(qū),需要時(shí)可在緩沖區(qū)中前后移動(dòng)。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)。而且,需確保當(dāng)更多的數(shù)據(jù)讀入緩沖區(qū)時(shí),不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)。

IO的各種流是阻塞的。這意味著,當(dāng)一個(gè)線程調(diào)用read() 或 write()時(shí),該線程被阻塞,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入。該線程在此期間不能再干任何事情了。 NIO的非阻塞模式,使一個(gè)線程從某通道發(fā)送請(qǐng)求讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時(shí),就什么都不會(huì)獲取。而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前,該線程可以繼續(xù)做其他的事情。 非阻塞寫也是如此。一個(gè)線程請(qǐng)求寫入一些數(shù)據(jù)到某通道,但不需要等待它完全寫入,這個(gè)線程同時(shí)可以去做別的事情。 線程通常將非阻塞IO的空閑時(shí)間用于在其它通道上執(zhí)行IO操作,所以一個(gè)單獨(dú)的線程現(xiàn)在可以管理多個(gè)輸入和輸出通道(channel)。

下面說明下一些名詞的含義:

1.Channel。Channel和IO中的Stream(流)是差不多一個(gè)等級(jí)的。只不過Stream是單向的,譬如:InputStream, OutputStream,而Channel是雙向的,既可以用來進(jìn)行讀操作,又可以用來進(jìn)行寫操作。

2.Buffer,故名思意,緩沖區(qū),實(shí)際上是一個(gè)容器,是一個(gè)連續(xù)數(shù)組。Channel提供從文件、網(wǎng)絡(luò)讀取數(shù)據(jù)的渠道,但是讀取或?qū)懭氲臄?shù)據(jù)都必須經(jīng)由Buffer。
客戶端發(fā)送數(shù)據(jù)時(shí),必須先將數(shù)據(jù)存入Buffer中,然后將Buffer中的內(nèi)容寫入通道。服務(wù)端這邊接收數(shù)據(jù)必須通過Channel將數(shù)據(jù)讀入到Buffer中,然后再?gòu)腂uffer中取出數(shù)據(jù)來處理。
下圖描述了一個(gè)從一個(gè)客戶端向服務(wù)端發(fā)送數(shù)據(jù),然后服務(wù)端接收數(shù)據(jù)的過程。

在這里插入圖片描述

3.Selector。Selector類是NIO的核心類,Selector能夠檢測(cè)多個(gè)注冊(cè)的通道上是否有事件發(fā)生,如果有事件發(fā)生,便獲取事件然后針對(duì)每個(gè)事件進(jìn)行相應(yīng)的響應(yīng)處理。這樣一來,只是用一個(gè)單線程就可以管理多個(gè)通道,也就是管理多個(gè)連接。這樣使得只有在連接真正有讀寫事件發(fā)生時(shí),才會(huì)調(diào)用函數(shù)來進(jìn)行讀寫,就大大地減少了系統(tǒng)開銷,并且不必為每個(gè)連接都創(chuàng)建一個(gè)線程,不用去維護(hù)多個(gè)線程,并且避免了多線程之間的上下文切換導(dǎo)致的開銷。

Java AIO(NIO.2)

與NIO不同,當(dāng)進(jìn)行讀寫操作時(shí),只需要調(diào)用API的Read和Write方法即可這兩個(gè)都是異步的,完成后會(huì)調(diào)用回調(diào)函數(shù)。
主要在java.nio.channels包下增加了下面四個(gè)異步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
其中,對(duì)于AsynchronousSocketChannel而言,linux和windows實(shí)現(xiàn)方式并不一致,windows上通過IOCP實(shí)現(xiàn),即WindowsAsynchronousSocketChannelImpl,實(shí)現(xiàn)接口為Iocp.OverlappedChannel;而在Linux上是UnixAsynchronousSocketChannelImpl,實(shí)現(xiàn)接口為Port.PollableChannel。
AIO不需要對(duì)selector進(jìn)行輪詢

Zero copy

例如從文件中讀取數(shù)據(jù)并將其通過網(wǎng)絡(luò)傳輸給其他應(yīng)用程序的的操作需要經(jīng)歷四次內(nèi)核態(tài)和用戶態(tài)的切換。
步驟如下:

1.read()的調(diào)用引起了從用戶態(tài)到內(nèi)核態(tài)的切換(看圖二),內(nèi)部是通過sys_read()(或者類似的方
法)發(fā)起對(duì)文件數(shù)據(jù)的讀取。數(shù)據(jù)的第一次復(fù)制是通過DMA(直接內(nèi)存訪問)將磁盤上的數(shù)據(jù)復(fù)制
到內(nèi)核空間的緩沖區(qū)中;

2.數(shù)據(jù)從內(nèi)核空間的緩沖區(qū)復(fù)制到用戶空間的緩沖區(qū)后,read()方法也就返回了。此時(shí)內(nèi)核態(tài)又切換
回用戶態(tài),現(xiàn)在數(shù)據(jù)也已經(jīng)復(fù)制到了用戶地址空間的緩存中;

3.socket的send()方法的調(diào)用又會(huì)引起用戶態(tài)到內(nèi)核的切換,第三次數(shù)據(jù)復(fù)制又將數(shù)據(jù)從用戶空間緩
沖區(qū)復(fù)制到了內(nèi)核空間的緩沖區(qū),這次數(shù)據(jù)被放在了不同于之前的內(nèi)核緩沖區(qū)中,這個(gè)緩沖區(qū)與數(shù)
據(jù)將要被傳輸?shù)降膕ocket關(guān)聯(lián);

4.send()系統(tǒng)調(diào)用返回后,就產(chǎn)生了第四次用戶態(tài)和內(nèi)核態(tài)的切換。隨著DMA單獨(dú)異步的將數(shù)據(jù)從內(nèi)
核態(tài)的緩沖區(qū)中傳輸?shù)絽f(xié)議引擎發(fā)送到網(wǎng)絡(luò)上,有了第四次數(shù)據(jù)復(fù)制。
Java中的java.nio.channels.FilleChannel中定義了兩個(gè)方法:transferTo和transferFrom,該方法允許將一個(gè)通道交叉連接到另一個(gè)通道,而不需要通過一個(gè)中轉(zhuǎn)緩沖區(qū)來傳遞數(shù)據(jù)。
使用transferTo()方式所經(jīng)歷的步驟:

5.transferTo調(diào)用會(huì)引起DMA將文件內(nèi)容復(fù)制到讀緩沖區(qū)(內(nèi)核空間的緩沖區(qū)),然后數(shù)據(jù)從這個(gè)緩沖
區(qū)復(fù)制到另一個(gè)與socket輸出相關(guān)的內(nèi)核緩沖區(qū)中;

6.第三次數(shù)據(jù)復(fù)制就是DMA把socket關(guān)聯(lián)的緩沖區(qū)中的數(shù)據(jù)復(fù)制到協(xié)議引擎上發(fā)送到網(wǎng)絡(luò)上。
這次改善,是通過將內(nèi)核、用戶態(tài)切換的次數(shù)從四次減少到兩次,將數(shù)據(jù)的復(fù)制次數(shù)從四次減少到
三次(只有一次用到cpu資源)。但這并沒有達(dá)到我們零復(fù)制的目標(biāo)。如果底層網(wǎng)絡(luò)適配器支持收集操作的
話,我們可以進(jìn)一步減少內(nèi)核對(duì)數(shù)據(jù)的復(fù)制次數(shù)。在內(nèi)核為2.4或者以上版本的linux系統(tǒng)上,socket緩
沖區(qū)描述符將被用來滿足這個(gè)需求。這個(gè)方式不僅減少了內(nèi)核用戶態(tài)間的切換,而且也省去了那次需要
cpu參與的復(fù)制過程。從用戶角度來看依舊是調(diào)用transferTo()方法,但是其本質(zhì)發(fā)生了變化:

7.調(diào)用transferTo方法后數(shù)據(jù)被DMA從文件復(fù)制到了內(nèi)核的一個(gè)緩沖區(qū)中;

8.數(shù)據(jù)不再被復(fù)制到socket關(guān)聯(lián)的緩沖區(qū)中了,僅僅是將一個(gè)描述符(包含了數(shù)據(jù)的位置和長(zhǎng)度等信
息)追加到socket關(guān)聯(lián)的緩沖區(qū)中。DMA直接將內(nèi)核中的緩沖區(qū)中的數(shù)據(jù)傳輸給協(xié)議引擎,消除了
僅剩的一次需要cpu周期的數(shù)據(jù)復(fù)制。

Java多線程

Java線程池

為什么要用線程池

1.降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建、銷毀線程造成的消耗。

2.提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。

3.提高線程的可管理性。線程是稀缺資源,如果無(wú)限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。

4.提供更多更強(qiáng)大的功能:線程池具備可拓展性,允許開發(fā)人員向其中增加更多的功能。比如延時(shí)定時(shí)線程池ScheduledThreadPoolExecutor,就允許任務(wù)延期執(zhí)行或定期執(zhí)行。

線程池的創(chuàng)建方式

線程池的創(chuàng)建方法總共有 7 種,但總體來說可分為 2 類:
一類是通過 ThreadPoolExecutor 創(chuàng)建的線程池;另一個(gè)類是通過 Executors 創(chuàng)建的線程池。

線程池的創(chuàng)建方式總共包含以下 7 種(其中 6 種是通過 Executors 創(chuàng)建的,1 種是通過ThreadPoolExecutor 創(chuàng)建的):
Executors.newFixedThreadPool:創(chuàng)建一個(gè)固定大小的線程池,可控制并發(fā)的線程數(shù),超出的線程會(huì)在隊(duì)列中等待;
Executors.newCachedThreadPool:創(chuàng)建一個(gè)可緩存的線程池,若線程數(shù)超過處理所需,緩存一段時(shí)間后會(huì)回收,若線程數(shù)不夠,則新建線程;
Executors.newSingleThreadExecutor:創(chuàng)建單個(gè)線程數(shù)的線程池,它可以保證先進(jìn)先出的執(zhí)行順序;
Executors.newScheduledThreadPool:創(chuàng)建一個(gè)可以執(zhí)行延遲任務(wù)的線程池;
Executors.newSingleThreadScheduledExecutor:創(chuàng)建一個(gè)單線程的可以執(zhí)行延遲任務(wù)的線程池;
Executors.newWorkStealingPool:創(chuàng)建一個(gè)搶占式執(zhí)行的線程池(任務(wù)執(zhí)行順序不確定)【JDK 1.8 添加】。

ThreadPoolExecutor:最原始的創(chuàng)建線程池的方式,它包含了 7 個(gè)參數(shù)可供設(shè)置。

參數(shù)說明
corePoolSize核心線程數(shù)量,線程池維護(hù)線程的最少數(shù)量
maximumPoolSize線程池維護(hù)線程的最大數(shù)量
keepAliveTime線程池除核心線程外的其他線程的最長(zhǎng)空閑時(shí)間,超過該時(shí)間的空閑線程會(huì)被銷毀
unitkeepAliveTime的單位,TimeUnit中的幾個(gè)靜態(tài)屬性:NANOSECONDS(納秒)、MICROSECONDS(微秒)、MILLISECONDS(毫秒)、SECONDS(秒)、MINUTES、HOURS、DAYS
workQueue線程池所使用的任務(wù)緩沖隊(duì)列,有如下幾種: 1. ArrayBlockingQueue:一個(gè)由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。2.- LinkedBlockingQueue:一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列。3. SynchronousQueue:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列,即直接提交給線程不保持它們。 4. PriorityBlockingQueue:一個(gè)支持優(yōu)先級(jí)排序的無(wú)界阻塞隊(duì)列。5 . DelayQueue:一個(gè)使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無(wú)界阻塞隊(duì)列,只有在延遲期滿時(shí)才能從中提取元素。6. LinkedTransferQueue:一個(gè)由鏈表結(jié)構(gòu)組成的無(wú)界阻塞隊(duì)列。與SynchronousQueue類似,還含有非阻塞方法。7. LinkedBlockingDeque:一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。
threadFactory線程工廠,用于創(chuàng)建線程,一般用默認(rèn)的即可
handler

線程池對(duì)拒絕任務(wù)的處理策略,包括如下幾種:1. AbortPolicy:拒絕并拋出異常。2.CallerRunsPolicy:使用當(dāng)前調(diào)用的線程來執(zhí)行此任務(wù)。3. DiscardOldestPolicy:拋棄隊(duì)列頭部(最舊)的一個(gè)任務(wù),并執(zhí)行當(dāng)前任務(wù)。4. DiscardPolicy:忽略并拋棄當(dāng)前任務(wù)。

一般來說,推薦使用ThreadPoolExecutor創(chuàng)建線程池(因?yàn)镋xecutor創(chuàng)建的很多線程池,Executors 返回的線程池對(duì)象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:允許的請(qǐng)求隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致 OOM。
2)CachedThreadPool:允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線程,從而導(dǎo)致 OOM。

Callable、Runable、Future、FutureTash

四者之間的關(guān)系:
Callable是Runnable封裝的異步運(yùn)算任務(wù)。
Future用來保存Callable異步運(yùn)算的結(jié)果
FutureTask封裝Future的實(shí)體類

1.Callable與Runnbale的區(qū)別
a、Callable定義的方法是call,而Runnable定義的方法是run。
b、call方法有返回值,而run方法是沒有返回值的。
c、call方法可以拋出異常,而run方法不能拋出異常。Future

2.Future表示異步計(jì)算的結(jié)果,主要是判斷任務(wù)是否完成、中斷任務(wù)、獲取任務(wù)執(zhí)行結(jié)果。

3.FutureTask
可取消的異步計(jì)算,此類提供了對(duì)Future的基本實(shí)現(xiàn),僅在計(jì)算完成時(shí)才能獲取結(jié)果,如果計(jì)算尚未完
成,則阻塞get方法。
FutureTask不僅實(shí)現(xiàn)了Future接口,還實(shí)現(xiàn)了Runnable接口,所以不僅可以將FutureTask當(dāng)成一個(gè)任務(wù)交給Executor來執(zhí)行,還可以通過Thread來創(chuàng)建一個(gè)線程。

線程池工作流程

簡(jiǎn)單來說,提交一個(gè)任務(wù)到線程池中,線程池的處理流程如下:
1、判斷線程池里的核心線程是否都在執(zhí)行任務(wù),如果不是(核心線程空閑或者還有核心線程沒有被創(chuàng)建)則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù)。如果核心線程都在執(zhí)行任務(wù),則進(jìn)入下個(gè)流程。
2、線程池判斷工作隊(duì)列是否已滿,如果工作隊(duì)列沒有滿,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里。如果工作隊(duì)列滿了,則進(jìn)入下個(gè)流程。
3、判斷線程池里的線程是否都處于工作狀態(tài),如果沒有,則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù)。如果已經(jīng)滿了,則交給飽和策略來處理這個(gè)任務(wù)。

線程的生命周期

線程的五個(gè)生命周期

新建(New)、就緒(Runnable)、運(yùn)行(Running)、阻塞(Blocked)和死亡(Dead)五種。

線程的實(shí)現(xiàn)有兩種方式,一是繼承Thread類,二是實(shí)現(xiàn)Runnable接口,但無(wú)論如何,當(dāng)我們new了這個(gè)對(duì)象后。線程就進(jìn)入了初始狀態(tài);當(dāng)該對(duì)象調(diào)用了start()方法,就進(jìn)入可執(zhí)行狀態(tài);進(jìn)入可執(zhí)行狀態(tài)后,當(dāng)該對(duì)象被操作系統(tǒng)選中。獲得CPU時(shí)間片就會(huì)進(jìn)入執(zhí)行狀態(tài);進(jìn)入執(zhí)行狀態(tài)后情況就比較復(fù)雜了
4.1. run()方法或main()方法結(jié)束后,線程就進(jìn)入終止?fàn)顟B(tài);
4.2. 當(dāng)線程調(diào)用了自身的sleep()方法或其它線程的join()方法,就會(huì)進(jìn)入堵塞狀態(tài)(該狀態(tài)既停止當(dāng)前線程,但并不釋放所占有的資源)。當(dāng)sleep()結(jié)束或join()結(jié)束后。該線程進(jìn)入可執(zhí)行狀態(tài),繼續(xù)等待OS分配時(shí)間片;
4.3. 線程調(diào)用了yield()方法,意思是放棄當(dāng)前獲得的CPU時(shí)間片,回到可執(zhí)行狀態(tài),這時(shí)與其它進(jìn)程處于同等競(jìng)爭(zhēng)狀態(tài),OS有可能會(huì)接著又讓這個(gè)進(jìn)程進(jìn)入執(zhí)行狀態(tài)。
4.4. 當(dāng)線程剛進(jìn)入可執(zhí)行狀態(tài)(注意,還沒執(zhí)行),發(fā)現(xiàn)將要調(diào)用的資源被synchroniza(同步),獲取不到鎖標(biāo)記。將會(huì)馬上進(jìn)入鎖池狀態(tài),等待獲取鎖標(biāo)記(這時(shí)的鎖池里或許已經(jīng)有了其它線程在等待獲取鎖標(biāo)記,這時(shí)它們處于隊(duì)列狀態(tài),既先到先得),一旦線程獲得鎖標(biāo)記后,就轉(zhuǎn)入可執(zhí)行狀態(tài)。等待OS分配CPU時(shí)間片。
4.5. 當(dāng)線程調(diào)用wait()方法后會(huì)進(jìn)入等待隊(duì)列(進(jìn)入這個(gè)狀態(tài)會(huì)釋放所占有的全部資源,與堵塞狀態(tài)不同)。進(jìn)入這個(gè)狀態(tài)后。是不能自己主動(dòng)喚醒的,必須依靠其它線程調(diào)用notify()或notifyAll()方法才干被喚醒(因?yàn)閚otify()僅僅是喚醒一個(gè)線程,但我們由不能確定詳細(xì)喚醒的是哪一個(gè)線程?;蛟S我們須要喚醒的線程不可以被喚醒,因此在實(shí)際使用時(shí),一般都用notifyAll()方法,喚醒有所線程),線程被喚醒后會(huì)進(jìn)入鎖池。等待獲取鎖標(biāo)記。

僵死進(jìn)程

什么是僵死進(jìn)程

  僵死進(jìn)程就是指子進(jìn)程退出時(shí),父進(jìn)程并未對(duì)其發(fā)出的SIGCHLD信號(hào)進(jìn)行適當(dāng)處理,導(dǎo)致子進(jìn)程停留在僵死狀態(tài)等待其父進(jìn)程,這個(gè)狀態(tài)下的子進(jìn)程就是僵死進(jìn)程。這個(gè)僵死進(jìn)程不占有內(nèi)存,也不會(huì)執(zhí)行代碼,更不能被調(diào)用,他只是在進(jìn)程列表中占了個(gè)地位而已。

如何結(jié)束僵死進(jìn)程

他只需要父進(jìn)程調(diào)用wait()函數(shù)來替他收尸然后就完整的結(jié)束這一生。否則會(huì)一直保存這個(gè)僵死的狀態(tài)。把他父進(jìn)程給kill了,這樣他就變成了一個(gè)孤兒進(jìn)程,父親沒了沒人替他收拾,這時(shí)候僵死進(jìn)程就會(huì)被過繼給一個(gè)名叫init()進(jìn)程,這個(gè)進(jìn)程會(huì)給他收尸。

JAVA多線程并發(fā)

JAVA的鎖

1.互斥同步鎖
1)Synchorized
2)ReentrantLock
互斥同步鎖也叫做阻塞同步鎖,特征是會(huì)對(duì)沒有獲取鎖的線程進(jìn)行阻塞。
要理解互斥同步鎖,首選要明白什么是互斥什么是同步。簡(jiǎn)單的說互斥就是非你即我,同步就是順序訪問?;コ馔芥i就是以互斥的手段達(dá)到順序訪問的目的。操作系統(tǒng)提供了很多互斥機(jī)制比如信號(hào)量,互斥量,臨界區(qū)資源等來控制在某一個(gè)時(shí)刻只能有一個(gè)或者一組線程訪問同一個(gè)資源。
Java里面的互斥同步鎖就是Synchorized和ReentrantLock,前者是由語(yǔ)言級(jí)別實(shí)現(xiàn)的互斥同步鎖,理解和寫法簡(jiǎn)單但是機(jī)制笨拙,在JDK6之后性能優(yōu)化大幅提升,即使在競(jìng)爭(zhēng)激烈的情況下也能保持一個(gè)和ReentrantLock相差不多的性能,所以JDK6之后的程序選擇不應(yīng)該再因?yàn)樾阅軉栴}而放棄synchorized。
ReentrantLock是API層面的互斥同步鎖,需要程序自己打開并在finally中關(guān)閉鎖,和synchorized相比更加的靈活,體現(xiàn)在三個(gè)方面:等待可中斷,公平鎖以及綁定多個(gè)條件。但是如果程序猿對(duì)
ReentrantLock理解不夠深刻,或者忘記釋放lock,那么不僅不會(huì)提升性能反而會(huì)帶來額外的問題。另外synchorized是JVM實(shí)現(xiàn)的,可以通過監(jiān)控工具來監(jiān)控鎖的狀態(tài),遇到異常JVM會(huì)自動(dòng)釋放掉鎖。而ReentrantLock必須由程序主動(dòng)的釋放鎖。
互斥同步鎖都是可重入鎖,好處是可以保證不會(huì)死鎖。但是因?yàn)樯婕暗胶诵膽B(tài)和用戶態(tài)的切換,因此比較消耗性能。JVM開發(fā)團(tuán)隊(duì)在JDK5-JDK6升級(jí)過程中采用了很多鎖優(yōu)化機(jī)制來優(yōu)化同步無(wú)競(jìng)爭(zhēng)情況下鎖的性能。比如:自旋鎖和適應(yīng)性自旋鎖,輕量級(jí)鎖,偏向鎖,鎖粗化和鎖消除。

2.非堵塞同步

原子類(CAS)
非阻塞同步鎖也叫樂觀鎖,相比悲觀鎖來說,它會(huì)先進(jìn)行資源在工作內(nèi)存中的更新,然后根據(jù)與主存中舊值的對(duì)比來確定在此期間是否有其他線程對(duì)共享資源進(jìn)行了更新,如果舊值與期望值相同,就認(rèn)為沒有更新,可以把新值寫回內(nèi)存,否則就一直重試直到成功。它的實(shí)現(xiàn)方式依賴于處理器的機(jī)器指令:

CAS(Compare And Swap)
JUC中提供了幾個(gè)Automic類以及每個(gè)類上的原子操作就是樂觀鎖機(jī)制
不激烈情況下,性能比synchronized略遜,而激烈的時(shí)候,也能維持常態(tài)。激烈的時(shí)候,Atomic的性能會(huì)優(yōu)于ReentrantLock一倍左右。但是其有一個(gè)缺點(diǎn),就是只能同步一個(gè)值,一段代碼中只能出現(xiàn)一個(gè) Atomic的變量,多于一個(gè)同步無(wú)效。因?yàn)樗荒茉诙鄠€(gè)Atomic之間同步。
非阻塞鎖是不可重入的,否則會(huì)造成死鎖。

3.無(wú)同步方案
1)可重入代碼
在執(zhí)行的任何時(shí)刻都可以中斷-重入執(zhí)行而不會(huì)產(chǎn)生沖突。特點(diǎn)就是不會(huì)依賴堆上的共享資源
2)ThreadLocal/Volaitile
線程本地的變量,每個(gè)線程獲取一份共享變量的拷貝,單獨(dú)進(jìn)行處理。

4.線程本地存儲(chǔ)
如果一個(gè)共享資源一定要被多線程共享,可以盡量讓一個(gè)線程完成所有的處理操作,比如生產(chǎn)者消費(fèi)者模式中,一般會(huì)讓一個(gè)消費(fèi)者完成對(duì)隊(duì)列上資源的消費(fèi)。典型的應(yīng)用是基于請(qǐng)求-應(yīng)答模式的web服務(wù)器的設(shè)計(jì)

SynchronousQueue原理

SynchronousQueue 是一個(gè)隊(duì)列,但它的特別之處在于它內(nèi)部沒有容器。其中的一個(gè)生產(chǎn)線程,當(dāng)它生產(chǎn)產(chǎn)品(即put的時(shí)候),如果當(dāng)前沒有人想要消費(fèi)產(chǎn)品(即當(dāng)前沒有線程執(zhí)行take),此生產(chǎn)線程必須阻塞,等待一個(gè)消費(fèi)線程調(diào)用take操作,take操作將會(huì)喚醒該生產(chǎn)線程,同時(shí)消費(fèi)線程會(huì)獲取生產(chǎn)線程的產(chǎn)品(即數(shù)據(jù)傳遞),這樣的一個(gè)過程稱為一次配對(duì)過程(當(dāng)然也可以先take后put,原理是一樣的)。SynchronousQueue的實(shí)現(xiàn)并不依賴AQS(AbstractQueuedSynchronizer)而是使用CAS。

SynchronousQueue的內(nèi)部實(shí)現(xiàn)了兩個(gè)類,一個(gè)是TransferStack類,使用LIFO順序存儲(chǔ)元素,這個(gè)類用于非公平模式;還有一個(gè)類是TransferQueue,使用FIFI順序存儲(chǔ)元素,這個(gè)類用于公平模式。這兩個(gè)類繼承自"Nonblocking Concurrent Objects with Condition Synchronization"算法,此算法是由W. N. Scherer III 和 M. L. Scott提出的,關(guān)于此算法的理論內(nèi)容在這個(gè)網(wǎng)站中:http://www.cs.rochester.edu/u/scott/synchronization/pseudocode/duals.html。兩個(gè)類的性能差不多,F(xiàn)IFO通常用于在競(jìng)爭(zhēng)下支持更高的吞吐量,而LIFO在一般的應(yīng)用中保證更高的線程局部性。

JVM

JVM相關(guān)知識(shí)

在這里插入圖片描述

JVM內(nèi)存模型

JVM包括五塊數(shù)據(jù)區(qū)域:

參數(shù)說明
方法區(qū)方法區(qū)是各個(gè)線程共享的內(nèi)存區(qū)域,用于存儲(chǔ)被虛擬機(jī)加載的類型信息常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù),其中包括運(yùn)行時(shí)常量池(用于存放編譯器生成的各種字面量和符號(hào)引用 )
虛擬機(jī)棧線程私有,描述的是Java方法執(zhí)行的線程內(nèi)存模型,每個(gè)方法被執(zhí)行的時(shí)候,Java虛擬機(jī)都會(huì)同步創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量,操作數(shù)棧,動(dòng)態(tài)連接,方法出口等信息。每一個(gè)方法被調(diào)用直到執(zhí)行完畢的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程,其中含有局部變量表,存放Java基本數(shù)據(jù)類型,對(duì)象引用和returnAddress(指向了一條字節(jié)碼指令的地址)
本地方法棧結(jié)構(gòu)與虛擬機(jī)棧相似,但是是服務(wù)于本地方法的
程序計(jì)數(shù)器線程私有,可以看成當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
JAVA堆線程共享,此內(nèi)存區(qū)域用于存放對(duì)象實(shí)例
直接內(nèi)存直接內(nèi)存并不是 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分, 但也會(huì)被頻繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于 Channel 與 Buffer 的 IO 方式, 它可以使用 Native 函數(shù)庫(kù)直接分配堆外內(nèi)存, 然后使用DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作(詳見: Java I/O 擴(kuò)展), 這樣就避免了在 Java堆和 Native 堆中來回復(fù)制數(shù)據(jù), 因此在一些場(chǎng)景中可以顯著提高性能。

其中,JVM堆從GC角度看看能被分為新生代(Eden 區(qū)、From Survivor 區(qū)和 To Survivor 區(qū))和老年代。如下圖所示:

在這里插入圖片描述

名詞解釋:

1.新生代。是用來存放新生的對(duì)象。一般占據(jù)堆的 1/3 空間。由于頻繁創(chuàng)建對(duì)象,所以新生代會(huì)頻繁觸發(fā)MinorGC 進(jìn)行垃圾回收。新生代又分為 Eden 區(qū)、ServivorFrom、ServivorTo 三個(gè)區(qū)。

2.Eden區(qū)。Java 新對(duì)象的出生地(如果新創(chuàng)建的對(duì)象占用內(nèi)存很大,則直接分配到老年代)。當(dāng) Eden 區(qū)內(nèi)存不夠的時(shí)候就會(huì)觸發(fā) MinorGC,對(duì)新生代區(qū)進(jìn)行一次垃圾回收。

3.ServivorFrom。 上一次 GC 的幸存者,作為這一次 GC 的被掃描者。

4.ServivorTo。保留了一次 MinorGC 過程中的幸存者。

5.老年代。主要存放應(yīng)用程序中生命周期長(zhǎng)的內(nèi)存對(duì)象。老年代的對(duì)象比較穩(wěn)定,所以 MajorGC 不會(huì)頻繁執(zhí)行。在進(jìn)行 MajorGC 前一般都先進(jìn)行了一次 MinorGC,使得有新生代的對(duì)象晉身入老年代,導(dǎo)致空間不夠用時(shí)才觸發(fā)。當(dāng)無(wú)法找到足夠大的連續(xù)空間分配給新創(chuàng)建的較大對(duì)象時(shí)也會(huì)提前觸發(fā)一次 MajorGC 進(jìn)行垃圾回收騰出空間。 MajorGC 采用標(biāo)記清除算法:首先掃描一次所有老年代,標(biāo)記出存活的對(duì)象,然后回收沒有標(biāo)記的對(duì)象。MajorGC 的耗時(shí)比較長(zhǎng),因?yàn)橐獟呙柙倩厥?。MajorGC 會(huì)產(chǎn)生內(nèi)存碎片,為了減少內(nèi)存損耗,我們一般需要進(jìn)行合并或者標(biāo)記出來方便下次直接分配。當(dāng)老年代也滿了裝不下的時(shí)候,就會(huì)拋出 OOM(Out of Memory)異常。

6.永久代。指內(nèi)存的永久保存區(qū)域,主要存放 Class 和 Meta(元數(shù)據(jù))的信息,Class 在被加載的時(shí)候被放入永久區(qū)域,它和存放實(shí)例的區(qū)域不同,GC 不會(huì)在主程序運(yùn)行期對(duì)永久區(qū)域進(jìn)行清理。所以這也導(dǎo)致了永久代的區(qū)域會(huì)隨著加載的 Class 的增多而脹滿,最終拋出 OOM 異常。在 Java8 中,永久代已經(jīng)被移除,被一個(gè)稱為“元數(shù)據(jù)區(qū)”(元空間)的區(qū)域所取代。元空間的本質(zhì)和永久代類似,元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制。類的元數(shù)據(jù)放入 native memory, 字符串池和類的靜態(tài)變量放入 java 堆中(從1.7開始),這樣可以加載多少類的元數(shù)據(jù)就不再由MaxPermSize 控制, 而由系統(tǒng)的實(shí)際可用空間來控制。除了OOM的原因,元空間取代永久代的原因是為了合并Hotspot和JRockit的代碼(http://openjdk.java.net/jeps/122Motivation)。

元數(shù)據(jù)的定義:

  1. It’s the model of the loaded class base that Java retains at runtime in order to dynamically load, link, JIT compile, and execute Java code.
  2. Different design choices you make when writing your code can significantly expand or contract the amount of metadata Java needs to retain.
  3. The JVM can give you a breakdown of metadata storage costs for individual structures that model each loaded class, allowing you to weigh and compare the costs of alternative designs.

這部分參考資料:https://developers.redhat.com/blog/2018/02/14/java-class-metadata

GC過程:
新生代GC采用復(fù)制算法。稱為Minor GC。

1.首先,把 Eden 和 ServivorFrom 區(qū)域中存活的對(duì)象復(fù)制到 ServicorTo 區(qū)域(如果對(duì)象的年齡已經(jīng)達(dá)到了老年的標(biāo)準(zhǔn),則賦值到老年代區(qū)),同時(shí)把這些對(duì)象的年齡+1(如果 ServicorTo 不夠位置了就放到老年區(qū))。

2.清空 eden、servicorFrom,以及互換這兩個(gè)區(qū)域名字。

JVM線程模型

一般來說,線程模型有三種,分別是:

1.內(nèi)核線程模型。
完全由操作系統(tǒng)內(nèi)核提供的內(nèi)核線程(Kernel-Level Thread ,KLT)來實(shí)現(xiàn)多線程。在此模型下,線程的切換調(diào)度由系統(tǒng)內(nèi)核完成,系統(tǒng)內(nèi)核負(fù)責(zé)將多個(gè)線程執(zhí)行的任務(wù)映射到各個(gè)CPU中去執(zhí)行。操作系統(tǒng)內(nèi)核提供的內(nèi)核線程(Kernel-Level Thread ,KLT)來實(shí)現(xiàn)多線程。在此模型下,線程的切換調(diào)度由系統(tǒng)內(nèi)核完成,系統(tǒng)內(nèi)核負(fù)責(zé)將多個(gè)線程執(zhí)行的任務(wù)映射到各個(gè)CPU中去執(zhí)行。由于內(nèi)核線程的支持,每個(gè)輕量級(jí)進(jìn)程都成為一個(gè)獨(dú)立的調(diào)度單元,即使有一個(gè)輕量級(jí)進(jìn)程在系統(tǒng)調(diào)用中阻塞了,也不會(huì)影響整個(gè)進(jìn)程繼續(xù)工作,但是輕量級(jí)進(jìn)程具有它的局限性:
首先,由于是基于內(nèi)核線程實(shí)現(xiàn)的,所以各種線程操作,如創(chuàng)建、析構(gòu)及同步,都需要進(jìn)行系統(tǒng)調(diào)用。而系統(tǒng)調(diào)用的代價(jià)相對(duì)較高,需要在用戶態(tài)(User Mode)和內(nèi)核態(tài)(Kernel Mode)中來回切換。
其次,每個(gè)輕量級(jí)進(jìn)程都需要有一個(gè)內(nèi)核線程的支持,因此輕量級(jí)進(jìn)程要消耗一定的內(nèi)核資源(如內(nèi)核線程的棧空間),因此一個(gè)系統(tǒng)支持輕量級(jí)進(jìn)程的數(shù)量是有限的。

在這里插入圖片描述

2.用戶線程模型。
用戶線程指的是完全建立在用戶空間的線程庫(kù)上,系統(tǒng)內(nèi)核不能感知線程存在的實(shí)現(xiàn)。用戶線程的建立、同步、銷毀和調(diào)度完全在用戶態(tài)中完成,不需要內(nèi)核的幫助。如果程序?qū)崿F(xiàn)得當(dāng),這種線程不需要切換到內(nèi)核態(tài),因此操作可以是非??焖偾业拖牡模部梢灾С忠?guī)模更大的線程數(shù)量,部分高性能數(shù)據(jù)庫(kù)中的多線程就是由用戶線程實(shí)現(xiàn)的。這種進(jìn)程與用戶線程之間1:N的關(guān)系稱為一對(duì)多的線程模型。使用用戶線程的優(yōu)勢(shì)在于不需要系統(tǒng)內(nèi)核支援,劣勢(shì)也在于沒有系統(tǒng)內(nèi)核的支援,所有的線程操作都需要用戶程序自己處理。線程的創(chuàng)建、切換和調(diào)度都是需要考慮的問題,而且由于操作系統(tǒng)只把處理器資源分配到進(jìn)程,那諸如“阻塞如何處理”、“多處理器系統(tǒng)中如何將線程映射到其他處理器上”這類問題解決起來將會(huì)異常困難,甚至不可能完成。因而使用用戶線程實(shí)現(xiàn)的程序一般都比較復(fù)雜,此處所講的“復(fù)雜”與“程序自己完成線程操作”,并不限制程序中必須編寫了復(fù)雜的實(shí)現(xiàn)用戶線程的代碼,使用用戶線程的程序,很多都依賴特定的線程庫(kù)來完成基本的線程操作,這些復(fù)雜性都封裝在線程庫(kù)之中,除了以前在不支持多線程的操作系統(tǒng)中(如DOS)的多線程程序與少數(shù)有特殊需求的程序外,現(xiàn)在使用用戶線程的程序越來越少了,Java、Ruby等語(yǔ)言都曾經(jīng)使用過用戶線程,最終又都放棄使用它。

在這里插入圖片描述

3.混合線程模型
線程除了依賴內(nèi)核線程實(shí)現(xiàn)和完全由用戶程序自己實(shí)現(xiàn)之外,還有一種將內(nèi)核線程與用戶線程一起使用的實(shí)現(xiàn)方式。在這種混合實(shí)現(xiàn)下,既存在用戶線程,也存在輕量級(jí)進(jìn)程。用戶線程還是完全建立在用戶空間中,因此用戶線程的創(chuàng)建、切換、析構(gòu)等操作依然廉價(jià),并且可以支持大規(guī)模的用戶線程并發(fā)。而操作系統(tǒng)提供支持的輕量級(jí)進(jìn)程則作為用戶線程和內(nèi)核線程之間的橋梁,這樣可以使用內(nèi)核提供的線程調(diào)度功能及處理器映射,并且用戶線程的系統(tǒng)調(diào)用要通過輕量級(jí)線程來完成,大大降低了整個(gè)進(jìn)程被完全阻塞的風(fēng)險(xiǎn)。在這種混合模式中,用戶線程與輕量級(jí)進(jìn)程的數(shù)量比是不定的,即為N:M的關(guān)系。許多UNIX系列的操作系統(tǒng),如Solaris、HP-UX等都提供了N:M的線程模型實(shí)現(xiàn)。

在這里插入圖片描述

對(duì)于Sun JDK來說,它的Windows版與Linux版都是使用一對(duì)一的線程模型(即內(nèi)核線程模型)實(shí)現(xiàn)的,一條Java線程就映射到一條輕量級(jí)進(jìn)程之中,因?yàn)閃indows和Linux系統(tǒng)提供的線程模型就是一對(duì)一的。
JVM 允許一個(gè)應(yīng)用并發(fā)執(zhí)行多個(gè)線程。Hotspot JVM 中的 Java 線程與原生操作系統(tǒng)線程有直接的映射關(guān)系。當(dāng)線程本地存儲(chǔ)、緩沖區(qū)分配、同步對(duì)象、棧、程序計(jì)數(shù)器等準(zhǔn)備好以后,就會(huì)創(chuàng)建一個(gè)操作系統(tǒng)原生線程。Java 線程結(jié)束,原生線程隨之被回收。操作系統(tǒng)負(fù)責(zé)調(diào)度所有線程,并把它們分配到任何可用的 CPU 上。當(dāng)原生線程初始化完畢,就會(huì)調(diào)用 Java 線程的 run() 方法。當(dāng)線程結(jié)束時(shí),會(huì)釋放原生線程和 Java 線程的所有資源。

Hotspot JVM 后臺(tái)運(yùn)行的系統(tǒng)線程主要有下面幾個(gè):

線程名功能
虛擬機(jī)線程(VM thread)這個(gè)線程等到 JVM 到達(dá)安全點(diǎn)操作出現(xiàn)。這些操作的類型有:stop-the-world 垃圾回收、線程棧 dump、線程暫停、線程偏向鎖(biased locking)解除。具體來說:The VMThread spends its time waiting for operations to appear in the VMOperationQueue, and then executing those operations. Typically these operations are passed on to the VMThread because they require that the VM reach a safepoint before they can be executed. In simple terms, when the VM is at safepoint all threads inside the VM have been blocked, and any threads executing in native code are prevented from returning to the VM while the safepoint is in progress. This means that the VM operation can be executed knowing that no thread can be in the middle of modifying the Java heap, and all threads are in a state such that their Java stacks are unchanging and can be examined.The most familiar VM operation is for garbage collection, or more specifically for the “stop-the-world” phase of garbage collection that is common to many garbage collection algorithms. But many other safepoint based VM operations exist, for example: biased locking revocation, thread stack dumps, thread suspension or stopping (i.e. The java.lang.Thread.stop() method) and numerous inspection/modification operations requested through JVMTI.Many VM operations are synchronous, that is the requestor blocks until the operation has completed, but some are asynchronous or concurrent, meaning that the requestor can proceed in parallel with the VMThread (assuming no safepoint is initiated of course).Safepoints are initiated using a cooperative, polling-based mechanism. In simple terms, every so often a thread asks “should I block for a safepoint?”. Asking this question efficiently is not so simple. One place where the question is often asked is during a thread state transition. Not all state transitions do this, for example a thread leaving the VM to go to native code, but many do. The other places where a thread asks are in compiled code when returning from a method or at certain stages during loop iteration. Threads executing interpreted code don’t usually ask the question, instead when the safepoint is requested the interpreter switches to a different dispatch table that includes the code to ask the question; when the safepoint is over, the dispatch table is switched back again. Once a safepoint has been requested, the VMThread must wait until all threads are known to be in a safepoint-safe state before proceeding to execute the VM operation. During a safepoint the Threads_lock is used to block any threads that were running, with the VMThread finally releasing the Threads_lock after the VM operation has been performed.
周期性任務(wù)線程這線程負(fù)責(zé)定時(shí)器事件(也就是中斷),用來調(diào)度周期性操作的執(zhí)行。
GC線程這些線程支持 JVM 中不同的垃圾回收活動(dòng)。
編譯器線程這些線程在運(yùn)行時(shí)將字節(jié)碼動(dòng)態(tài)編譯成本地平臺(tái)相關(guān)的機(jī)器碼。
信號(hào)分發(fā)線程這個(gè)線程接收發(fā)送到 JVM 的信號(hào)并調(diào)用適當(dāng)?shù)?JVM 方法處理。

此部分參考資料: https://openjdk.java.net/groups/hotspot/docs/RuntimeOverview.html

垃圾回收

GC Roots

可達(dá)性分析中GC Roots對(duì)象包括下列幾種:

  1. 虛擬機(jī)棧引用的對(duì)象
  2. 方法區(qū)中類靜態(tài)屬性引用的對(duì)象。
  3. 方法區(qū)中常量引用的對(duì)象。
  4. 本地方法棧中JNI引用的對(duì)象。
  5. 虛擬機(jī)內(nèi)部的引用,比如基本數(shù)據(jù)類型對(duì)應(yīng)的class對(duì)象。
  6. 所有同步鎖持有的對(duì)象。
  7. 反應(yīng)虛擬機(jī)內(nèi)部情況的JMXBean、JVMTI注冊(cè)的回調(diào),本地代碼緩存等。
  8. 局部回收時(shí),某個(gè)區(qū)域中的對(duì)象完全可能被其他區(qū)域的對(duì)象所引用,所以關(guān)聯(lián)區(qū)域的對(duì)象也一并加入GC Roots。

由于目前主流Java虛擬機(jī)使用的都是準(zhǔn)確式垃圾收集(指虛擬機(jī)可以準(zhǔn)確知道一段值是代碼還是數(shù)據(jù)),因此虛擬機(jī)并不需要一個(gè)不漏的檢查完所有執(zhí)行上下文和全局的位置,而是有辦法直接得到哪些地方存著對(duì)象的引用。Hotspot使用OopMap來完成任務(wù),一旦類加載完成,HotSpot就會(huì)把對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來。

OopMap 記錄了棧上本地變量到堆上對(duì)象的引用關(guān)系。其作用是:垃圾收集時(shí),收集線程會(huì)對(duì)棧上的內(nèi)存進(jìn)行掃描,看看哪些位置存儲(chǔ)了 Reference 類型。如果發(fā)現(xiàn)某個(gè)位置確實(shí)存的是 Reference 類型,就意味著它所引用的對(duì)象這一次不能被回收。但問題是,棧上的本地變量表里面只有一部分?jǐn)?shù)據(jù)是 Reference 類型的(它們是我們所需要的),那些非 Reference 類型的數(shù)據(jù)對(duì)我們而言毫無(wú)用處,但我們還是不得不對(duì)整個(gè)棧全部掃描一遍,這是對(duì)時(shí)間和資源的一種浪費(fèi)。
一個(gè)很自然的想法是,能不能用空間換時(shí)間,在某個(gè)時(shí)候把棧上代表引用的位置全部記錄下來,這樣到真正 gc 的時(shí)候就可以直接讀取,而不用再一點(diǎn)一點(diǎn)的掃描了。事實(shí)上,大部分主流的虛擬機(jī)也正是這么做的,比如 HotSpot ,它使用一種叫做 OopMap 的數(shù)據(jù)結(jié)構(gòu)來記錄這類信息。
我們知道,一個(gè)線程意味著一個(gè)棧,一個(gè)棧由多個(gè)棧幀組成,一個(gè)棧幀對(duì)應(yīng)著一個(gè)方法,一個(gè)方法里面可能有多個(gè)安全點(diǎn)。 gc 發(fā)生時(shí),程序首先運(yùn)行到最近的一個(gè)安全點(diǎn)停下來,然后更新自己的 OopMap ,記下棧上哪些位置代表著引用。枚舉根節(jié)點(diǎn)時(shí),遞歸遍歷每個(gè)棧幀的 OopMap ,通過棧中記錄的被引用對(duì)象的內(nèi)存地址,即可找到這些對(duì)象( GC Roots )。
可以把oopMap簡(jiǎn)單理解成是調(diào)試信息。在源代碼里面每個(gè)變量都是有類型的,但是編譯之后的代碼就只有變量在棧上的位置了。oopMap就是一個(gè)附加的信息,告訴你棧上哪個(gè)位置本來是個(gè)什么東西。 這個(gè)信息是在JIT編譯時(shí)跟機(jī)器碼一起產(chǎn)生的。因?yàn)橹挥芯幾g器知道源代碼跟產(chǎn)生的代碼的對(duì)應(yīng)關(guān)系。 每個(gè)方法可能會(huì)有好幾個(gè)oopMap,就是根據(jù)safepoint把一個(gè)方法的代碼分成幾段,每一段代碼一個(gè)oopMap,作用域自然也僅限于這一段代碼。 循環(huán)中引用多個(gè)對(duì)象,肯定會(huì)有多個(gè)變量,編譯后占據(jù)棧上的多個(gè)位置。那這段代碼的oopMap就會(huì)包含多條記錄。
通過上面的解釋,我們可以很清楚的看到使用 OopMap 可以避免全棧掃描,加快枚舉根節(jié)點(diǎn)的速度。但這并不是它的全部用意。它的另外一個(gè)更根本的作用是,可以幫助 HotSpot 實(shí)現(xiàn)準(zhǔn)確式 GC 。

但是隨著而來的又有一個(gè)問題,就是在方法執(zhí)行的過程中, 可能會(huì)導(dǎo)致引用關(guān)系發(fā)生變化,那么保存的OopMap就要隨著變化。如果每次引用關(guān)系發(fā)生了變化都要去修改OopMap的話,這又是一件成本很高的事情。所以這里就引入了安全點(diǎn)的概念。
什么是安全點(diǎn)?OopMap的作用是為了在GC的時(shí)候,快速進(jìn)行可達(dá)性分析,所以O(shè)opMap并不需要一發(fā)生改變就去更新這個(gè)映射表。只要這個(gè)更新在GC發(fā)生之前就可以了。所以O(shè)opMap只需要在預(yù)先選定的一些位置上記錄變化的OopMap就行了。這些特定的點(diǎn)就是SafePoint(安全點(diǎn))。由此也可以知道,程序并不是在所有的位置上都可以進(jìn)行GC的,只有在達(dá)到這樣的安全點(diǎn)才能暫停下來進(jìn)行GC。
既然安全點(diǎn)決定了GC的時(shí)機(jī),那么安全點(diǎn)的選擇就至為重要了。安全點(diǎn)太少,會(huì)讓GC等待的時(shí)間太長(zhǎng),太多會(huì)浪費(fèi)性能。所以安全點(diǎn)的選擇是以程序“是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征”為標(biāo)準(zhǔn)的,所以我們這里了解一下結(jié)果就行了。一般會(huì)在如下幾個(gè)位置選擇安全點(diǎn):

  1. 循環(huán)的末尾
  2. 方法臨返回前 / 調(diào)用方法的call指令后
  3. 可能拋異常的位置

安全點(diǎn)另一方面問題:如何讓垃圾收集時(shí)所有線程都到達(dá)安全點(diǎn):主要有兩種方案:搶先式中斷和主動(dòng)式中斷。
搶斷式中斷就是在GC的時(shí)候,讓所有的線程都中斷,如果這些線程中發(fā)現(xiàn)中斷地方不在安全點(diǎn)上的,就恢復(fù)線程,讓他們重新跑起來,直到跑到安全點(diǎn)上。
主動(dòng)式中斷在GC的時(shí)候,不會(huì)主動(dòng)去中斷線程,僅僅是設(shè)置一個(gè)標(biāo)志,當(dāng)程序運(yùn)行到安全點(diǎn)時(shí)就去輪訓(xùn)該位置,發(fā)現(xiàn)該位置被設(shè)置為真時(shí)就自己中斷掛起。所以輪訓(xùn)標(biāo)志的地方是和安全點(diǎn)重合的,另外創(chuàng)建對(duì)象需要分配內(nèi)存的地方也需要輪詢?cè)撐恢谩?/p>

但是安全點(diǎn)也不是完美的,對(duì)于處于Sleep或者block的線程,無(wú)法到達(dá)安全點(diǎn),因此引入了安全區(qū)域進(jìn)行處理。

四種引用類型

1.強(qiáng)引用。即傳統(tǒng)引用。

2.軟引用。用來描述還有用,但非必須的對(duì)象。僅在系統(tǒng)內(nèi)存即將溢出時(shí)回收。SoftReference類。常用于緩存等場(chǎng)景。

3.弱引用。必須程度小于軟引用。在下一次垃圾收集時(shí)回收。WeakReference類。ThreadLocal類。

4.虛引用。最弱的引用,一個(gè)對(duì)象是否有虛引用的存在,不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過虛引用獲取一個(gè)對(duì)象實(shí)例。虛引用一般用于在一個(gè)對(duì)象被回收時(shí)收到一個(gè)系統(tǒng)通知。虛引用一般和一個(gè)隊(duì)列綁定。PhantomReference類。虛引用主要用來跟蹤對(duì)象被垃圾回收的活動(dòng)。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。程序如果發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對(duì)象的內(nèi)存被回收之前采取必要的行動(dòng)。

常見垃圾回收算法

1.標(biāo)記清除算法(mark-sweep)。標(biāo)記所有需要回收的對(duì)象,并在標(biāo)記完成后,統(tǒng)一回收所有被標(biāo)記的對(duì)象。會(huì)產(chǎn)生內(nèi)存碎片化嚴(yán)重,后續(xù)可能發(fā)生大對(duì)象不能找到可利用空間的問題,并且如果Java堆中包含大量需要回收的對(duì)象,必須進(jìn)行大量標(biāo)記和清除的動(dòng)作。

2.復(fù)制算法。 為了解決 Mark-Sweep 算法內(nèi)存碎片化的缺陷而被提出的算法。按內(nèi)存容量將內(nèi)存劃分為等大小的兩塊。每次只使用其中一塊,當(dāng)這一塊內(nèi)存滿后將尚存活的對(duì)象復(fù)制到另一塊上去,把已使用的內(nèi)存清掉。這種算法雖然實(shí)現(xiàn)簡(jiǎn)單,內(nèi)存效率高,不易產(chǎn)生碎片,但是最大的問題是可用內(nèi)存被壓縮到了原本的一半。且存活對(duì)象增多的話,Copying 算法的效率會(huì)大大降低。

3.標(biāo)記整理算法(Mark-Compact)。結(jié)合了以上兩個(gè)算法,為了避免缺陷而提出。標(biāo)記階段和 Mark-Sweep 算法相同,標(biāo)記后不是清理對(duì)象,而是將存活對(duì)象移向內(nèi)存的一端。然后清除端邊界外的對(duì)象。

4.分代收集算法。分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根據(jù)對(duì)象存活的不同生命周期將內(nèi)存劃分為不同的域,一般情況下將 GC 堆劃分為老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特點(diǎn)是每次垃圾回收時(shí)只有少量對(duì)象需要被回收,新生代的特點(diǎn)是每次垃圾回收時(shí)都有大量垃圾需要被回收,因此可以根據(jù)不同區(qū)域選擇不同的算法。

目前大部分 JVM 的 GC 對(duì)于新生代都采取 Copying 算法,因?yàn)樾律忻看卫厥斩家厥沾蟛糠謱?duì)象,即要復(fù)制的操作比較少,但通常并不是按照 1:1 來劃分新生代。一般將新生代劃分為一塊較大的 Eden 空間和兩個(gè)較小的 Survivor 空間(From Survivor , To Survivor ),每次使用Eden 空間和其中的一塊 Survivor 空間,當(dāng)進(jìn)行回收時(shí),將該兩塊空間中還存活的對(duì)象復(fù)制到另一塊 Survivor 空間中,Eden:From Survivor:To Survivor =8:1:1。而老年代因?yàn)槊看沃换厥丈倭繉?duì)象,因而采用 Mark-Compact 算法。當(dāng)對(duì)象在 Survivor 區(qū)躲過一次 GC 后,其年齡就會(huì)+1。默認(rèn)情況下年齡到達(dá) 15 的對(duì)象會(huì)被移到老生代中。
目前的分代回收器中,新生代占總空間的1/3,老年代占2/3.。

實(shí)際上,并不是內(nèi)存被耗空的時(shí)候才拋出OutOfMemoryException,而是JVM98%的時(shí)間都花費(fèi)在內(nèi)存回收,每次回收的內(nèi)存小于2%滿足時(shí)就拋出異常。

常見的垃圾回收器

Serial
用于新生代,單線程,STW時(shí)間長(zhǎng)。
SerialOld
用于老年代,單線程的。一樣有STW。
ParallelScavenger
在Serial基礎(chǔ)上,使用了多線程。
ParallelOld
應(yīng)用于老年代,多線程的。一樣有STW。
ParNew
與ParallelScavenger的區(qū)別就是,PN能更好的和CMS配合使用,PN的響應(yīng)時(shí)間優(yōu)先,PS的吞吐量?jī)?yōu)先。
CMS
三色標(biāo)記:
黑色標(biāo)記:自己已經(jīng)標(biāo)記,直接引用的對(duì)象區(qū)域已經(jīng)標(biāo)記
灰色標(biāo)記:自己標(biāo)記完成,但引用區(qū)域沒來得及標(biāo)記
白色標(biāo)記:沒有遍歷到的區(qū)域,可以理解為沒有標(biāo)記
三色標(biāo)記的漏標(biāo)問題:
原本A引用B,B引用C,在A被標(biāo)記為黑色后,A建立了指向C的引用,且B指向C的引用斷開。此時(shí)A已經(jīng)被標(biāo)記為黑色,則不會(huì)再便利A的引用,且B對(duì)C的引用已經(jīng)斷開。這樣會(huì)造成C未被標(biāo)記,被垃圾回收,造成空指針問題。
CMS解決方式:
當(dāng)發(fā)生A引用C<黑色對(duì)象指向了白色對(duì)象>的情況。此時(shí)將A標(biāo)記為灰色。并且在全部標(biāo)記結(jié)束后,會(huì)有一個(gè)remark階段,必須從頭掃描一遍。發(fā)生在重新標(biāo)記階段,所以在這個(gè)階段必須STW。
CMS有4個(gè)主要階段:初始標(biāo)記階段、并發(fā)標(biāo)記階段、重新標(biāo)記階段、并發(fā)清理階段。
初始標(biāo)記階段:開始一個(gè)短暫的STW,然后進(jìn)行初始標(biāo)記,初始標(biāo)記只標(biāo)記根節(jié)點(diǎn),所以這個(gè)STW時(shí)間很短。這個(gè)階段是單線程的。
并發(fā)標(biāo)記階段:據(jù)說GC大部分時(shí)間都浪費(fèi)在這,所以這一步是和工作線程同時(shí)運(yùn)行的。一邊產(chǎn)生垃圾,一邊標(biāo)記垃圾。
重新標(biāo)記階段:開始一個(gè)短暫的STW,把在并發(fā)標(biāo)記階段,工作線程產(chǎn)生的垃圾重新標(biāo)記一下,這個(gè)STW時(shí)間也很短。這個(gè)階段是多線程的。
并發(fā)清理階段:工作線程和垃圾回收線程同時(shí)運(yùn)行,把這個(gè)時(shí)候工作線程產(chǎn)生的垃圾叫做浮動(dòng)垃圾,浮動(dòng)垃圾就要等到下一次CMS清理了。CMS標(biāo)記的一個(gè)實(shí)例:
 

在這里插入圖片描述

CMS是老年代的垃圾回收器,在老年代分配不下的時(shí)候,觸發(fā)CMS。
CMS的最大問題:CMS會(huì)使內(nèi)存碎片化,老年代產(chǎn)生了很多的碎片,然后從年輕代過來的對(duì)象無(wú)法找到空間,造成了promotion failed。這時(shí)候,CMS既沒有機(jī)會(huì)進(jìn)行垃圾回收,又放不下新來的對(duì)象,在這種情況下,CMS會(huì)調(diào)用SerialOld來進(jìn)行垃圾回收。

G1
G1在邏輯上分代,在物理上不分代。G1引入了分而治之的思想,把內(nèi)存分為一個(gè)一個(gè)的小塊(region)。每個(gè)region邏輯上屬于下面四種分代中的一種。
四種分代:
a.Old區(qū):老對(duì)象
b.Survivor區(qū):存活對(duì)象
c.Eden區(qū):新生對(duì)象
d.Humongous區(qū):大對(duì)象,如果這個(gè)對(duì)象特別大,可能會(huì)跨兩個(gè)region。
針對(duì)三色標(biāo)記漏標(biāo)問題,G1解決方案是:
當(dāng)發(fā)生B指向C的引用<指針>斷開的時(shí)候。將這個(gè)引用<指針>記錄在一個(gè)特定區(qū)域。垃圾回收線程在掃描的時(shí)候會(huì)查看這個(gè)區(qū)域的增量數(shù)據(jù)。發(fā)現(xiàn)有增量數(shù)據(jù)。會(huì)看C此時(shí)還有沒有對(duì)象指向他,如果有則將區(qū)域C標(biāo)記為灰色。否則不標(biāo)記<視為可回收垃圾>。

ZGC
邏輯和物理上都不分代,在并發(fā)標(biāo)記的時(shí)候采用的是顏色指針。42位記錄地址,4位代表狀態(tài),18位的空閑。

垃圾回收器的選擇

按內(nèi)存大小分:

在這里插入圖片描述

類加載機(jī)制

在這里插入圖片描述

上圖為JVM加載Java類的過程。

加載
加載是類加載過程中的一個(gè)階段,這個(gè)階段會(huì)在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的入口。注意這里不一定非得要從一個(gè)Class文件獲取,這里既可以從ZIP包中讀?。ū热鐝膉ar包和war包中讀?。?,從網(wǎng)絡(luò)中獲取(Web Applet),可以在運(yùn)行時(shí)計(jì)算生成(動(dòng)態(tài)代理),也可以由其它文件生成(比如將JSP文件轉(zhuǎn)換成對(duì)應(yīng)的Class類),從數(shù)據(jù)庫(kù)讀?。ɡ缒承┲虚g件服務(wù)器),從加密文件中獲?。ㄓ脕矸乐笴lass文件被窺探)。用戶可以通過自定義的ClassLoader來完成類的加載。
對(duì)于數(shù)據(jù)類來說,其空間是Java直接在內(nèi)存中動(dòng)態(tài)構(gòu)造的,但是類加載器還是得加載其中的元素類型。其遵循一下規(guī)律。

如果數(shù)組的組件類型是引用類型。遞歸采用本節(jié)定義的加載過程去加載組件類型。數(shù)組C將會(huì)標(biāo)識(shí)再加載該組件類型的類加載器的類名稱空間上(因?yàn)橐粋€(gè)類型必須與類加載器一起確定其唯一性)。
如果數(shù)組的組件類型不是引用類型,例如(int),Java虛擬機(jī)會(huì)把數(shù)組C標(biāo)記為與引導(dǎo)類加載器管理。
數(shù)組類的可訪問行與它的組件類型的可訪問行一致。如果組件類型不是引用類型,他的數(shù)組類的可訪問行將默認(rèn)為public,可被所有的類和接口訪問到,
驗(yàn)證
這一階段的主要目的是為了確保Class文件的字節(jié)流中包含的信息是否符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。
驗(yàn)證主要包括:

文件格式驗(yàn)證。主要是字節(jié)流是否符合Class文件格式的規(guī)范。
元數(shù)據(jù)驗(yàn)證。對(duì)字節(jié)碼所描述的信息進(jìn)行語(yǔ)義分析,以保證其描述的信息符合Java語(yǔ)言規(guī)范。
字節(jié)碼驗(yàn)證。通過數(shù)據(jù)流分析和控制流分析,確認(rèn)程序語(yǔ)義是合法的,符合邏輯的,不會(huì)危害虛擬機(jī)。
符號(hào)引用驗(yàn)證。發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)換為直接引用的時(shí)候。
準(zhǔn)備
準(zhǔn)備階段是正式為類變量(被static修飾的變量)分配內(nèi)存并設(shè)置類變量的初始值階段,即在方法區(qū)中分配這些變量所使用的內(nèi)存空間。

解析
解析階段是指虛擬機(jī)將常量池中的符號(hào)引用替換為直接引用的過程。符號(hào)引用主要有CONSTANT_Class_info, Constant_Fieldref_Info,Constant_Methodref_Info等類型的常量。

符號(hào)引用以一組符號(hào)來描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能夠無(wú)歧義的定位到目標(biāo)即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等類型的常量出現(xiàn)。符號(hào)引用與虛擬機(jī)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定加載到內(nèi)存中。在Java中,一個(gè)java類將會(huì)編譯成一個(gè)class文件。在編譯時(shí),java類并不知道所引用的類的實(shí)際地址,因此只能使用符號(hào)引用來代替。比如org.simple.People類引用了org.simple.Language類,在編譯時(shí)People類并不知道Language類的實(shí)際內(nèi)存地址,因此只能使用符號(hào)org.simple.Language(假設(shè)是這個(gè),當(dāng)然實(shí)際中是由類似于CONSTANT_Class_info的常量來表示的)來表示Language類的地址。各種虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局可能有所不同,但是它們能接受的符號(hào)引用都是一致的,因?yàn)榉?hào)引用的字面量形式明確定義在Java虛擬機(jī)規(guī)范的Class文件格式中。

初始化
初始化階段是類加載最后一個(gè)階段,前面的類加載階段之后,除了在加載階段可以自定義類加載器以外,其它操作都由JVM主導(dǎo)。到了初始階段,才開始真正執(zhí)行類中定義的Java程序代碼。類初始化主要執(zhí)行類構(gòu)造器<clinit>方法。
<clinit>方法是由編譯器自動(dòng)收集類中所有類變量的賦值語(yǔ)句和靜態(tài)語(yǔ)句(也就是static{}中的語(yǔ)句)合并產(chǎn)生的,收集順序就是文件中出現(xiàn)的順序。靜態(tài)語(yǔ)句塊可以為出現(xiàn)在其后的靜態(tài)變量賦值,但是不能訪問。
以下幾種情況不會(huì)進(jìn)行初始化:

通過子類引用父類的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化,而不會(huì)觸發(fā)子類的初始化。
定義對(duì)象數(shù)組,不會(huì)觸發(fā)該類的初始化。
常量在編譯期間會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用定義常量的類,不會(huì)觸發(fā)定義常量所在的類。
通過類名獲取Class對(duì)象,不會(huì)觸發(fā)類的初始化。
通過Class.forName加載指定類時(shí),如果指定參數(shù)initialize為false時(shí),也不會(huì)觸發(fā)類初始化,其實(shí)這個(gè)參數(shù)是告訴虛擬機(jī),是否要對(duì)類進(jìn)行初始化。
通過ClassLoader默認(rèn)的loadClass方法,也不會(huì)觸發(fā)初始化動(dòng)作。
類加載器和雙親委派模型
啟動(dòng)類加載器(Bootstrap ClassLoader)。C++實(shí)現(xiàn),負(fù)責(zé)加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath參數(shù)指定路徑中的,且被虛擬機(jī)認(rèn)可(按文件名識(shí)別,如rt.jar)的類。
擴(kuò)展類加載器(Extension ClassLoader)。負(fù)責(zé)加載 JAVA_HOME\lib\ext 目錄中的,或通過java.ext.dirs系統(tǒng)變量指定路徑中的類庫(kù)。
應(yīng)用程序類加載器(Application ClassLoader)。負(fù)責(zé)加載用戶路徑(classpath)上的類庫(kù)。
當(dāng)一個(gè)類收到了類加載請(qǐng)求,他首先不會(huì)嘗試自己去加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類去完成,每一個(gè)層次類加載器都是如此,因此所有的加載請(qǐng)求都應(yīng)該傳送到啟動(dòng)類加載其中,只有當(dāng)父類加載器反饋?zhàn)约簾o(wú)法完成這個(gè)請(qǐng)求的時(shí)候(在它的加載路徑下沒有找到所需加載的Class),子類加載器才會(huì)嘗試自己去加載。 采用雙親委派的一個(gè)好處是比如加載位于rt.jar包中的類java.lang.Object,不管是哪個(gè)加載器加載這個(gè)類,最終都是委托給頂層的啟動(dòng)類加載器進(jìn)行加載,這樣就保證了使用不同的類加載器最終得到的都是同樣一個(gè)Object對(duì)象。

在這里插入圖片描述

雙親委派模型的好處:

安全性,避免了核心類被替換避免了類的重復(fù)加載,因?yàn)橥粋€(gè)類可以被不同的classLoader加載。

一些特點(diǎn):
1.全盤負(fù)責(zé),當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí),該Class所依賴的和引用的其他Class也將由該類加載器負(fù)責(zé)載入,除非顯示使用另外一個(gè)類加載器來載入。
2.父類委托,先讓父類加載器試圖加載該類,只有在父類加載器無(wú)法加載該類時(shí),才使用本類加載器從自己的類路徑中加載該類。
3.緩存機(jī)制,緩存機(jī)制將會(huì)保證所有加載過的Class都會(huì)被緩存,當(dāng)程序中需要使用某個(gè)Class時(shí),類加載器先從緩存區(qū)尋找該Class,只有緩存區(qū)不存在,系統(tǒng)才會(huì)讀取該類對(duì)應(yīng)的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)換成Class對(duì)象,存入緩存區(qū)。這就是為什么修改了Class后,必須重啟JVM,程序的修改才會(huì)生效。

java對(duì)象內(nèi)存相關(guān)

內(nèi)存分配機(jī)制

類加載完成后會(huì)在java堆中劃分區(qū)域分配給對(duì)象。分配包含兩種方式:

指針碰撞。如果Java堆的內(nèi)存是規(guī)整,即所有用過的內(nèi)存放在一邊,而空閑的放在另一邊。分配內(nèi)存時(shí)將位于中間的指針指示器向空閑的內(nèi)存移動(dòng)一段與對(duì)象大小相等的距離,這樣便完成分配內(nèi)存工作??臻e列表。如果Java堆的內(nèi)存不是規(guī)整的,則需要由虛擬機(jī)維護(hù)一個(gè)列表來記錄哪些內(nèi)存是可用的,這樣在分配的時(shí)候可以從列表中查詢到足夠大的內(nèi)存分配給對(duì)象,并在分配后更新列表記錄。

選擇哪種分配方式是由 Java 堆是否規(guī)整來決定的,而 Java 堆是否規(guī)整又由所 采用的垃圾收集器是否帶有壓縮整理功能決定。

那怎么解決內(nèi)存分配的并發(fā)問題呢?
一方面對(duì)分配內(nèi)存空間的行為進(jìn)行同步處理(采用CAS+失敗重試來保證同步);
另一方面給每個(gè)進(jìn)程在java堆中預(yù)分配一小塊空間(TLAB,Thread Local Allocation Buffer),對(duì)象現(xiàn)在tlab上分配空間而TLAB的分配才需要用到同步鎖。

從分區(qū)角度看,內(nèi)存一般在eden區(qū)分配,如果空間不夠進(jìn)行一次minor GC,如果還不夠則啟用擔(dān)保機(jī)制在老年代分配。特別的,對(duì)于大對(duì)象,直接進(jìn)入老年代。

訪問內(nèi)存中的對(duì)象

目前有兩種方式:句柄訪問和直接指針。
句柄訪問是指:Java堆中劃分出一塊內(nèi)存來作為句柄池,引用中存儲(chǔ)對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)(實(shí)例池)與對(duì)象類型數(shù)據(jù)(方法區(qū))各自的具體地址信息。這種方法的優(yōu)勢(shì):引用中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)(垃圾收集時(shí)移動(dòng)對(duì)象是非常普遍的行為)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而引用本身不需要修改。
直接指針:如果使用直接指針訪問,引用中存儲(chǔ)的直接就是對(duì)象地址,那么Java堆對(duì)象內(nèi)部的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息。優(yōu)勢(shì):速度更快,節(jié)省了一次指針定位的時(shí)間開銷。由于對(duì)象的訪問在Java中非常頻繁,因此這類開銷積少成多后也是非??捎^的執(zhí)行成本。HotSpot中采用的就是這種方式。

JVM調(diào)優(yōu)

JVM調(diào)優(yōu)可以考慮在以下幾個(gè)方面進(jìn)行:
線程池:解決用戶響應(yīng)時(shí)間長(zhǎng)的問題
連接池
JVM啟動(dòng)參數(shù):調(diào)整各代的內(nèi)存比例和垃圾回收算法,提高吞吐量
程序算法:改進(jìn)程序邏輯算法提高性能

GC調(diào)優(yōu)

在GC調(diào)優(yōu)之前,我們需要記住下面的原則:

  1. 多數(shù)的Java應(yīng)用不需要在服務(wù)器上進(jìn)行GC優(yōu)化;
  2. 多數(shù)導(dǎo)致GC問題的Java應(yīng)用,都不是因?yàn)槲覀儏?shù)設(shè)置錯(cuò)誤,而是代碼問題;
  3. 在應(yīng)用上線之前,先考慮將機(jī)器的JVM參數(shù)設(shè)置到最優(yōu)(最適合);
  4. 減少創(chuàng)建對(duì)象的數(shù)量;
  5. 減少使用全局變量和大對(duì)象;
  6. GC優(yōu)化是到最后不得已才采用的手段;
  7. 在實(shí)際使用中,分析GC情況優(yōu)化代碼比優(yōu)化GC參數(shù)要多得多;

GC優(yōu)化的目的有兩個(gè):

  1. 將轉(zhuǎn)移到老年代的對(duì)象數(shù)量降低到最?。?/li>
  2. 減少full GC的執(zhí)行時(shí)間;

為了達(dá)到上面的目的,一般地,你需要做的事情有:

  1. 減少使用全局變量和大對(duì)象;
  2. 調(diào)整新生代的大小到最合適;
  3. 設(shè)置老年代的大小為最合適;
  4. 選擇合適的GC收集器;

 到此這篇關(guān)于Java面試必備八股文整理的文章就介紹到這了,更多相關(guān)Java面試八股文內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java 并發(fā)編程:volatile的使用及其原理解析

    Java 并發(fā)編程:volatile的使用及其原理解析

    下面小編就為大家?guī)硪黄狫ava 并發(fā)編程:volatile的使用及其原理解析。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-05-05
  • IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文(必看)

    IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文(必看)

    這篇文章主要介紹了IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文了,本文通過截圖的形式給大家展示,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 解決Java?API不能遠(yuǎn)程訪問HBase的問題

    解決Java?API不能遠(yuǎn)程訪問HBase的問題

    這篇文章主要介紹了解決Java?API不能遠(yuǎn)程訪問HBase的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 基于RabbitMQ的簡(jiǎn)單應(yīng)用(詳解)

    基于RabbitMQ的簡(jiǎn)單應(yīng)用(詳解)

    下面小編就為大家分享一篇基于RabbitMQ的簡(jiǎn)單應(yīng)用(詳解),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2017-11-11
  • SpringCloud集成Eureka并實(shí)現(xiàn)負(fù)載均衡的過程詳解

    SpringCloud集成Eureka并實(shí)現(xiàn)負(fù)載均衡的過程詳解

    這篇文章主要給大家詳細(xì)介紹了SpringCloud集成Eureka并實(shí)現(xiàn)負(fù)載均衡的過程,文章通過代碼示例和圖文講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的參考價(jià)值,需要的朋友可以參考下
    2023-11-11
  • BigDecimal的toString()、toPlainString()和toEngineeringString()區(qū)別及用法詳解

    BigDecimal的toString()、toPlainString()和toEngineeringString()區(qū)

    使用BigDecimal進(jìn)行打印的時(shí)候,經(jīng)常會(huì)對(duì)BigDecimal提供的三個(gè)toString方法感到好奇,以下整理3個(gè)toString方法的區(qū)別及用法,需要的朋友可以參考下
    2023-08-08
  • hibernate關(guān)于session的關(guān)閉實(shí)例解析

    hibernate關(guān)于session的關(guān)閉實(shí)例解析

    這篇文章主要介紹了hibernate關(guān)于session的關(guān)閉實(shí)例解析,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • Java之jdbc連接mysql數(shù)據(jù)庫(kù)的方法步驟詳解

    Java之jdbc連接mysql數(shù)據(jù)庫(kù)的方法步驟詳解

    這篇文章主要介紹了Java之jdbc連接mysql數(shù)據(jù)庫(kù)的方法步驟詳解,JCBC技術(shù)是java開發(fā)必備的只是,jdbc連接mysql數(shù)據(jù)庫(kù),這是一個(gè)比較簡(jiǎn)單的方法,有興趣的可以了解一下
    2020-07-07
  • spring的pointcut正則表達(dá)式的實(shí)現(xiàn)

    spring的pointcut正則表達(dá)式的實(shí)現(xiàn)

    本文主要介紹了spring的pointcut正則表達(dá)式的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • java 多態(tài)與抽象類詳解總結(jié)

    java 多態(tài)與抽象類詳解總結(jié)

    在面向?qū)ο蟮母拍钪?,所有的?duì)象都是通過類來描繪的,但是反過來,并不是所有的類都是用來描繪對(duì)象的,如果一個(gè)類中沒有包含足夠的信息來描繪一個(gè)具體的對(duì)象,這樣的類就是抽象類,而多態(tài)是同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力
    2021-11-11

最新評(píng)論