招銀網絡後臺開發電話面經

目錄html

 

一、自我介紹java

二、介紹一下微服務架構(由於研究方向是微服務架構)程序員

三、微服務架構與SOA有什麼區別?算法

四、微服務架構的服務粒度怎麼肯定,服務怎麼通訊?spring

五、java中的樂觀鎖與悲觀鎖數據庫

六、equals與hashcode的區別,存入集合時的判斷過程編程

七、java的內存模型,什麼數據放在什麼區域網絡

八、垃圾回收算法,新生代和年老代用的什麼算法,爲何用這個算法?session

九、數據庫事務的隔離級別?多線程


一、自我介紹

二、介紹一下微服務架構(由於研究方向是微服務架構)

2.一、什麼事爲服務架構

    形象一點來講,微服務架構就像搭積木,每一個微服務都是一個零件,並使用這個零件組裝出不一樣的形狀。通俗來講,微服務架構就是把一個大系統按業務功能分解成多個職責單一的小系統,並利用簡單的方法使多個小系統相互協做,組成一個大系統。

若是學科派一點,微服務架構就是把因相同緣由而變化的功能聚合到一塊兒,而把因不一樣緣由而變化的功能分離開,並利用輕量化機制(一般爲HTTP RESTful  API)實現通訊。

追本溯源,Martin Folwer對微服務架構的定義是:

微服務架構是一種架構模式,它提倡將單一應用程序劃分紅一組小的服務,服務之間互相協調、互相配合,爲用戶提供最終價值。每一個服務運行在其獨立的進程中,服務與服務間採用輕量級的通訊機制互相協做(一般是基於HTTP協議的RESTful API)。每一個服務都圍繞着具體業務進行構建,而且可以被獨立的部署到生產環境、類生產環境等。另外,對具體的服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建。

對於我我的,我更喜歡一種延續性的解釋,微服務架構≈ 模塊化開發+分佈式計算。無論微服務架構的定義怎麼樣,都是在描述一個核心思想:把大系統拆分紅小型系統,把大事化小,以下降系統的複雜性,從而大幅下降系統建設、升級、運維的風險和成本。

順帶提一下,亞馬遜創始人Jeff Bezos在2002年就說過:全部團隊的模塊都要以Service Interface的方式將數據和功能開放出來。不這樣作的人會被炒魷魚。這纔是思路超前的大牛。

須要注意的是「微服務」與「微服務架構」是有本質區別的。「微服務」強調的是服務的大小,它關注的是某一個點。而「微服務架構」則是一種架構思想,須要從總體上對軟件系統進行通盤的考慮。

Chris Richardson說:「微服務」是一個很糟糕的名字,它致使開發人員建立了許多粒度很小的服務,每一個服務擁有一個單獨的REST端點。不只如此,這個名字還暗示了微服務在開發者心目中的重要位置。例如,人們會說「咱們能夠用微服務來解決這個問題」;我也看到了愈來愈多的「某某微服務框架」,而實際上,這些框架跟微服務架構不必定有太多聯繫,它們只是簡單的Web框架(如:spring-boot)。使用「微服務架構」這個名字會更恰當些。它是一種架構風格,它把一系列協做的服務組織成一個系統來支撐業務。

1.二、常見的微服務組件

1.服務註冊:服務提供方將本身調用地址註冊到服務註冊中心,讓服務調用方可以方便地找到本身。

2.服務發現:服務調用方從服務註冊中心找到本身須要調用的服務的地址。

3.負載均衡:服務提供方通常以多實例的形式提供服務,負載均衡功能可以讓服務調用方鏈接到合適的服務節點。而且,節點選擇的工做對服務調用方來講是透明的。

4.服務網關:服務網關是服務調用的惟一入口,能夠在這個組件是實現用戶鑑權、動態路由、灰度發佈、A/B測試、負載限流等功能。

5.配置中心:將本地化的配置信息(properties,xml, yaml等)註冊到配置中心,實現程序包在開發、測試、生產環境的無差異性,方便程序包的遷移。

6.API管理:以方便的形式編寫及更新API文檔,並以方便的形式供調用者查看和測試。

7.集成框架:微服務組件都以職責單一的程序包對外提供服務,集成框架以配置的形式將全部微服務組件(特別是管理端組件)集成到統一的界面框架下,讓用戶可以在統一的界面中使用系統。

8.分佈式事務:對於重要的業務,須要經過分佈式事務技術(TCC、高可用消息服務、最大努力通知)保證數據的一致性。具備表明性的有spring transaction

9.調用鏈:記錄完成一個業務邏輯時調用到的微服務,並將這種串行或並行的調用關係展現出來。在系統出錯時,能夠方便地找到出錯點。具備表明性的有pinpoint.

