java基礎的個人理解總結【二】

JVM調優

GC調優步驟:

1>.打印GC日誌

-xx:+PrintGCDetails -xx:+PrintGCTimeStamps -xx:+PrintGCDateStamps -xloggc:./gc.log

Tomcat可以直接夾在JAVA_OPTS變量裏

2>分析日誌得到關鍵性指標

判斷這個項目爲什麼產生FullGC,爲什麼需要去調優

3>分析GC原因,調優JVM參數

1.Parallel Scavenge收集器(默認)

分析parallel-gc.log

第一次調優,設置Metaspace,增大元空間大小

-XX:MetaspaceSize=64M -XXMaxMetaspaceSize=64M
-XX:MetaspaceSize=128M -XXMaxMetaspaceSize=128M

比較一下幾次調優的結果

吞吐量 最大停頓 平均停頓 YGC FGC
97.647% 360ms 67 16 2
98.205% 200ms 69 11 0

2.配置CMS收集器

分析gc-cms.log

-XX:+UseConcMarkSweepGC

3.配置G1收集器

分析gc-g1.log

-XX:+UseG1GC
young GC [GC pause]
initial-mark (參數 initiatingHeapOccupancyPercent)
mixed GC (參數 G1HeapWastePercent)
Full GC (無可用region)

G1的目標就是爲了減少Full GC,滿足應用性能指標。

四、HashMap【不太全面,部分總結】

衆所周知,HashMap底層結構,1.8之前是數組+鏈表,從1.8開始則是數組+鏈表+紅黑樹(鏈表長度大於8),線程不安全。

hash衝突HashMap中主要是通過key的hashCode來計算hash值的,只要hashCode相同,計算出來的hash值就一樣。如果存儲的對象對多了,就有可能不同的對象所算出來的hash值是相同的,這就出現了所謂的hash衝突。詳情如下:

 

隨着存儲的對象數量不斷增加,可能會導致以下幾種情況:

1.存儲對象key 值相同(hash值一定相同),導致衝突;
2.存儲對象key 值不同,由於 hash 函數的侷限性導致hash 值相同,衝突;
3.存儲對象key 值不同,hash 值不同,但 hash 值對數組長度取模後相同,衝突;

HashMap裏面解決hash衝突的方法就是鏈地址法,

鏈地址法的基本思想是:每個哈希表節點都有一個next指針,多個哈希表節點可以用next指針構成一個單向鏈表,被分配到同一個索引上的多個節點可以用這個單向 鏈表連接起來,如: 鍵值對k2, v2與鍵值對k1, v1通過計算後的索引值都爲2,這時及產生衝突,但是可以通道next指針將k2, k1所在的節點連接起來,這樣就解決了哈希的衝突問題 。

講到HashMap,面試裏面指定會被問與Hashtable的區別:

區別名稱 hashMap hashtable
繼承 AbstractMap Dictionary
線程是否安全 非synchronized,不安全,但效率好 synchronized,安全,效率低
默認初始容量 16 11
擴容方式 2n,原來的2倍 2*oldCapacity +1
key值 可爲null,但至多1個 不能爲null
遍歷 Iterator enumerator

五、基礎必備

<1>  String、StringBuffer、StringBuilder的區別

區別名稱 String StringBuffer StringBuilder
字符串是否可變 字符串常量,不可變,因爲是final 字符串變量,可變 字符串變量,可變
線程是否安全 多線程、安全 多線程、安全 單線程、不安全 
執行效率

 

String s1 = "abc" ;
String s2 = "abc" ;
String s3= new String("abc");

用s1、s2這種方式創建字符串對象的時候,首先會去字符串常量池中查找看有沒有“abc”字符串,如果有則返回它的地址給s1,如果沒有則在常量池中創建“abc”字符串,並將地址返回給s1.  所以s1 == s2 ;

用s3這種方式創建字符串對象的時候,首先會去字符串常量池中查找看有沒有“abc字符串,如果沒有則在常量池中創建“abc”字符串,然後在堆內存中創建“Cat”字符串,並將堆內存中的地址返回給s3.

也就是說,s3這種方式,常量池與堆內存都要保存,並且返回的地址是堆內存的地址,而不是常量池中的地址。

String s1 = "abc" ;
String s4 = "ab" + "c";
String s5 = "a" + "bc";
String s6 = "a";
String s7 = "b";
String s8 = "c";
String s9 = s6 + s7 +s8;

s1 、s4、s5都是指向常量池中字符串“abc”地址的一個引用,所以s1 == s4 == s5,

s9也是跟s1他們相等的,但是他們開闢的空間缺很浪費,s1 :直接在常量池中放入“abc”

