1. 程式人生 > 其它 >力扣演算法題9. 迴文數(Java)

力扣演算法題9. 迴文數(Java)

https://blog.csdn.net/yanpenglei/article/details/119406377 參考

1.  JVM 預設使用的java 虛擬機器是HotSpot。

 

2. Java的雙親委託機制是什麼?

 

它的意思是,除了頂層的啟動類載入器以外,其餘的類載入器,在載入之前,都會委派給它的父載入器進行載入。這樣一層層向上傳遞,直到祖先們都無法勝任,它才會真正的載入。

 

Java預設是這種行為。當然Java中也有很多打破雙親行為的騷操作,比如SPI(JDBC驅動載入),OSGI等。

 

2.JVM使用“類”的生命週期是:

裝載、連結、初始化、使用、解除安裝 1.裝載: 定要找到Class檔案所在的全路徑,然後裝載到記憶體中J,ava又是一門面向物件的開發語這個過程用到了類載入器 ClassLoader。

2.連線 該階段又包含了驗證、準備和解析3個過程,如下。

(1)驗證

   校驗.class檔案的正確性。

(2)準備

     2.1給static靜態變數分配記憶體,並初始化static的預設值。

    private static int a=10; 此時a變數會被賦值為預設值     2.2符號引用和直接引用    符號引用:4f62 6a65 6374 ----->loading 二進位制流是沒有辦法被cpu直接解析的 (.class  變數 方法等等裡面全是二進位制的)   直接應用:0x00ab---0x00ba 實體記憶體 3.初始化:給static變數 賦予實際的值 private static int a=10; a=10; 4.使用:物件的初始化、物件的垃圾回收、物件的銷燬.

3、GC Roots 有哪些?

1、 GC Roots 是一組必須活躍的引用。用通俗的話來說,就是程式接下來通過直接引用或者間接引用,能夠訪問到的潛在被使用的物件。

2、 GC Roots 包括:Java 執行緒中,當前所有正在被呼叫的方法的引用型別引數、區域性變數、臨時值等。也就是與我們棧幀相關的各種引用。所有當前被載入的 Java 類。Java 類的引用型別靜態變數。執行時常量池裡的引用型別常量(String 或 Class 型別)。JVM 內部資料結構的一些引用,比如 sun.jvm.hotspot.memory.Universe 類。用於同步的監控物件,比如呼叫了物件的 wait() 方法。JNI handles,包括 global handles 和 local handles。

3、 這些 GC Roots 大體可以分為三大類,下面這種說法更加好記一些:活動執行緒相關的各種引用。類的靜態變數的引用。JNI 引用。

4、 有兩個注意點:我們這裡說的是活躍的引用,而不是物件,物件是不能作為 GC Roots 的。GC 過程是找出所有活物件,並把其餘空間認定為“無用”;而不是找出所有死掉的物件,並回收它們佔用的空間。所以,哪怕 JVM 的堆非常的大,基於 tracing 的 GC 方式,回收速度也會非常快。

4、說說Java 垃圾回收機制

在 Java 中,程式設計師是不需要顯示的去釋放一個物件的記憶體的,而是由虛擬機器自行執行。在 JVM 中,有一個垃圾回收執行緒,它是低優先順序的,在正常情況下是不會執行的,只有在虛擬機器空閒或者當前堆記憶體不足時,才會觸發執行,掃面那些沒有被任何引用的物件,並將它們新增到要回收的集合中,進行回收。

5、分代收集演算法

當前主流 VM 垃圾收集都採用”分代收集” (Generational Collection)演算法, 這種演算法會根據物件存活週期的不同將記憶體劃分為幾塊, 如 JVM 中的新生代、老年代、永久代, 這樣就可以根據各年代特點分別採用最適當的 GC 演算法。 不同分割槽,採用不同的GC演算法。 具體演算法看6.

6、有哪些 GC 演算法?

標記-清除演算法

分為標記和清除階段,首先從每個 GC Roots 出發依次標記有引用關係的物件,最後清除沒有標記的物件。

執行效率不穩定,如果堆包含大量物件且大部分需要回收,必須進行大量標記清除,導致效率隨物件數量增長而降低。

存在記憶體空間碎片化問題,會產生大量不連續的記憶體碎片,導致以後需要分配大物件時容易觸發 Full GC。

標記-複製演算法

