java基礎知識

java基礎知識

複習java基礎知識的筆記java

進程和線程:

進程和線程主要區別在於他們是操做系統不一樣的資源管理方式.

進程是程序的一次執行過程(運行中的程序),是系統運行程序的基本單位.
一個進程至少包含一個線程(main),

能夠包含多個線程.(換言之,線程是進程內的執行單元)

線程與進程類似,它是比進程更小的執行單位.一個進程在執行過程當中能夠產生多個線程.
同類線程有共享的堆和方法區(jdk8以後的元空間(MetaSpace)),
每一個線程又有本身的程序計數器,虛擬機棧,本地方法棧.
系統在各個線程之間的切換工做要比進程負擔低,所以線程又被稱爲輕量級進程

線程的幾種狀態:(見:jdk Thread類源碼中的state枚舉類)

NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED

併發和並行:

併發是指計算機在同一時間段內處理多任務的執行. 
如我和小明同時訪問淘寶網站,那麼淘寶服務器就同時處理我和小明的訪問請求

並行是指多任務同時執行,可是任務之間沒有任何關係,不涉及共享資源.
好比我一邊看電視一邊喝水,2件事互不干擾

公平鎖:c++

指根據線程在隊列中的優先級獲取鎖,好比線程優先加入阻塞隊列,那麼線程就優先獲取鎖

非公平鎖:算法

指在獲取鎖的時候,每一個線程都會去爭搶,而且都有機會獲取到鎖,無關線程的優先級

可重入鎖:數據庫

一個線程獲取到鎖後,若是繼續遇到被相同鎖修飾的資源或方法,那麼能夠繼續獲取該鎖.
   對synchronized來講,每一個鎖都有線程持有者和鎖計數器,每次線程獲取到鎖,會記錄下
   改線程,而且鎖的計數器就+1,當線程退出synchronized代碼塊的時候,線程計數就會-1,
   當鎖計數爲0的時候,就釋放鎖.

自旋鎖:緩存

指當鎖被獲取後,其餘線程並不會中止獲取,而是一直去嘗試獲取.這樣作的好處是減小上下文開銷,
   缺點是增長cpu消耗. 
   CAS底層就使用了自旋操做(不是自旋鎖,而是若是預期值和原值比較不成功就會一直比較)

獨佔鎖:安全

 鎖一次只能被一個線程佔有使用,Synchronized和ReetrantLock都是獨佔鎖

共享鎖:服務器

鎖能夠被多個線程持有,對於ReentrantReadWriteLock而言,它的讀鎖是共享鎖,寫鎖是獨佔鎖

volatile:

Volatile是JVM提供的輕量級的同步機制

1: volatile保證內存可見性併發

JMM內存模型實現老是線程從主內存(共享內存)讀取數據,線程把主存的變量存儲到本地,
    在本地進行修改,而後寫回主內存,而不是直接在主存中進行操做.
    
    那麼這就可能形成可見性問題:假設2個線程從主存讀取同一個變量,一個線程修改了它的本地變量,
    並寫回了主存,可是另外一個線程仍然使用的是以前的值,這就形成了數據的不一致.
    volatile關鍵字修飾的變量就解決了這個問題:被volatile修飾的變量要求線程使用時,
    都從主內存中讀取,而不使用本地的拷貝.

2: volatile不保證原子性jvm

原子性指一個操做的完整性,它是不可分割的,要麼同時成功執行,要麼同時失敗回滾. 
      當多個線程同時對volatie關鍵字修飾的變量進行非原子性操做(++,--)的時候,
      變量可能會被多++一次,少++一次,多--一次,少--一次.
      volatile並不能保證操做的原子性,最後獲得的結果可能不盡人意

如何解決原子性:測試

1: 最簡單的方法就是加鎖
    2: 使用CAS原子類

3: volatile禁止指令重排序

指令重排序是編譯器和cpu爲了儘量高效的執行程序而採起的一種優化手段,
    它會致使程序實際執行的順序和代碼的順序不必定相符,
    而volatil就是在執行的代碼先後加入屏障,使cpu在執行時
    沒法重排序,就按代碼的順序執行

CAS:

CAS:CompareAndSet,比較並交換,它將指定內存位置的值與給定值進行比較,
  若是兩個值相等,就將內存位置的值
  改成給定值.CAS涉及3個元素:內存地址,期盼值和目標值,
  只有內存地址對應的值和指望的值相同時,才把內存地址對應的值修改成目標值.

CAS在JAVA中的底層實現(Atomic原子類實現)

1:Unsafe類:

Unsafe類是CAS的核心類,由jdk自動加載,它的方法都是native方法.
由於Java沒法像c/c++同樣直接使用底層指針操做對象
內存,Unsafe類的做用就是專門解決這個問題,它能夠直接操做對象在內存中的地址.
具體步驟是:首先獲取當前Atomic對象的value在內存中真實的偏移地址,再根據這個偏移地址
獲取value的真實值,而後再重複這個步驟,把兩次獲取到的值進行比較,
若是比較成功,則繼續操做,不然繼續循環比較.
而獲取value在內存中真實的偏移地址和比較設置值方法都是native的.

2: volatile:

Atomic原子類內部的value值是volatile修飾的,這就保證了value的可見性.

CAS的缺點:

1: 循環時間開銷大

若是預期的值和當前值比較不成功,那麼CAS會一直進行循環.若是長時間比較不成功
      就會一直循環,致使CPU開銷過大

2: 只能保證一個共享變量的原子操做

在獲取內存地址和設置值的時候都是當前Atomic對象的volatile值,若是要保證多個共享變量,
      那麼能夠經過加鎖來保證線程安全和原子性.

3: ABA問題

儘管一個線程CAS操做成功,但並不表明這個過程就是沒有問題的.
       假設2個線程讀取了主內存中的共享變量,若是一個線程對主內存中的值進行了修改後,
       又把新值改回了原來的值,而此時另外一個線程進行CAS操做,發現原值和期盼的值是
       同樣的,就順利的進行了CAS操做.這就是CAS引起的ABA問題.

4: 解決ABA問題

juc的atomic包下提供了AtomicStampedReference這個類來解決CAS的原子引用
        更新的ABA問題,它相較於普通的Atomic原子類多增長了一個版本號的字段,
        每次修改引用就更新版本號,這樣即便發生ABA問題,也能經過版本號判斷引用
        是否被修改過了.

線程池的好處:

池化技術家常便飯:數據庫鏈接池,Http鏈接池,線程池都是這種思想.
 池化技術的好處很是明顯,以往單個的new Thread,不易於線程之間的管理,
 而池化技術把全部線程都放在一個池子裏,要用就取出,用完就回收,這樣很是易於管理,
 而且能夠下降資源的消耗,使線程能夠重複利用,提升任務的響應速度,當任務到來時,就能夠
 處理.

線程池構造參數:

ThreadPoolExecutor
(int corePoolSize,
 int maximumPoolSize, 
 long keepAliveTime,
 TimeUnit unit,
 BlockingQueue<Runnable> workQueue,
 ThreadFactory threadFactory,
 RejectedExecutionHandler handler)

corePoolSize:

線程池的核心線程數(常駐線程數),也就是線程池的最小線程數,這部分線程不會被回收.

maximumPoolSize:

線程池最大線程數,線程池中容許同時執行的最大線程數量

keepAliveTime:

當線程池中的數量超過 corePoolSize(最小線程池數量),而且此時沒有新的任務執行,那麼
  會保持    keepAliveTime 的時間纔會回收線程

unit:

keepAliveTime的時間單位

workQueue:

任務隊列,當有新任務來臨時,若是核心線程數corePoolSize被用完,此時若是workQueue有空間,
  任務就會被放入workQueue

threadFactory:

建立工做線程的工廠,也就是如何建立線程的,通常採用默認的

handler:

拒絕策略. 若是線程池陷入一種極端狀況:工做隊列滿了,沒法再容納新的任務,最大工做線程也到達限制了,
  此時線程池如何處理這種極端狀況.
  ThreadPoolExecutor 提供了四種策略:

AbortPolicy(是線程池的默認拒絕策略):

若是還有新任務到來,那麼拒絕,並拋出RejectedExecutionException異常

CallerRunsPolicy:

這種策略不會拒絕執行新任務,可是由發出任務的線程執行,也就是說當線程池沒法
     執行新任務的時候,就由請求線程本身執行任務

DiscardPolicy:

這種策略會拒絕新任務,可是不會拋出異常

DiscardOldestPolicy:

這種策略不會拒絕策略,他會拋棄隊列中等待最久那個任務,來執行新任務

阿里巴巴開發者手冊不建議開發者使用Executors建立線程池:

newFixedThreadPool和newSingleThreadExecutor:
  會建立固定數量線程的線程池和單線程線程池,儘管兩者線程池數量有限,
  可是它會建立長度爲Integer.MAX_VALUE長度的阻塞隊列,這樣可能會致使阻塞隊列
  的任務過多而致使OOM(OutOfMemoryError)
  newCachedThreadPool和newScheduledThreadPool:
  會建立緩存線程池和週期任務線程池,兩者線程池的最大線程爲Integer.MAX_VALUE,
  也可能會致使OOM

JVM運行時內存分區:.