10.支撐平臺:系統微服務化後,系統變得更加碎片化,系統的部署、運維、監控等都比單體架構更加複雜,那麼,就須要將大部分的工做自動化。如今,能夠經過Docker等工具來中和這些微服務架構帶來的弊端。 例如:持續集成、藍綠髮布、健康檢查、性能健康等等。嚴重點,以咱們兩年的實踐經驗,能夠這麼說,若是沒有合適的支撐平臺或工具,就不要使用微服務架構。

通常狀況下,若是但願快速地體會微服務架構帶來的好處,使用Spring Cloud提供的服務註冊(Eureka)、服務發現(Ribbon)、服務網關(Zuul) 三個組件便可以快速入門。其它組件則須要根據自身的業務選擇性使用。

1.3微服務架構有哪些優點劣勢?

要談優點,就必定要有對比,咱們能夠嘗試着從兩個維度來對比:

1.3.1單體架構和微服務架構的對比

【結論】:

n對於簡單項目來講,單體架構5勝8敗。

優點項:開發效率、上手難度、運維效率、硬件需求、項目成本;劣勢項:系統設計(高內聚低耦合)、系統設計(擴展性)、需求變動響應速度、系統升級效率、知識積累、非功能需求、職責、成就感、風險)

n對於複雜項目來講,單體架構2勝11敗。

優點項:上手難度、運維效率;劣勢項:硬件需求、項目成本、開發效率、系統設計(高內聚低耦合)、系統設計(擴展性)、需求變動響應速度、系統升級效率、知識積累、非功能需求、職責、成就感、風險;)

1.3.2微服務與共享庫的對比.,

1.3.3什麼場景須要用微服務架構?

看了上面的比較,微服務架構能夠說是以壓倒性的優點賽過單體架構和共享庫,會讓人產生一種錯覺,微服務架構就是軟件開發中的銀彈。

可是,正如你們所瞭解的,軟件研發是一個系統工程,它沒有銀彈,不可以一招鮮吃遍天。正如當年的CMMI和敏捷方法同樣,敏捷雖好,但它不必定能適用於全部的場景,它對組織環境、團隊氛圍、溝通方式、技術能力這些都是有一些要求的,若是用很差,反而會帶來一些負面影響。

那麼,咱們何時須要採用微服務呢?從我我的的經驗來看,我認爲有三種場景能夠考慮使用微服務。

1.規模大(團隊超過10人)

2.業務複雜度高(系統超過5個子模塊)

3.須要長期演進(項目開發和維護週期超過半年)

 

三、微服務架構與SOA有什麼區別?

首先SOA和微服務架構一個層面的東西,而對於ESB和微服務網關是一個層面的東西,一個談的是架構風格和方法,一個談的是實現工具或組件。

 1.SOA(Service Oriented Architecture)「面向服務的架構」:他是一種設計方法,其中包含多個服務, 服務之間經過相互依賴最終提供一系列的功能。一個服務一般以獨立的形式存在與操做系統進程中。各個服務之間 經過網絡調用。

 2.微服務架構:其實和 SOA 架構相似,微服務是在 SOA 上作的昇華,微服務架構強調的一個重點是「業務須要完全的組件化和服務化」,原有的單個業務系統會拆分爲多個能夠獨立開發、設計、運行的小應用。這些小應用之間經過服務完成交互和集成。

 微服務架構 = 80%的SOA服務架構思想 + 100%的組件化架構思想 + 80%的領域建模思想
--------------------

四、微服務架構的服務粒度怎麼肯定,服務怎麼通訊?

五、java中的樂觀鎖與悲觀鎖

樂觀鎖:得到鎖後一直持有鎖以防本線程再次申請該鎖形成無畏的解鎖再加鎖開銷,或者假設沒有衝突而去完成同步代碼塊,若是衝突再循環重試,或者採起申請鎖失敗後不馬上掛起而是稍微等待再次嘗試獲取的等待策略減小線程由於掛起、阻塞、喚醒(發生CPU的調度切換)而形成的開銷。

偏向鎖、輕量級鎖(CAS輪詢)、自旋鎖就是基於上述思路的樂觀鎖。

悲觀鎖:悲觀鎖就是無論是否發生多線程衝突,只要存在這種可能,就每次訪問都加鎖,加鎖就會致使鎖之間的爭奪,有爭奪就會有輸贏,輸者等待。

syncrhoized是一種獨佔鎖,即:佔用該鎖的線程才能夠執行,申請該鎖的線程只能掛起等待,直到佔用鎖的線程釋放鎖才喚醒,拿到鎖並執行。因此syncrhoized是一種悲觀鎖,凡是用syncrhoized加了鎖的多線程之間都會因鎖的爭奪而致使掛起、喚醒等開銷。