s4:在常量池,先開闢一段空間放“ab”, 再開闢一段空間放“c”,最後再開闢一段空間放“abc”【假如常量池中沒有“abc”】

s9:更復雜,在常量池分別開闢“a”  "b"  "c" ,最後再開闢一段空間放“abc”【假如常量池中沒有“abc”】。

結果都一樣,引用指向常量池中的“abc”.

<2>  值傳遞與引用傳遞

值傳遞:在方法被調用時,實參通過形參把它的內容副本傳入方法內部,此時形參接收到的內容是實參值的一個拷貝,因此在方法內對形參的任何操作,都僅僅是對這個副本的操作,不影響原始值的內容。

 1public static void valueCrossTest(int age,float weight){
 2    System.out.println("傳入的age:"+age);
 3    System.out.println("傳入的weight:"+weight);
 4    age=33;
 5    weight=89.5f;
 6    System.out.println("方法內重新賦值後的age:"+age);
 7    System.out.println("方法內重新賦值後的weight:"+weight);
 8    }
 9
10//測試
11public static void main(String[] args) {
12        int a=25;
13        float w=77.5f;
14        valueCrossTest(a,w);
15        System.out.println("方法執行後的age:"+a);
16        System.out.println("方法執行後的weight:"+w);
17}

運行之後的結果 ,毫無疑問

1傳入的age:25
2傳入的weight:77.5
3
4方法內重新賦值後的age:33
5方法內重新賦值後的weight:89.5
6
7方法執行後的age:25
8方法執行後的weight:77.5

a和w作爲實參傳入valueCrossTest之後,無論在方法內做了什麼操作,最終a和w都沒變化。

 

值傳遞傳遞的是真實內容的一個副本,對副本的操作不影響原內容,也就是形參怎麼變化,不會影響實參對應的內容。

引用傳遞:”引用”也就是指向真實內容的地址值,在方法調用時,實參的地址通過方法調用被傳遞給相應的形參,在方法體內,形參和實參指向通愉快內存地址,對形參的操作會影響的真實內容。

 //    例子1
public class Person {
        private String name;
        private int age;

        public String getName() {
            return name;
          }
          public void setName(String name) {
              this.name = name;
          }
          public int getAge() {
              return age;
          }
          public void setAge(int age) {
              this.age = age;
          }
  }
  public static void PersonCrossTest(Person person){
          System.out.println("傳入的person的name:"+person.getName());
          person.setName("隔壁老王");
          System.out.println("方法內重新賦值後的name:"+person.getName());
      }
  //測試
  public static void main(String[] args) {
          Person p=new Person();
          p.setName("我是鄰居老張");
          p.setAge(45);
          PersonCrossTest(p);
          System.out.println("方法執行後的name:"+p.getName());
  }

執行後得到的是:

1傳入的person的name:鄰居老張
2方法內重新賦值後的name:隔壁老王
3方法執行後的name:隔壁老王

可以看出,person經過personCrossTest()方法的執行之後,內容發生了改變,對形參的操作,改變了實際對象的內容。

這樣還沒完事,繼續看,假如將PersonCrossTest()方法改成如下:

//    例子2
1public static void PersonCrossTest(Person person){
2        System.out.println("傳入的person的name:"+person.getName());
3        person=new Person();//加多此行代碼
4        person.setName("隔壁老王");
5        System.out.println("方法內重新賦值後的name:"+person.getName());
6    }

其他條件不變,則得到的結果爲:

1傳入的person的name:鄰居老張
2方法內重新賦值後的name:隔壁老王
3方法執行後的name:鄰居老張

爲啥跟上面的不一樣呢,不就多建了次對象嗎?

          Person p=new Person();
          p.setName("我是鄰居老張");
          p.setAge(45);

執行這段代碼的時候,

JVM會在堆內開闢一塊內存,用來存儲p對象的所有內容,同時在main()方法所在線程的棧區中創建一個引用p存儲堆區中p對象的真實地址,

 

當執行到PersonCrossTest()方法時,因爲方法內有這麼一行代碼:

       person=new Person();//加多此行代碼

JVM需要在堆內另外開闢一塊內存來存儲new Person(),那此時形參person指向了這個地址。引用傳遞中形參實參指向同一個對象,形參的操作會改變實參對象的改變

無論是基本類型和是引用類型,在實參傳入形參時,都是值傳遞,也就是說傳遞的都是一個副本,而不是內容本身。

 

因此在第一個例子中,對形參p的操作,會影響到實參對應的對象內容。而在第二個例子中,當執行到new Person()之後,JVM在堆內開闢一塊空間存儲新對象,並且把person改成指向新對象的地址,此時:

p依舊是指向舊的對象,person指向新對象的地址。