為了解決記憶體碎片問題,將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中一塊。當使用的這塊空間用完了,就將存活物件複製到另一塊,再把已使用過的記憶體空間一次清理掉。主要用於進行新生代。

實現簡單、執行高效,解決了記憶體碎片問題。代價是可用記憶體縮小為原來的一半,浪費空間。

HotSpot 把新生代劃分為一塊較大的 Eden 和兩塊較小的 Survivor(s0,s1),每次分配記憶體只使用 Eden 和其中一塊 Survivor(s0和s1 永遠只有一個有值)。垃圾收集時將 Eden 和 Survivor 中仍然存活的物件一次性複製到另一塊 Survivor 上,然後直接清理掉 Eden 和已用過的那塊 Survivor。HotSpot 預設Eden 和 Survivor 的大小比例是 8:1,即每次新生代中可用空間為整個新生代的 90%。

標記-整理演算法

標記-複製演算法在物件存活率高時要進行較多複製操作,效率低。如果不想浪費空間,就需要有額外空間分配擔保,應對被使用記憶體中所有物件都存活的極端情況,所以老年代一般不使用此演算法。

老年代使用標記-整理演算法,標記過程與標記-清除演算法一樣,但不直接清理可回收物件,而是讓所有存活物件都向記憶體空間一端移動,然後清理掉邊界以外的記憶體。

標記-清除與標記-整理的差異在於前者是一種非移動式演算法而後者是移動式的。如果移動存活物件,尤其是在老年代這種每次回收都有大量物件存活的區域,是一種極為負重的操作,而且移動必須全程暫停使用者執行緒。如果不移動物件就會導致空間碎片問題,只能依賴更復雜的記憶體分配器和訪問器解決。

7、JVM 記憶體區域

JVM 記憶體區域主要分為執行緒私有區域【程式計數器、虛擬機器棧、本地方法區】、執行緒共享區域【JAVA 堆、方法區】、直接記憶體。

執行緒私有資料區域生命週期與執行緒相同, 依賴使用者執行緒的啟動/結束 而 建立/銷燬(在 Hotspot VM 內, 每個執行緒都與作業系統的本地執行緒直接對映, 因此這部分記憶體區域的存/否跟隨本地執行緒的生/死對應)。

執行緒共享區域隨虛擬機器的啟動/關閉而建立/銷燬。

直接記憶體並不是 JVM 執行時資料區的一部分, 但也會被頻繁的使用: 在 JDK 1.4 引入的 NIO 提供了基於Channel與 Buffer的IO方式, 它可以使用Native函式庫直接分配堆外記憶體, 然後使用DirectByteBuffer 物件作為這塊記憶體的引用進行操作(詳見: Java I/O 擴充套件), 這樣就避免了在 Java堆和 Native 堆中來回複製資料, 因此在一些場景中可以顯著提高效能。

8、MinorGC,MajorGC、FullGC都什麼時候發生?

MinorGC在年輕代空間不足的時候發生,MajorGC指的是老年代的GC,出現MajorGC一般經常伴有MinorGC。

FullGC有三種情況。

1、 當老年代無法再分配記憶體的時候

2、 元空間不足的時候

3、 顯示呼叫System.gc的時候。另外,像CMS一類的垃圾回收器,在MinorGC出現promotion failure的時候也會發生FullGC

9、垃圾收集器對比

1.Serial GC  Serial 即序列的意思,也就是說它以序列的方式執行,它是單執行緒的收集器,只會使用一個執行緒進行垃圾收集工作,GC 執行緒工作時,其它所有執行緒都將停止工作會產生 stop the world  。

  stop the world  停頓時間會相對比較長。

  結果:對於客戶端而言,響應時間變慢了。 新老代之分: Serial(new) 標記複製演算法  適用於新生代 Serial(Old)   標記整理演算法、適用於老年代。 2. Parallel收集器     同樣是多執行緒的收集器,其它收集器目標是儘可能縮短垃圾收集時使用者執行緒的停頓時間,而它的目標是提高吞吐量(吞吐量 = 執行使用者程式的時間 / (執行使用者程式的時間 + 垃圾收集的時間))產生 stop the world。 Parallel(new) 標記複製演算法  適用於新生代 Parallel(Old)   標記整理演算法、適用於老年代。

3.CMS 收集器(只適用老年代)
CMS(Concurrent Mark Sweep),收集器幾乎佔據著 JVM 老年代收集器的半壁江山,它劃時代的意義就在於垃圾回收執行緒幾乎能做到與使用者執行緒同時工作。