在多線程的加鎖機制中,JVM會首先嚐試樂觀鎖,失敗後才調用悲觀鎖。

六、equals與hashcode的區別,存入集合時的判斷過程

先來試想一個場景,若是你想查找一個集合中是否包含某個對象,那麼程序應該怎麼寫呢?一般的作法是逐一取出每一個元素與要查找的對象一一比較,當發現二者進行equals比較結果相等時,則中止查找並返回true,不然,返回false。可是這個作法的一個缺點是當集合中的元素不少時,譬若有一萬個元素,那麼逐一的比較效率勢必降低很快。因而有人發明了一種哈希算法來提升從該集合中查找元素的效率,這種方式將集合分紅若干個存儲區域(能夠當作一個個桶),每一個對象能夠計算出一個哈希碼,能夠根據哈希碼分組,每組分別對應某個存儲區域,這樣一個對象根據它的哈希碼就能夠分到不一樣的存儲區域(不一樣的桶中)。以下圖所示:

                                            

       實際的使用中,一個對象通常有key和value,能夠根據key來計算它的hashCode。假設如今所有的對象都已經根據本身的hashCode值存儲在不一樣的存儲區域中了,那麼如今查找某個對象(根據對象的key來查找),不須要遍歷整個集合了,如今只須要計算要查找對象的key的hashCode,而後找到該hashCode對應的存儲區域,在該存儲區域中來查找就能夠了,這樣效率也就提高了不少。說了這麼多相信你對hashCode的做用有了必定的瞭解,下面就來看看hashCode和equals的區別和聯繫。

       在研究這個問題以前,首先說明一下JDK對equals(Object obj)和hashCode()這兩個方法的定義和規範:在Java中任何一個對象都具有equals(Object obj)和hashCode()這兩個方法,由於他們是在Object類中定義的。 equals(Object obj)方法用來判斷兩個對象是否「相同」,若是「相同」則返回true,不然返回false。 hashCode()方法返回一個int數,在Object類中的默認實現是「將該對象的內部地址轉換成一個整數返回」。 

下面是官方文檔給出的一些說明:

  1. <span style="font-size:18px;">hashCode 的常規協定是:     
  2. 在 Java 應用程序執行期間,在同一對象上屢次調用 hashCode 方法時,必須一致地返回相同的整數,前提是對象上 equals 比較中所用的信息沒有被修改。從某一應用程序的一次執行到同一應用程序的另外一次執行,該整數無需保持一致。     
  3. 若是根據 equals(Object) 方法,兩個對象是相等的,那麼在兩個對象中的每一個對象上調用 hashCode 方法都必須生成相同的整數結果。     
  4. 如下狀況不 是必需的:若是根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼在兩個對象中的任一對象上調用 hashCode 方法一定會生成不一樣的整數結果。可是,程序員應該知道,爲不相等的對象生成不一樣整數結果能夠提升哈希表的性能。     
  5. 實際上,由 Object 類定義的 hashCode 方法確實會針對不一樣的對象返回不一樣的整數。(這通常是經過將該對象的內部地址轉換成一個整數來實現的,可是 JavaTM 編程語言不須要這種實現技巧。)     
  6.     
  7. 當equals方法被重寫時,一般有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。</span>  


下面是我查閱了相關資料以後對以上的說明作的概括總結:

1.若重寫了equals(Object obj)方法,則有必要重寫hashCode()方法。

2.若兩個對象equals(Object obj)返回true,則hashCode()有必要也返回相同的int數。

3.若兩個對象equals(Object obj)返回false,則hashCode()不必定返回不一樣的int數。

4.若兩個對象hashCode()返回相同int數,則equals(Object obj)不必定返回true。

5.若兩個對象hashCode()返回不一樣int數,則equals(Object obj)必定返回false。

6.同一對象在執行期間若已經存儲在集合中,則不能修改影響hashCode值的相關信息,不然會致使內存泄露問題。

       想要弄清楚以上六點,先要知道何時須要重寫equals和hashCode。通常來講涉及到對象之間的比較大小就須要重寫equals方法,可是爲何第一點說重寫了equals就須要重寫hashCode呢?實際上這只是一條規範,若是不這樣作程序也能夠執行,只不過會隱藏bug。通常一個類的對象若是會存儲在HashTable,HashSet,HashMap等散列存儲結構中,那麼重寫equals後最好也重寫hashCode,不然會致使存儲數據的不惟一性(存儲了兩個equals相等的數據)。而若是肯定不會存儲在這些散列結構中,則能夠不重寫hashCode。可是我的以爲仍是重寫比較好一點,誰能保證後期不會存儲在這些結構中呢,何況重寫了hashCode也不會下降性能,由於在線性結構(如ArrayList)中是不會調用hashCode,因此重寫了也沒關係,也爲後期的修改打了補丁。

 