所以此時對person的操作,實際上是對新對象的操作,於實參p中對應的對象毫無關係

六、多線程

線程:單個進程中執行中每個任務就是一個線程。線程是進程中執行運算的最小單位。

進程:是執行中一段程序,即一旦程序被載入到內存中並準備執行,它就是一個進程。

如何實現線程?

1、extends  Thread  class

2、implement Runable interface

3、implement Callable interface

線程池:

       如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效        率,因爲頻繁創建線程和銷燬線程需要時間。 
       那麼有沒有一種辦法使得線程可以複用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務?

創建線程要花費昂貴的資源和時間,如果任務來了才創建線程那麼響應時間會變長,而且一個進程能創建的線程數有限。
爲了避免這些問題,在程序啓動的時候就創建若干線程來響應處理,它們被稱爲線程池

使用線程池的好處:

1、降低資源消耗。重複利用已創建線程,降低線程創建與銷燬的資源消耗。 
2、提高響應效率。任務到達時,不需等待創建線程就能立即執行。 
3、提高線程可管理性。 
4、防止服務器過載。內存溢出、CPU耗盡 

Thread 類中的 start 和 run 方法有什麼區別?

start 方法被用來啓動新創建的線程,而且 start 內部調用了 run 方法

當你調用 run 方法的時候,只會是在原來的線程中調用,沒有新的線程啓動

一個線程運行時發生異常會怎樣?

如果異常沒有被捕獲該線程將會停止執行。

線程調度算法:搶佔式,一個線程用完CPU之後,操作系統會根據線程優先級、線程飢餓情況等數據算出一個總的優先級並分配下一個時間片給某個線程執行。

synchronized和Lock有什麼區別?

 

區別 synchronized Lock
  JVM層面實現的,java提供的關鍵字 API層面的鎖
  底層會自動釋放 手動釋放鎖
  等待不可中斷,除非拋出異常或者執行完成 可以中斷,通過interrupt()可中斷
  不可綁定多個條件 可實現分組喚醒需要喚醒的鎖

樂觀鎖、悲觀鎖:

悲觀鎖:

還是像它的名字一樣,對於併發間操作產生的線程安全問題持悲觀狀態,悲觀鎖認爲競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨佔的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。

樂觀鎖:

就像它的名字一樣,對於併發間操作產生的線程安全問題持樂觀狀態,樂觀鎖認爲競爭不總是會發生,因此它不需要持有鎖,將比較-替換這兩個動作作爲一個原子操作嘗試去修改內存中的變量,如果失敗則表示發生衝突,那麼就應該有相應的重試邏輯。【樂觀鎖假設認爲數據一般情況下不會造成衝突,所以在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,如果發現衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去做。

sleep 、wait的區別【 sleep()睡眠時,保持對象鎖,仍然佔有該鎖;而wait()睡眠時,釋放對象鎖。

  sleep wait
  沒有釋放鎖,不放棄監視器 釋放鎖,放棄監視器【

使得其他線程可以使用同步控制塊或者方法(鎖代碼塊和方法鎖)。

  可以在任何地方使用(使用範圍)   wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用
  必須捕獲異常 不需要捕獲異常  
     

yield、join方法

yield()方法是停止當前線程,讓同等優先權的線程或更高優先級的線程有執行的機會。如果沒有的話,那麼yield()方法將不會起作用,並且由可執行狀態後馬上又被執行。   

join()方法是用於在某一個線程的執行過程中調用另一個線程執行,等到被調用的線程執行結束後,再繼續執行當前線程。

簡述線程的五種狀態,同樣也是線程的生命週期

   (1)新建(new):當一個線程處於新建狀態時,它僅僅是一個空的線程對象,系統不爲它分配資源。Tread t = new Tread(new Runner());

   (2)就緒(Runable):此時線程處在隨時可以運行的狀態,在隨後的任意時刻,都可能進入運行狀態。t.star( );

   (3)運行(Running):處於這個狀態的線程佔用CPU,執行程序代碼。

   (4)阻塞(Blocked):阻塞狀態是指線程因爲某些原因放棄CPU,暫時停止運行,直到線程重新進入就緒狀態。wait、sleep、同步鎖被佔用;

   (5)死亡(Dead):當線程退出run()方法時,就進入死亡狀態,該線程生命週期結束。可能正常執行完run()方法退出,也可能是遇到異常。

線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;

請說出你所知道的線程同步的方法

wait():使一個線程處於等待狀態,並且釋放所持有的對象的lock。
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要捕捉InterruptedException異常。
notify():喚醒一個處於等待狀態的線程,注意的是在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。
Allnotity():喚醒所有處入等待狀態的線程,注意並不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。

七、網絡協議HTTP、TCP