使用標記-清除演算法收集老年代垃圾。

工作流程主要有如下 4 個步驟:

初始標記: 僅僅只是標記一下 GC Roots 能直接關聯到的物件,速度很快,需要停頓(Stop-the-world)
併發標記: 進行 GC Roots Tracing 的過程,它在整個回收過程中耗時最長,不需要停頓
重新標記: 為了修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,需要停頓(Stop-the-world)
併發清除: 清理垃圾,不需要停頓
在整個過程中耗時最長的併發標記和併發清除過程中,收集器執行緒都可以與使用者執行緒一起工作,不需要進行停頓。只有在初始標記,重新標記的時候會進行短暫的停頓。

但 CMS 收集器也有如下缺點:

吞吐量低
無法處理浮動垃圾
標記 - 清除演算法帶來的記憶體空間碎片問題
顯式的使用該垃圾收集器作為老年代垃圾收集器的方式:-XX:+UseConcMarkSweepGC

4.G1垃圾收集器(適用新老年代)

https://blog.csdn.net/iva_brother/article/details/87886525

       把G1單獨拿出來的原因是其比較複雜,在JDK 1.7確立是專案目標,在JDK 7u2版本之後釋出,並在JDK 9中成為了預設的垃圾回收器。通過“-XX:+UseG1GC”啟動引數即可指定使用G1 GC。

G1從整體看還是基於標記-清除演算法的,但是區域性上是基於複製演算法的。這樣就意味者它空間整合做的比較好,因為不會產生空間碎片。G1還是併發與並行的,它能夠充分利用多CPU、多核的硬體環境來縮短“stop the world”的時間。G1還是分代收集的,但是G1不再像上文所述的垃圾收集器,需要分代配合不同的垃圾收集器,因為G1中的垃圾收集區域是“分割槽”(Region)的,不同的Region配置不同的垃圾回收演算法。G1的分代收集和以上垃圾收集器不同的就是除了有年輕代的ygc,全堆掃描的full GC外,還有包含所有年輕代以及部分老年代Region的Mixed GC。G1還可預測停頓,通過調整引數,制定垃圾收集的最大停頓時間。

1分割槽的概念
G1的堆區在分代的基礎上,引入分割槽的概念。G1將堆分成了若干Region,以下和”分割槽”代表同一概念。(這些分割槽不要求是連續的記憶體空間)Region的大小可以通過G1HeapRegionSize引數進行設定,其必須是2的冪,範圍允許為1Mb到32Mb。 JVM的會基於堆記憶體的初始值和最大值的平均數計算分割槽的尺寸,平均的堆尺寸會分出約2000個Region。分割槽大小一旦設定,則啟動之後不會再變化。如下圖簡單畫了下G1分割槽模型。

 

 