下面來看一張對象放入散列集合的流程圖:

 

       從上面的圖中能夠清晰地看到在存儲一個對象時,先進行hashCode值的比較,而後進行equals的比較。可能如今你已經對上面的6點概括有了一些認識。咱們還能夠經過JDK中得源碼來認識一下具體hashCode和equals在代碼中是如何調用的。

HashSet.java 

 

  1. <span style="font-size:18px;">  public boolean add(E e) {  
  2.     return map.put(e, PRESENT)==null;  
  3.     }</span>  


HashMap.java

 

  1. <span style="font-size:18px;">    public V put(K key, V value) {  
  2.         if (key == null)  
  3.             return putForNullKey(value);  
  4.         int hash = hash(key.hashCode());  
  5.         int i = indexFor(hash, table.length);  
  6.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  7.             Object k;  
  8.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  9.                 V oldValue = e.value;  
  10.                 e.value = value;  
  11.                 e.recordAccess(this);  
  12.                 return oldValue;  
  13.             }  
  14.         }  
  15.   
  16.         modCount++;  
  17.         addEntry(hash, key, value, i);  
  18.         return null;  
  19.     }</span>  

 

最後再來看幾個測試的例子吧:

測試一:覆蓋equals(Object obj)但不覆蓋hashCode(),致使數據不惟一性

 

  1. <span style="font-size:18px;">public class HashCodeTest {  
  2.     public static void main(String[] args) {  
  3.         Collection set = new HashSet();  
  4.         Point p1 = new Point(1, 1);  
  5.         Point p2 = new Point(1, 1);  
  6.   
  7.         System.out.println(p1.equals(p2));  
  8.         set.add(p1);   //(1)  
  9.         set.add(p2);   //(2)  
  10.         set.add(p1);   //(3)  
  11.   
  12.         Iterator iterator = set.iterator();  
  13.         while (iterator.hasNext()) {  
  14.             Object object = iterator.next();  
  15.             System.out.println(object);  
  16.         }  
  17.     }  
  18. }  
  19.   
  20. class Point {  
  21.     private int x;  
  22.     private int y;  
  23.   
  24.     public Point(int x, int y) {  
  25.         super();  
  26.         this.x = x;  
  27.         this.y = y;  
  28.     }  
  29.   
  30.     @Override  
  31.     public boolean equals(Object obj) {  
  32.         if (this == obj)  
  33.             return true;  
  34.         if (obj == null)  
  35.             return false;  
  36.         if (getClass() != obj.getClass())  
  37.             return false;  
  38.         Point other = (Point) obj;  
  39.         if (x != other.x)  
  40.             return false;  
  41.         if (y != other.y)  
  42.             return false;  
  43.         return true;  
  44.     }  
  45.   
  46.     @Override  
  47.     public String toString() {  
  48.         return "x:" + x + ",y:" + y;  
  49.     }  
  50.   
  51. }  
  52. </span>  


 

 

輸出結果:

 

[java] view plain copy

  在CODE上查看代碼片派生到個人代碼片

  1. <span style="font-size:18px;">true  
  2. x:1,y:1  
  3. x:1,y:1  
  4. </span>  


緣由分析:

 

(1)當執行set.add(p1)時(1),集合爲空,直接存入集合;

(2)當執行set.add(p2)時(2),首先判斷該對象(p2)的hashCode值所在的存儲區域是否有相同的hashCode,由於沒有覆蓋hashCode方法,因此jdk使用默認Object的hashCode方法,返回內存地址轉換後的整數,由於不一樣對象的地址值不一樣,因此這裏不存在與p2相同hashCode值的對象,所以jdk默認不一樣hashCode值,equals必定返回false,因此直接存入集合。

 (3)當執行set.add(p1)時(3),時,由於p1已經存入集合,同一對象返回的hashCode值是同樣的,繼續判斷equals是否返回true,由於是同一對象因此返回true。此時jdk認爲該對象已經存在於集合中,因此捨棄。

 

測試二:覆蓋hashCode方法,但不覆蓋equals方法,仍然會致使數據的不惟一性

修改Point類:

[java] view plain copy

  在CODE上查看代碼片派生到個人代碼片

  1. <span style="font-size:18px;">class Point {  
  2.     private int x;  
  3.     private int y;  
  4.   
  5.     public Point(int x, int y) {  
  6.         super();  
  7.         this.x = x;  
  8.         this.y = y;  
  9.     }  
  10.   
  11.     @Override  
  12.     public int hashCode() {  
  13.         final int prime = 31;  
  14.         int result = 1;  
  15.         result = prime * result + x;  
  16.         result = prime * result + y;  
  17.         return result;  
  18.     }  
  19.   
  20.     @Override  
  21.     public String toString() {  
  22.         return "x:" + x + ",y:" + y;  
  23.     }  
  24.   
  25. }  
  26. </span>  


輸出結果:

[java] view plain copy

  在CODE上查看代碼片派生到個人代碼片

  1. <span style="font-size:18px;">false  
  2. x:1,y:1  
  3. x:1,y:1</span>  

 

 

緣由分析:

(1)當執行set.add(p1)時(1),集合爲空,直接存入集合;

(2)當執行set.add(p2)時(2),首先判斷該對象(p2)的hashCode值所在的存儲區域是否有相同的hashCode,這裏覆蓋了hashCode方法,p1和p2的hashCode相等,因此繼續判斷equals是否相等,由於這裏沒有覆蓋equals,默認使用'=='來判斷,因此這裏equals返回false,jdk認爲是不一樣的對象,因此將p2存入集合。

 (3)當執行set.add(p1)時(3),時,由於p1已經存入集合,同一對象返回的hashCode值是同樣的,而且equals返回true。此時jdk認爲該對象已經存在於集合中,因此捨棄。

 

綜合上述兩個測試,要想保證元素的惟一性,必須同時覆蓋hashCode和equals才行。

(注意:在HashSet中插入同一個元素(hashCode和equals均相等)時,會被捨棄,而在HashMap中插入同一個Key(Value 不一樣)時,原來的元素會被覆蓋。)

 

測試三:在內存泄露問題

[java] view plain copy

  在CODE上查看代碼片派生到個人代碼片

  1. <span style="font-size:18px;">public class HashCodeTest {  
  2.     public static void main(String[] args) {  
  3.         Collection set = new HashSet();  
  4.         Point p1 = new Point(1, 1);  
  5.         Point p2 = new Point(1, 2);  
  6.   
  7.         set.add(p1);  
  8.         set.add(p2);  
  9.           
  10.         p2.setX(10);  
  11.         p2.setY(10);  
  12.           
  13.         set.remove(p2);  
  14.   
  15.         Iterator iterator = set.iterator();  
  16.         while (iterator.hasNext()) {  
  17.             Object object = iterator.next();  
  18.             System.out.println(object);  
  19.         }  
  20.     }  
  21. }  
  22.   
  23. class Point {  
  24.     private int x;  
  25.     private int y;  
  26.   
  27.     public Point(int x, int y) {  
  28.         super();  
  29.         this.x = x;  
  30.         this.y = y;  
  31.     }  
  32.   
  33.   
  34.     public int getX() {  
  35.         return x;  
  36.     }  
  37.   
  38.   
  39.     public void setX(int x) {  
  40.         this.x = x;  
  41.     }  
  42.   
  43.   
  44.     public int getY() {  
  45.         return y;  
  46.     }  
  47.   
  48.   
  49.     public void setY(int y) {  
  50.         this.y = y;  
  51.     }  
  52.   
  53.   
  54.     @Override  
  55.     public int hashCode() {  
  56.         final int prime = 31;  
  57.         int result = 1;  
  58.         result = prime * result + x;  
  59.         result = prime * result + y;  
  60.         return result;  
  61.     }  
  62.   
  63.   
  64.     @Override  
  65.     public boolean equals(Object obj) {  
  66.         if (this == obj)  
  67.             return true;  
  68.         if (obj == null)  
  69.             return false;  
  70.         if (getClass() != obj.getClass())  
  71.             return false;  
  72.         Point other = (Point) obj;  
  73.         if (x != other.x)  
  74.             return false;  
  75.         if (y != other.y)  
  76.             return false;  
  77.         return true;  
  78.     }  
  79.   
  80.   
  81.     @Override  
  82.     public String toString() {  
  83.         return "x:" + x + ",y:" + y;  
  84.     }  
  85.   
  86. }  
  87. </span>  

 

運行結果:
[java] view plain copy

  在CODE上查看代碼片派生到個人代碼片

  1. <span style="font-size:18px;">x:1,y:1  
  2. x:10,y:10</span>  

 