HTTP:HyperText Transfer Protocol,超文本傳輸協議

TCP:Transmission Control Protocol,傳輸控制協議

HTTP的工作原理:

      HTTP 協議採用請求/響應模型。客戶端向服務器發送一個請求報文,服務器以一個狀態作爲響應。

  以下是 HTTP 請求/響應的步驟:
     1、客戶端連接到web服務器:HTTP 客戶端與web服務器建立一個 TCP 連接;
         2、客戶端向服務器發起 HTTP 請求:通過已建立的TCP 連接,客 戶端向服務器發送一個請求報文;
         3、服務器接收 HTTP 請求並返回 HTTP 響應:服務器解析請求,定位請求資源,服務器將資源副本寫到 TCP 連接,由客戶端讀取;
         4、釋放 TCP 連接:若connection 模式爲close,則服務器主動 關閉TCP 連接,客戶端被動關閉連接,釋放TCP 連接;若
connection 模式爲keepalive,則該連接會保持一段時間,在該 時間內可以繼續接收請求;
         5、客戶端瀏覽器解析HTML內容:客戶端將服務器響應的 html 文 本解析並顯示;

      例如:在瀏覽器地址欄鍵入URL,按下回車之後會經歷以下 流程:
       1、瀏覽器向 DNS 服務器請求解析該 URL 中的域名所對應 的 IP 地址;
       2、解析出 IP 地址後,根據該 IP 地址和默認端口 80或443,和服務器建立 TCP 連接;
       3、瀏覽器發出讀取文件(URL 中域名後面部分對應的文件) 的HTTP 請求,該請求報文作爲 TCP 三次握手的第三個報文的數據發送給服務器;(若在https下會有12個包,SSL握手有8個包)
       4、服務器對瀏覽器請求作出響應,並把對應的 html 文本 發送給瀏覽器;
       5、釋放 TCP 連接;
       6、瀏覽器將該 html 文本並顯示內容;

HTTP、HTTPS之間的區別

區別 HTTP HTTPS
證書 不需要 需要ca申請證書,一般免費證書較少,因而需要一定費用。
傳輸方式 明文傳輸 ssl加密傳輸協議
端口 80 443
連接 簡單,無狀態 SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議
頁面響應 速度快,HTTP 使用 TCP 三次握手建立連接,客戶端和服務器需要交換 3 個包 速度慢, HTTPS除了 TCP 的三個包,還要加上 ssl 握手需要的 9 個包,所以一共是 12 個包。

TCP:說到TCP,不得不講的就是傳說中的三次握手,四次揮手。

    ACK:確認序號的標誌,ACK=1表示確認號有效,ACK=0表示報文不含確認序號信息

    SYN:連接請求序號標誌,用於建立連接,SYN=1表示請求連接

    FIN:結束標誌,用於釋放連接,爲1表示關閉本方數據流

建立連接的三次握手:

 

握手之前主動打開連接的客戶端結束CLOSED階段,被動打開的服務器端也結束CLOSED階段,並進入LISTEN階段。隨後開始“三次握手”:

  • 第一次握手:客戶端發送syn包(syn=x)的數據包到服務器,並進入SYN_SENT狀態,等待服務器確認;
  • 第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=x+1),同時自己也發送一個SYN包(syn=y),即SYN+ACK包,此時服務器進入SYN_RCVD狀態;
  • 第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

關閉連接的四次揮手:

  • 第一次揮手:客戶端發出釋放FIN=1,自己序列號seq=u,進入FIN-WAIT-1狀態
  • 第二次揮手:服務器收到客戶端的後,發出ACK=1確認標誌和客戶端的確認號ack=u+1,自己的序列號seq=v,進入CLOSE-WAIT狀態
  • 第三次揮手:客戶端收到服務器確認結果後,進入FIN-WAIT-2狀態。此時服務器發送釋放FIN=1信號,確認標誌ACK=1,確認序號ack=u+1,自己序號seq=w,服務器進入LAST-ACK(最後確認態)
  • 第四次揮手:客戶端收到回覆後,發送確認ACK=1,ack=w+1,自己的seq=u+1,客戶端進入TIME-WAIT(時間等待)。客戶端經過2個最長報文段壽命後,客戶端CLOSE;服務器收到確認後,立刻進入CLOSE狀態。

爲什麼建立連接三次握手,關閉連接要四次揮手?

  • 三次握手時,服務器同時把ACK和SYN放在一起發送到了客戶端那裏
  • 四次揮手時,當收到對方的 FIN 報文時,僅僅表示對方不再發送數據了但是還能接收數據,己方是否現在關閉發送數據通道,需要上層應用來決定,因此,己方 ACK 和 FIN 一般都會分開發送。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章