JDK8以前:線程私有的部分有:程序計數器(PC寄存器),JAVA虛擬機棧,
 本地方法棧(native),線程共享部分有: GC堆,方法區(永久代包含運行時常量池)
 JDK8以後:線程私有的部分不變, 線程共享部分的方法區改成了元空間(MetaSpace),
 運行時常量池也移動到了 heap空間

程序計數器:

程序計數器又稱PC寄存器,它是記錄着當前線程執行的字節碼的行號指示器.
  cpu是經過時間片輪換制度執行每一個線程的任務,而在多個線程之間切換時,
  程序計數器就記錄當前線程執行的位置,當cpu又開始執行此線程的時候,它須要知道
  上次運行的位置,那麼就是經過程序計數器直到上次字節碼的執行位置,
  因此每一個線程都會有屬於本身的程序計數器

Java虛擬機棧:

Java虛擬機棧描述的是方法執行的內存模型,每一個方法在執行時會在虛擬機棧中建立一個
   棧幀,每一個方法的執行,就對應着棧幀在虛擬機棧中的入棧和出棧.
   棧幀由局部變量表,操做數棧,方法出口,動態連接等數據組成.

Java虛擬機棧的錯誤

StackOverflowError:

當Java虛擬機棧沒法動態擴容的時候,當前線程執行或請求的棧的大小超過了Java虛擬機棧的最大空間
      (好比遞歸嵌套調用太深),那麼拋出StackOverflowError錯誤

OutOfMemoryError:

1: 當Java虛擬機棧容許動態擴容的時候,當前虛擬機棧執行請求的棧的大小
          仍然超過了擴容以後的最大空間,沒法繼續爲棧分配空間(堆內存分配空間太小),
          那麼拋出OutOfMemoryError錯誤
         
       2: Java堆存放對象實例,當須要爲對象分配內存時,而堆空間大小已經達到最大值,
          沒法爲對象實例繼續分配空間時,拋出 OutOfMemoryError錯誤
          
       3: GC時間過長可能會拋出OutOfMemoryError.也大部分的時間都用在GC上了,並
         且每次回收都只回收一點內存,而清理的一點內存很快又被消耗殆盡,這樣就惡性循環,
         不斷長時間的GC,就可能拋出GC Overhead limit,可是這點在個人機器上測試不出來,
         可能與jdk版本或gc收集器或Xmx分配內存的大小有關,一直拋出的是java heap sapce

       4: 由於jvm內存依賴於本地物理內存,那麼給程序分配超額的物理內存,而堆內存充足,
          那麼GC就不會執行回收,DirectByteBuffer對象就不會被回收,若是繼續分配
          直接物理內存,那麼可能會出現DirectBufferMemoryError
            
       5: 一個應用程序能夠建立的線程數有限,若是建立的線程的數量達到相應平臺的上限,
          那麼可能會出現 unable to create new native thread 錯誤   
          
       6:jdk8以後的Metaspace元空間也有可能拋出OOM,Metasapce受限於物理內存,它存儲
         着類的元信息,當Metaspace裏的類的信息過多時,Metaspace可能會發生OOM.
         這裏是可使用cglb的字節碼生成類的技術測試的.

虛擬機棧棧的動態擴容:

上面說過若是當虛擬機棧容許動態擴容,當動態擴容的空間都不夠用的時候,就拋出OutOfMemory異常.
       虛擬機棧的動態擴容是指在棧空間不夠用的時候,自動增長棧的內存大小.
       
       動態擴容棧有2種方法:
       Stack Copying: 就是分配一個更大的棧空間,把原來的棧拷貝到新棧空間去.
       Segmented Stack: 能夠理解爲一個雙向鏈表把多個棧連接起來,一開始只分配一個棧,當
                        這個空間不夠時.再分配一個棧空間,用鏈表連接起來.

局部變量表:存放編譯期可知的各類數據類型(常見的8大數據類型)和引用類型(reference,能夠是指向對象的指針,也能夠是句柄)

操做數棧:與局部變量相似,它也能夠存聽任意類型的數據,但它是做爲數據在計算時的臨時存儲空間,入站和出棧

動態連接:由於每一個方法在運行時都會建立相應的棧幀,那麼棧幀也會保存一份方法的引用,棧幀保存方法的引用是爲了支持方法調用過程當中的動態連接.

動態連接是指將符號引用轉爲直接引用的過程.
  若是方法調用另外一個方法或者調用另外一個類的成員變量就須要知道其名字,
  符號引用就至關於名字,運行時就將這個名字解析成相應的直接引用.

方法出口:方法執行後,有2種方式退出: 1: 執行方法的過程當中遇到了異常; 2: 遇到正常的返回字節碼指令.