緣由分析:

       假設p1的hashCode爲1,p2的hashCode爲2,在存儲時p1被分配在1號桶中,p2被分配在2號筒中。這時修改了p2中與計算hashCode有關的信息(x和y),當調用remove(Object obj)時,首先會查找該hashCode值得對象是否在集合中。假設修改後的hashCode值爲10(仍存在2號桶中),這時查找結果空,jdk認爲該對象不在集合中,因此不會進行刪除操做。然而用戶覺得該對象已經被刪除,致使該對象長時間不能被釋放,形成內存泄露。解決該問題的辦法是不要在執行期間修改與hashCode值有關的對象信息,若是非要修改,則必須先從集合中刪除,更新信息後再加入集合中。

 

總結:

       1.hashCode是爲了提升在散列結構存儲中查找的效率,在線性表中沒有做用。

       2.equals和hashCode須要同時覆蓋。

       3.若兩個對象equals返回true,則hashCode有必要也返回相同的int數。

       4.若兩個對象equals返回false,則hashCode不必定返回不一樣的int數,但爲不相等的對象生成不一樣hashCode值能夠提升哈希表的性能。

       5.若兩個對象hashCode返回相同int數,則equals不必定返回true。

       6.若兩個對象hashCode返回不一樣int數,則equals必定返回false。

       7.同一對象在執行期間若已經存儲在集合中,則不能修改影響hashCode值的相關信息,不然會致使內存泄露問題。

七、java的內存模型,什麼數據放在什麼區域

程序計數器(線程私有):

  是當前線程所執行的字節碼的行號指示器,每條線程都要有一個獨立的程序計數器,這類內存也稱爲「線程私有」的內存。

  正在執行java方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指令的地址)。若是仍是Native方法,則爲空。

  這個內存區域是惟一一個在虛擬機中沒有規定任何OutOfMemoryError狀況的區域。

Java虛擬機棧(線程私有):

  也是線程私有的。

  每一個方法在執行的時候會建立一個棧幀,存儲了局部變量表,操做數棧,動態鏈接,方法返回地址等。

  每一個方法從調用到執行完畢,對應一個棧幀在虛擬機棧中的入棧和出棧。

  一般所說的棧,通常是指虛擬機棧中的局部變量表部分。

  局部變量表所需的內存在編譯期間完成分配。

  若是線程請求的棧深度大於虛擬機所容許的深度,則StackOverflowError。

   若是虛擬機棧能夠動態擴展,擴展到沒法申請足夠的內存,則OutOfMemoryError。

本地方法棧(線程私有):

  和虛擬機棧相似,主要爲虛擬機使用到的Native方法服務

  也會拋出StackOverflowError和OutOfMemoryError。

Java堆(線程共享):

  被全部線程共享的一塊內存區域,在虛擬機啓動時建立,用於存放對象實例

  堆能夠按照可擴展來實現(經過-Xmx和-Xms來控制)

  當堆中沒有內存能夠分配給實例,也沒法再擴展時,則拋出OutOfMemoryError異常。

方法區(線程共享):

  被全部線程共享的一塊內存區域。

  用於存儲已被虛擬機加載的類信息,常量,靜態變量等。

  這個區域的內存回收目標主要針對常量池的回收和對類型的卸載。

  當方法區沒法知足內存分配需求時,則拋出OutOfMemoryError異常。

  在HotSpot虛擬機中,用永久代來實現方法區,將GC分代收集擴展至方法區,可是這樣容易遇到內存溢出的問題。

  JDK1.7中,已經把放在永久代的字符串常量池移到堆中。

  JDK1.8撤銷永久代,引入元空間。

 

八、垃圾回收算法,新生代和年老代用的什麼算法,爲何用這個算法?

1. 標記清除算法

 標記清除算法是最基礎的回收算法,分爲標記和清除兩個部分:首先標記出全部須要回收的對象,這一過程在可達性分析過程當中進行。在標記完以後統一回收全部被標記的對象。

 標記清除算法有以下不足:

效率問題:
 標記和清除這兩個過程的效率不高

空間問題
 清除以後會產生大量不連續的內存碎片,內存碎片太多會致使之後的程序運行中沒法分配出較大的內存,從內不得不觸發另外的垃圾回收。

 如上圖中,通過標記清除以後,假設有了100M空間,可是這100M是不連續的,最大的一塊連續空間可能才10M,因此致使以後程序須要一塊20M內存空間時就不得再也不進行一次GC來繼續清理空間,效率極低。

 鑑於標記清除算法有如上的缺陷,因此如今通常是用的是其的變種算法。

2. 複製算法(新生代算法)

