JVM垃圾清除算法
前言:大家好,我是小威,24屆畢業生,在一家滿意的公司實習。本篇將記錄幾次面試中經常被問到的知識點以及對學習的知識點總結和面試題的復盤。
本篇文章記錄的基礎知識,適合在學Java的小白,也適合復習中,面試中的大佬🤩🤩。
如果文章有什么需要改進的地方還請大佬不吝賜教👏👏。
小威在此先感謝各位大佬啦~~🤞🤞
🏠個人主頁:小威要向諸佬學習呀
🧑個人簡介:大家好,我是小威,一個想要與大家共同進步的男人😉😉
目前狀況🎉:24屆畢業生,在一家滿意的公司實習👏👏
🎁如果大佬在準備面試,可以使用我找實習前用的刷題神器哦刷題神器點這里喲
💕歡迎大家:這里是CSDN,我總結知識的地方,歡迎來到我的博客,我親愛的大佬😘
以下正文開始
文章目錄
- 🎄JVM線程私有和共享的區域
- 🎇線程上下文切換
- 🍒如何判斷對象是否存活
- 🍸引用計數法
- 🎍可達性分析法
- 🍖JVM中的垃圾回收算法
- 🧃標記清除算法
- 🥫復制算法
- 🥓標記整理算法
- 🍨如何判斷變量是否線程安全
- 🍻最長遞增子序列
🎄JVM線程私有和共享的區域
JVM線程私有的區域有:虛擬機棧,本地方法棧,程序計數器。
虛擬機棧:主要存儲方法,局部變量,運行的數據。
本地方法棧:主要存儲本地方法(含有Native關鍵字的方法)。
程序計數器:存儲程序運行位置的字節碼行號指示器。
JVM線程共享的區域有:Java堆,元空間
Java堆:存儲所有創建的對象,數組等。
元空間:存儲虛擬機加載的字節碼數據,常量,靜態變量,運行時常量池等。
🎇線程上下文切換
線程上下文切換,也就是CPU不再執行當前的線程,而去執行其他的線程。那有哪些原因會導致線程的上下文切換呢?
當發生上下文切換時,操作系統會保存當前線程的狀態,恢復另一個線程的狀態,此時程序計數器會記住下一條jvm指令的執行地址,同時上文記錄,程序計數器是線程私有的。
🍒如何判斷對象是否存活
判斷對象是否存活有兩種方法:引用計數算法和可達性分析算法。
🍸引用計數法
在對象被創建的時候,會在對象頭中分配一個空間,即計時器,來保存這個對象被引用的次數。如果這個對象被其他的對象引用,它的引用計數器會+1,如果刪除其他對象對這個對象的引用,則它的引用計數會-1,當對象的引用計數為0時,這個對象就會被當成垃圾回收。
優點:
引用計數法實現起來比較簡單,判斷對象是否存活的效率比較高。
缺點:
無法解決對象之間循環引用的問題,不能檢測到環的出現。例如,A和B之間相互引用,此時計數器都會顯示為1,此時A和B都無法進行垃圾回收。
🎍可達性分析法
Java虛擬機中的垃圾回收機制都是采用的可達性分析算法來探索存活的對象的。此種方法工作原理是會掃描java堆中的對象,沿著GC Roots對象往下尋找,看看是否能在此引用鏈中找到該對象,如果找不到的話,證明該對象沒用了,表示該對象可以回收。
可達性分析算法最大的優點之一就是解決了對象之間的相互循環依賴的問題,目前和引用計數法比起來沒有缺點。
🍖JVM中的垃圾回收算法
對于新生代和老年代的對象,在JVM中會采取不同的垃圾回收算法。年輕代的對象一般都是朝生暮死的,創建之后很快就會被回收,而老年代的對象是需要長期存活的,因此用到的算法大不相同。新生代對應的收集方法為“Minor GC”,老年代對應的收集方法稱為“Major GC”,而對于整個堆空間和方法區的回收被稱為“Full GC”
🧃標記清除算法
標記清除算法為最基礎的垃圾收集算法,即為每個對象都分配一個標記為,這個標記位會記錄對象的狀態。標記著所要回收的對象,在標記完成后,統一回收掉所有被標記的對象,也可以標記存活的對象,清理掉未標記的對象。標記清除算法用于老年代的垃圾回收中。
優點: 基于可達性分析算法,實現起來比較簡單,后續的算法都是基于這種思想來實現的。
缺點:
影響最大的一點在于,標記清除算法會使內存空間碎片化,即標記并清除垃圾后,會產生很多不連續的內存空間,這將導致較大的對象因為無法找到連續的內存而提前觸發一次垃圾回收。
如果大部分對象需要回收,就會進行大量的標記和清除操作,存活對象數量多時效率會降低。
🥫復制算法
復制算法被用于新生代的垃圾回收機制中,新生代有三部分,Eden(80%),和兩個survivor區(From Survivor 和 To Survivor)。兩個Survivor區為容量大小相等的兩塊內存,每次只使用其中的一塊內存,當使用的那塊內存用完后,就會將內存中還存活著的對象復制到另一塊內存上,然后把使用過的那塊內存空間清空。
優點: 實現起來比較簡單,效率也比較高,可以保證內存有連續的區域,能夠解決標記清除算法導致的內存碎片問題。
缺點:
可分配的內存空間縮小了一半兒,代價比較高,內存空間浪費比較多; 存活的對象比較多的時候使用復制算法將會導致效率降低。
進行標記清除算法時,會導致應用程序掛起(停頓),即stop the world(STW)。
擴展:
90%以上的對象都是朝生暮死的,所以在新生代中,每次為對象分配內存時會使用Eden區和其中的一塊Survivor區,當發生垃圾回收時,JVM會將Eden和Survivor中存活的對象都復制到另一塊Survivor區域內,之后清理掉Eden區和Survivor區域中的空間。綜上所述,建立新對象時,新生代可用內存空間為整個兒新生代容量的90%(80%的Eden區和10%的Survivor區),如果發生了極少部分情況,即多于10%的對象存活下來了,沒有被垃圾回收器回收掉,此時JVM會觸發空間擔保機制,即當Survivor空間不足以容納一次Minor GC后的存活對象時,就需要依賴老年代進行分配擔保。
🥓標記整理算法
標記整理法是對標記清除算法的一個改進。第一個階段和標記清除算法一樣,都是將對象標記為存活和死亡狀態,然而在第二階段,標記清除算法只是將被標記的對象進行清除,標記整理算法會將存活的對象進行整理并且放到另一個端,然后再把所有的對象清除掉。
標記整理算法用于老年代的回收機制中。
優點: 不會像垃圾清除算法那樣產生不連續的內存碎片空間
不會像復制算法那樣劃分兩個區域,提高了空間的利用率
缺點:
效率上肯定更慢一些,因為多了一步整理的操作過程。
🍨如何判斷變量是否線程安全
對于成員變量和靜態變量
- 如果它們沒有被共享,則它們是線程安全的;
- 如果它們被共享了,根據它們的狀態是否能夠改變,又會分兩種情況:如果只有讀操作,則它們是線程安全的;如果有讀寫操作,則這段代碼是臨界區,是需要考慮線程安全的。
對于局部變量是否線程安全
- 局部變量是線程安全的
- 但局部變量引用的對象則未必線程安全。如果該對象沒有逃離方法的作用訪問,它是線程安全的;如果該對象逃離方法的作用范圍,則是需要考慮線程安全的。
🍻最長遞增子序列
給你一個整數數組 nums ,找到其中最長嚴格遞增子序列的長度。
子序列 是由數組派生而來的序列,刪除(或不刪除)數組中的元素而不改變其余元素的順序。例如,[3,6,2,7] 是數組 [0,3,1,6,2,2,7] 的子序列。
示例 1:
輸入:nums = [10,9,2,5,3,7,101,18]
輸出:4
解釋:最長遞增子序列是 [2,3,7,101],因此長度為 4 。
示例 2:
輸入:nums = [0,1,0,3,2,3]
輸出:4
示例 3:
輸入:nums = [7,7,7,7,7,7,7]
輸出:1
思路:該題讓求出最長的遞增子序列,因此至少需要一次遍歷,考慮到代碼進行到每一步的狀態才可以,所以動態規劃法解決此題比較容易。
代碼+詳解:
class Solution {public int lengthOfLIS(int[] nums) {if(nums.length==0){return 0; //長度為0直接返回0}int [] dp=new int[nums.length];dp[0]=1; //初始化數組,也可以調用庫函數Arrays.fill(dp,1),但是效率會慢些int result=1;//初始化結果for(int i=1;i<nums.length;i++){dp[i]=1;//初始化數組for(int j=0;j<i;j++){if(nums[j]<nums[i]){dp[i]=Math.max(dp[i],dp[j]+1);//循環更新dp[i]的最大值}}result=Math.max(result,dp[i]);}return result;} }文章到這里就結束了,如果有什么疑問的地方請指出,諸佬們一起討論🍻
希望能和諸佬們一起努力,今后進入到心儀的公司
再次感謝各位小伙伴兒們的支持🤞
總結
- 上一篇: 34、CSS高频前端面试题之CSS基础
- 下一篇: 如何大幅提高 Django 网站加载速度