JVM常見面試題 (1)記憶體洩漏與記憶體溢位的區別 記憶體洩漏是指不再使用的物件無法得到及時的回收,持續佔用記憶體空間,從而造成記憶體空間的浪費。 記憶體洩漏很容易導致記憶體溢位,但記憶體溢位不一定是記憶體洩漏導致的。 (2)Young GC會有stw嗎? 不管什麼GC,都會發送 stop‐the‐world,區別是發生的時間長短,主要取決於不同的垃圾收集器。 (3)Major gc和Full gc的區別 Major GC在很多參考資料中是等價於Full GC 的,我們也可以發現很多效能監測工具中只有Minor GC 和Full GC。一般情況下,一次 Full GC 將會對年輕代、老年代、元空間以及堆外記憶體進行垃圾回收。 觸發Full GC的原因其實有很多: 當年輕代晉升到老年代的物件大小,並比目前老年代剩餘的空間大小還要大時,會觸發 Full GC;當老年代的空間使用率超過某閾值時,會觸發 Fu ll GC;當元空間不足時(JDK1.7永久代不足),也會觸發 Full GC;當呼叫 System.gc() 也會安排一次 Full GC。 (4)判斷垃圾的方式 引用計數 可達性分析 (5)為什麼要區分新生代和老年代 當前虛擬機器的垃圾收集都採用分代收集演算法,這種演算法沒有什麼新的思想,只是根據物件存活週期的不同將記憶體分為幾塊。 一般將 java 堆分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集演算法。 比如在新生代中,每次收集都會有大量物件死去,所以可以選擇複製演算法,只需要付出少量物件的複製成本就可以完成每次垃圾收集。 而老年代的物件存活機率是比較高的,而且沒有額外的空間對它進行分配擔保,所以我們必須選擇“標記‐清除”或“標記‐整理”演算法進行垃圾收集。 (6)方法區中的類資訊會被回收嗎? 方法區主要回收的是無用的類,那麼如何判斷一個類是無用的類的呢?判定一個常量是否是“廢棄常量”比較簡單,而要判定一個類是否是“無用的 類”的條件則相對苛刻許多。類需要同時滿足下面 3 個條件才能算是 “無用的類” a‐該類所有的例項都已經被回收,也就是 Java 堆中不存在該類的任何例項。 b‐載入該類的 ClassLoader 已經被回收。 c‐該類對應的 java.lang.Class 物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。 (7)CMS與G1的區別 CMS 主要集中在老年代的回收,而 G1 集中在分代回收,包括了年輕代的 Young GC 以及老年代的 Mixed GC。 G1 使用了 Region 方式對堆記憶體進行了劃分,且基於標記整理演算法實現,整體減少了垃圾碎片的產生。 在初始化標記階段,搜尋可達物件使用到的 Card Table,其實現方式不一樣。 G1可以設定一個期望的停頓時間。 (8)為什麼需要Survivor區 如果沒有Survivor,Eden區每進行一次Minor GC,存活的物件就會被送到老年代。 這樣一來,老年代很快被填滿,觸發Major GC(因為Major GC一般伴隨著Minor GC,也可以看做觸發了Full GC)。老年代的記憶體空間遠大於新生代,進行一次Full GC消耗的時間比Minor GC長得多。執行時間長有什麼壞處?頻發的Full GC消耗的時間很長,會影響 大型程式的執行和響應速度。 (9)為什麼需要兩個Survivor區 最大的好處就是解決了碎片化。也就是說為什麼一個Survivor區不行?第一部分中,我們知道了必須設定Survivor區。 假設現在只有一個Survivor區,我們來模擬一下流程: 剛剛新建的物件在Eden中,一旦Eden滿了,觸發一次Minor GC,Eden中的存活物件就會被移動到Survivor區。 這樣繼續迴圈下去,下一次Eden滿了的時候,問題來了,此時進行Minor GC,Eden和Survivor各有一些存活物件, 如果此時把Eden區的存活物件硬放到Survivor區,很明顯這兩部分物件所佔有的記憶體是不連續的,也就導致了記憶體碎片化。 永遠有一個Survivor是空的,另一個非空的Survivor無碎片。 (10)為什麼Eden:S1:S2=8:1:1 新生代中的物件大多數是“朝生夕死”的 (11)堆記憶體中的物件都是所有執行緒共享的嗎? JVM預設為每個執行緒在Eden上開闢一個buffer區域,用來加速物件的分配,稱之為TLAB,全稱:Thread Local Allocation Buffer。 物件優先會在TLAB上分配,但是TLAB空間通常會比較小,如果物件比較大,那麼還是在共享區域分配。 (12)Java虛擬機器棧的深度越大越好嗎? 執行緒棧的大小是個雙刃劍,如果設定過小,可能會出現棧溢位,特別是在該執行緒內有遞迴、大的迴圈時出現溢位的可能性更大,如果該值設定過大, 就有影響到建立棧的數量,如果是多執行緒的應用,就會出現記憶體溢位的錯誤。 (13)垃圾收集器一般如何選擇 a‐用預設的 b‐關注吞吐量:使用Parallel c‐關注停頓時間:使用CMS、G1 d‐記憶體超過8GB:使用G1 e‐記憶體很大或希望停頓時間幾毫秒級別:使用ZGC (14)什麼是方法內聯? 正常呼叫方法時,會不斷地往Java虛擬機器棧中存放新的棧幀,這樣開銷比較大,其實jvm虛擬機器內部為了節省這樣開銷,可以把一些方法放到同一個 棧幀中執行。 (15)什麼樣的情況下物件會進入Old區? a‐大物件 b‐到了GC年齡閾值 c‐擔保機制 d‐動態物件年齡判斷 (16)聊聊Minor GC、Major GC、Full GC發生的時機 Minor GC:Eden或S區空間不足或發生了Major GC Major GC:Old區空間不足 Full GC:Old空間不足,元空間不足,手動呼叫System.gc())