2.1 複製算法概念

 複製算法是針對Java堆中的新生代內存垃圾回收所使用的回收策略,解決了」標記-清理」的效率問題。

 複製算法將堆中可用的新生代內存按容量劃分紅大小相等的兩塊內存區域,每次只使用其中的一塊區域。當其中一塊內存區域須要進行垃圾回收時,會將此區域內還存活着的對象複製到另外一塊上面,而後再把此內存區域一次性清理掉。

 這樣作的好處是每次都是對整個新生代一半的內存區域進行內存回收,內存分配時也就不須要考慮內存碎片等複雜狀況,只須要移動堆頂指針,按順序分配便可。此算法實現簡單,運行高效。算法的執行流程以下圖 :

 如今主流的虛擬機,包括HotSpot都是採用的這種回收策略進行新生代內存的回收。

2.2 新生代內存劃分

 新生代中98%的對象都是」朝生夕死」的,因此並不須要按照1 : 1的比例來劃份內存空間,而是將內存(新生代內存)分爲一塊較大的Eden(伊甸園)空間和兩塊較小的Survivor(倖存者)空間,每次使用Eden和其中一塊Survivor(兩個Survivor區域一個稱爲From區,另外一個稱爲To區域)。

 當進行垃圾回收時,將Eden和Survivor中還存活的對象一次性複製到另外一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。

 當Survivor空間不夠用時,則須要依賴其餘內存(老年代)進行分配擔保。

 HotSpot默認Eden與Survivor的大小比例是8 : 1,也就是說Eden:Survivor From : Survivor To = 8:1:1。因此每次新生代可用內存空間爲整個新生代容量的90%,而剩下的10%用來存放回收後存活的對象。

HotSpot實現的複製算法流程以下:

 1. 當Eden區滿的時候,會觸發第一次Minor gc,把還活着的對象拷貝到Survivor From區;當Eden區再次觸發Minor gc的時候,會掃描Eden區和From區域,對兩個區域進行垃圾回收,通過此次回收後還存活的對象,則直接複製到To區域,並將Eden和From區域清空。

 2. 當後續Eden又發生Minor gc的時候,會對Eden和To區域進行垃圾回收,存活的對象複製到From區域,並將Eden和To區域清空。

 3. 部分對象會在From和To區域中複製來複制去,如此交換15次(由JVM參數MaxTenuringThreshold決定,這個參數默認是15),最終若是仍是存活,就存入到老年代。

 發生在新生代的垃圾回收成爲Minor GC,Minor GC又稱爲新生代GC,由於新生代對象大多都具有朝生夕滅的特性,所以Minor GC(採用複製算法)很是頻繁,通常回收速度也比較快。

3. 標記整理算法(老年代回收算法)

 複製算法在對象存活率較高的老年代會進行不少次的複製操做,效率很低,因此在棧的老年代不適用複製算法。

 針對老年代對象存活率高的特色,提出了一種稱之爲」標記-整理算法」。標記過程仍與」標記-清除」過程一致,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活對象都向一端移動,而後直接清理掉端邊界之外的內存。流程圖以下:

 發生在老年代的GC稱爲Full GC,又稱爲Major GC,其常常會伴隨至少一次的Minor GC(並不是絕對,在Parallel Scavenge收集器中就有直接進行Full GC的策略選擇過程)。Major GC的速度通常會比Minor GC慢10倍以上。
 

九、數據庫事務的隔離級別?

若是一個數據庫聲稱支持事務的操做,那麼該數據庫必需要具有如下四個特性:

⑴ 原子性(Atomicity)

  原子性是指事務包含的全部操做要麼所有成功,要麼所有失敗回滾,這和前面兩篇博客介紹事務的功能是同樣的概念,所以事務的操做若是成功就必需要徹底應用到數據庫,若是操做失敗則不能對數據庫有任何影響。

⑵ 一致性(Consistency)

  一致性是指事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態,也就是說一個事務執行以前和執行以後都必須處於一致性狀態。

  拿轉帳來講,假設用戶A和用戶B二者的錢加起來一共是5000,那麼無論A和B之間如何轉帳,轉幾回帳,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。

⑶ 隔離性(Isolation)

  隔離性是當多個用戶併發訪問數據庫時,好比操做同一張表時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做所幹擾,多個併發事務之間要相互隔離。

  即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始以前就已經結束,要麼在T1結束以後纔開始,這樣每一個事務都感受不到有其餘事務在併發地執行。

  關於事務的隔離性數據庫提供了多種隔離級別,稍後會介紹到。

⑷ 持久性(Durability)

  持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。

  例如咱們在使用JDBC操做數據庫時,在提交事務方法後,提示用戶事務操做完成,當咱們程序執行完成直到看到提示後,就能夠認定事務以及正確提交,即便這時候數據庫出現了問題,也必需要將咱們的事務徹底執行完成,不然就會形成咱們看到提示事務處理完畢,可是數據庫由於故障而沒有執行事務的重大錯誤。

  

  以上介紹完事務的四大特性(簡稱ACID),如今重點來講明下事務的隔離性,當多個線程都開啓事務操做數據庫中的數據時,數據庫系統要能進行隔離操做,以保證各個線程獲取數據的準確性,在介紹數據庫提供的各類隔離級別以前,咱們先看看若是不考慮事務的隔離性,會發生的幾種問題:

1,髒讀

  髒讀是指在一個事務處理過程裏讀取了另外一個未提交的事務中的數據。

  當一個事務正在屢次修改某個數據,而在這個事務中這屢次的修改都還未提交,這時一個併發的事務來訪問該數據,就會形成兩個事務獲得的數據不一致。例如:用戶A向用戶B轉帳100元,對應SQL命令以下

update account set money=money+100 where name=’B’;  (此時A通知B)

    update account set money=money - 100 where name=’A’;

  當只執行第一條SQL時,A通知B查看帳戶,B發現確實錢已到帳(此時即發生了髒讀),而以後不管第二條SQL是否執行,只要該事務不提交,則全部操做都將回滾,那麼當B之後再次查看帳戶時就會發現錢其實並無轉。

2,不可重複讀

  不可重複讀是指在對於數據庫中的某個數據,一個事務範圍內屢次查詢卻返回了不一樣的數據值,這是因爲在查詢間隔,被另外一個事務修改並提交了。

  例如事務T1在讀取某一數據,而事務T2立馬修改了這個數據而且提交事務給數據庫,事務T1再次讀取該數據就獲得了不一樣的結果,發送了不可重複讀。

  不可重複讀和髒讀的區別是,髒讀是某一事務讀取了另外一個事務未提交的髒數據,而不可重複讀則是讀取了前一事務提交的數據。

  在某些狀況下,不可重複讀並非問題,好比咱們屢次查詢某個數據固然以最後查詢獲得的結果爲主。但在另外一些狀況下就有可能發生問題,例如對於同一個數據A和B依次查詢就可能不一樣,A和B就可能打起來了……

3,虛讀(幻讀)

  幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中全部的行的某個數據項作了從「1」修改成「2」的操做,這時事務T2又對這個表中插入了一行數據項,而這個數據項的數值仍是爲「1」而且提交給數據庫。而操做事務T1的用戶若是再查看剛剛修改的數據,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像產生幻覺同樣,這就是發生了幻讀。

  幻讀和不可重複讀都是讀取了另外一條已經提交的事務(這點就髒讀不一樣),所不一樣的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據總體(好比數據的個數)。

 

  如今來看看MySQL數據庫爲咱們提供的四種隔離級別:

  ① Serializable (串行化):可避免髒讀、不可重複讀、幻讀的發生。

  ② Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。

  ③ Read committed (讀已提交):可避免髒讀的發生。

  ④ Read uncommitted (讀未提交):最低級別,任何狀況都沒法保證。

 

  以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,固然級別越高,執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(相似於Java多線程中的鎖)使得其餘的線程只能在鎖外等待,因此平時選用何種隔離級別應該根據實際狀況。在MySQL數據庫中默認的隔離級別爲Repeatable read (可重複讀)。

  在MySQL數據庫中,支持上面四種隔離級別,默認的爲Repeatable read (可重複讀);而在Oracle數據庫中,只支持Serializable (串行化)級別和Read committed (讀已提交)這兩種級別,其中默認的爲Read committed級別。

  在MySQL數據庫中查看當前事務的隔離級別:

select @@tx_isolation;

  在MySQL數據庫中設置事務的隔離 級別:

set  [glogal | session]  transaction isolation level 隔離級別名稱;

    set tx_isolation=’隔離級別名稱;’

例1:查看當前事務的隔離級別:

  

例2:將事務的隔離級別設置爲Read uncommitted級別:

  

或:

  

記住:設置數據庫的隔離級別必定要是在開啓事務以前!

  若是是使用JDBC對數據庫的事務設置隔離級別的話,也應該是在調用Connection對象的setAutoCommit(false)方法以前。調用Connection對象的setTransactionIsolation(level)便可設置當前連接的隔離級別,至於參數level,可使用Connection對象的字段:

  

在JDBC中設置隔離級別的部分代碼:

  

  後記:隔離級別的設置只對當前連接有效。對於使用MySQL命令窗口而言,一個窗口就至關於一個連接,當前窗口設置的隔離級別只對當前窗口中的事務有效;對於JDBC操做數據庫來講,一個Connection對象至關於一個連接,而對於Connection對象設置的隔離級別只對該Connection對象有效,與其餘連接Connection對象無關。

相關文章
相關標籤/搜索