不管何種方式退出,在方法退出後,都須要回到方法被調用時的位置,
    方法在返回時就須要在棧幀中保存一些信息,用來幫助它恢復上層方法的執行狀態.

本地方法棧:

與Java虛擬機棧相似,Java虛擬機棧是對Java方法執行的描述,
  可是本地方法棧是對jvm的native方法描述,也就是第三方c/c++
  編寫的方法,本地方法棧也有相應的 局部變量表,操做數棧,方法出口,動態連接

堆是jvm內存區域中最大的一塊區域.
  
  堆區的做用是爲對象分配內存,存儲他們,並負責回收它們之中無用的對象.
  堆能夠分爲新生代和老年代,新生代佔堆區的1/3,老年代佔堆區的2/3.
  新生代包括eden,from survivor, to survivor三個空間
  其中 eden空間最大佔新生代的80%內存,from和to都是1:1.
  
  新生代是GC發生最頻繁的區域.
  發生在新生代的GC被稱爲MinorGC,老年代的GC被稱爲Major GC / Full GC
  

 由於老年代的空間比新生代的空間大,因此一般老年代的GC時間會比新生代的GC時間慢不少.
 對象優先在eden區域分配,當eden區域沒有空間時,虛擬機就會發起一次Minor GC

判斷對象存活的方法

1: 引用計數法:

給每一個對象添加一個引用計數器,當對象被引用的時候,引用計數器就+1,當引用失效時,引用計數器
      就-1,直到引用計數器爲0,就表明對象再也不被引用.
      引用計數的主要缺陷是很難解決循環引用的問題:也就是當2個對象互相引用的時候,除了彼此,
      就沒有其餘地方引用這2個對象,那麼他們的引用計數都爲1,就沒法被回收

2: 可達性算法:

經過一系列被稱爲GC ROOTS的對象節點往下搜索,節點走過的地方被稱爲引用鏈,
      若是一個對象不被任何引用鏈走過,那麼稱
      此對象不可達.

什麼是GC Root

上面說經過GC Root對象搜索引用鏈,那麼GC Root對象是什麼對象,或者什麼樣的對象是GC Root對象.
    
    能夠做爲GC Root對象的有: 
    1: 虛擬機棧和本地方法棧區(native)的引用對象;
    2: 堆區裏的靜態變量引用的對象;
    3: 堆區裏的常量池的常量引用的對象

垃圾回收算法:

複製算法:

將內存分爲2塊大小的內存空間,每次使用其中一塊空間,當一塊使用完後,
     將還存活的對象複製到另外一塊空間去,而後清楚已經使用過的空間.
     根據GC角度來講就是:在新生代的eden空間和From Survivor空間經歷過MinorGC後,
     仍然存活的對象採用複製算法複製到To Survivor,
     並將To Survivor(其實就是空間不空閒的那塊Survivor區域)的對象年齡+1,
     默認對象年齡撐過15歲,那麼進入老年代.

     複製算法的缺點就是太耗空間內存.

標記-清除算法:

標記出全部須要被回收的對象,而後統一回收全部被標記的對象.
     
     標記清除算法的最大缺點就是會形成不連續的內存空間,
     也就是內存碎片,由於對象在內存中的分佈是不均勻的.

標記-整理算法:

是對標記-清除算法作出的改進,標記整理算法也是首先標記出全部須要被回收的對象,
     不一樣的是,它會使全部仍然存活的對象向空間的一段移動,而後對其它端進行清理.
     
     此算法雖然不會產生內存碎片,可是它的效率會比標記清楚算法慢

分代收集算法:

分代收集算法不是一種具體的收集算法.
     由於堆是分爲新生代(包括eden空間,from survivor,to survivor)
     老年代的,分代收集算法就是在不一樣的分代空間採用不一樣的垃圾回收算法:
     如複製算法應用於新生代,標記清除和標記整理應用於老年代

強引用:

通常經常使用的new方式建立對象,建立的就是強引用. 只要強引用存在,
    垃圾回收器就不會回收.

軟引用

SoftReference , 非必須引用,若是內存足夠或正常,就不回收,可是當
     內存不夠,快發生OOM的時候就回收掉軟引用對象.

弱引用

WeakReference , 對於弱引用的對象來講,只要垃圾回收器開始回收,
    不管空間是否充足,都回收弱引用的對象.

虛引用(幽靈引用)

和其餘幾種引用不一樣,虛引用不會決定對象生命週期,垃圾回收時,沒法經過虛引用獲取對象
    值.虛引用在任什麼時候候均可能被垃圾回收掉.虛引用必須和引用隊列(ReferenceQueue)使用.

軟引用,弱引用,虛引用在被GC前會被加入到與其關聯的引用隊列中.

相關文章
相關標籤/搜索