欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java虛擬機學習筆記進階篇

 更新時間:2019年06月07日 10:25:20   投稿:laozhang  
在本篇內(nèi)容里小編給大家分享了關(guān)于java虛擬機學習筆記的進階內(nèi)容,需要的朋友們跟著學習下。

上一節(jié)是把大概的流程給過了一遍,但是還有很多地方?jīng)]有說到,后續(xù)的慢慢會涉及到,敬請期待!

這次我們說說垃圾收集器,又名gc,顧名思義,就是收集垃圾的容器,那什么是垃圾呢?在我們這里指的就是堆中那些沒人要的對象。

1.垃圾收集器的由來

為什么要有垃圾收集器啊?不知道有沒有想過這個問題,你說我運行一個程序要什么垃圾收集器???

隨意看一下下面兩行代碼:

User user = new User("root","123456")
user = new User("lisi","123123")

簡單畫一下內(nèi)存圖,可以看到user這個局部變量本來是指向root這個對象,現(xiàn)在改為指向lisi這個對象,那么此時這個root對象沒有人用,假如類似root這樣的對象非常多的話,那么jvm性能就會越來越低,直至最后創(chuàng)建個對象可能都要十幾秒,而且堆內(nèi)存總有一天會裝滿就會報內(nèi)存溢出異常;

所以我們就要想辦法把類似root這種對象給清理掉,這樣才能保證jvm高效的運行;

假如虛擬機沒有提供gc你覺得會怎么樣?其實也行,只不過你每次需要你用代碼手動釋放不需要的對象,關(guān)于這點有好處有壞處,好處就是有利于我們對堆內(nèi)存的控制,壞處就是我們在一些比較復雜的程序之中由于手動釋放內(nèi)存難免會出錯,但是這中錯誤還不怎么明顯,可能要你去慢慢調(diào)試好久才能看到!

所以java就把這種工作自己處理了,讓一個gc線程一直在后臺運行,隨時準備清理不需要用的對象,雖然相當程度上會對jvm性能造成一些影響,但是由于gc太好用了,我們不用再人為的去關(guān)心垃圾對象的釋放,簡化了我們編寫程序的難度,所以這種影響程度完全可以接受!

這里順便一提兩個基本概念,內(nèi)存泄漏和內(nèi)存溢出:

內(nèi)存溢出(Memory Overflow)比較好理解,就是我們保存對象需要的空間太大了,但是申請內(nèi)存比較小,于是裝不下,于是就會報內(nèi)存溢出異常,比如說你申請了一個integer,但給它存了long才能存下的數(shù),那就是內(nèi)存溢出;專業(yè)點的說法就是:你要求分配的內(nèi)存超出了系統(tǒng)能給你的,系統(tǒng)不能滿足需求,于是產(chǎn)生溢出。

內(nèi)存泄漏(Memory Leak)指的就是我們new出來的對象保存在堆中但是沒有釋放,于是堆中內(nèi)存會越來越少,會導致系統(tǒng)運行速度減慢,嚴重情況會使程序卡死;專業(yè)點的說法就是:你用malloc或new申請了一塊內(nèi)存,但是沒有通過free或delete將內(nèi)存釋放,導致這塊內(nèi)存一直處于占用狀態(tài)。

對于我們jvm來說,通常情況下我們不用擔心內(nèi)存泄漏,因為有一個強大的gc在我們程序的背后默默地為我們清理,但是也會有特殊情況,比如當被分配的對象可達但已無用(未對作廢數(shù)據(jù)內(nèi)存單元的賦值null)即會引起,至于這個可達是什么意思,后面會慢慢說到;

相對而言內(nèi)存溢出我們比較常見,還有g(shù)c只會對堆內(nèi)存進行回收,所以靜態(tài)變量是不會回收的;

再順便提一下另外兩個小概念,非守護線程(也叫用戶線程)和守護線程,看下面這個丑陋的程序運行會有幾個線程???

public class User{
 public static void main(String[] args){
  System.out.println("我是java小新人");
 }
}

兩個線程,一個是執(zhí)行main方法的線程,后臺還有g(shù)c執(zhí)行g(shù)c的線程,在這里,用戶線程就是執(zhí)行main方法的那個線程,執(zhí)行g(shù)c的線程就是守護線程,默默地守護者jvm,假如jvm是雅典娜,那么守護線程就是黃金圣斗士;

當用戶線程停止之后整個程序直接停止,守護線程也會終止;但是黃金圣斗士掛了雅典娜還是可以好好活著的繼續(xù)愉快的玩耍的;

2.堆內(nèi)存結(jié)構(gòu)

哎,內(nèi)存中的結(jié)構(gòu)如果真的要通過源代碼去看,簡直讓人崩潰,除了專業(yè)搞這方面的不然真的很難懂,本來我想自己畫一下草圖了,發(fā)現(xiàn)太丑陋了,于是去順手借了一張圖:

途中可以很清楚的看到,整塊堆內(nèi)存分為年輕人聚集的地方和老年人聚集的地方,年輕人比較少趨勢占用1/3空間(新生代),老年人比較多就占用2/3的空間(老年代),然而啊,年輕人又要分分類,分別是Eden區(qū)占新生代8/10,F(xiàn)rom Survivor區(qū)占新生代1/10,To Survivor區(qū)占新生代1/10,emmm。。。我特意查了一下百度翻譯,Eden---->樂園,Survivor----->幸存者;哦~~~我感覺我仿佛明白了命名人的意圖!

那么新生代和老年代到底是干什么的呢?我們創(chuàng)建的對象是放在哪里?。?/p>

新生代:java對象申請內(nèi)存和存放對象的地方,而且存放的對象都是那種死的比較快的對象,很多時候創(chuàng)建沒多久就清理掉了,那些活的時間比較長的對象都被移動到了老年代。

老年代:存大對象比如長字符串、數(shù)組由于需要大量連續(xù)的內(nèi)存空間,可以直接進入老年代;還有長期存活的對象也會進入老年代,具體是多長時間呢,其實默認就是經(jīng)過15 對新生代的清理(Minor Gc)還能活著的對象。

而垃圾收集器對這兩塊內(nèi)存有兩種行為,一種是對新生代的清理,叫做Minor Gc,另外一種是對老年代的清理被叫做Major Gc。

順便提一點:很多博客中都把Major GC和Full GC說成是一種,其實還是有區(qū)別的,因為很多java虛擬機的實現(xiàn)不一樣,所以就有各種各樣的名稱,比如Minor Gc又叫做Young GC,Major GC也可以叫做Old GC,但是Full GC卻有點不同,F(xiàn)ull GC 是清理整個堆空間 —— 包括年輕代、老年代和永久代(也叫做方法區(qū))。因此 Full GC 可以說是 Minor GC 和 Major GC 的結(jié)合。當然在我們這里,為了好理解我們也就把Full GC當作Major GC就可以了?!?/p>

3.篩選清理對象

GC要工作的話,必須首先知道哪些對象要被清理,你想一下,在新生代和老年代有這么多對象,怎么篩選會又快又省事呢?可以有以下兩種方法

1.引用計數(shù)算法,相當于給你創(chuàng)建的對象偷偷的添加一個計數(shù)器,每引用一次這個對象,計數(shù)器就加一,引用失效就減一,當這個計數(shù)器為0的時候,說明這個對象沒有變量引用了,于是我們就可以說這個對象可以被清理了

2.根搜索算法(jvm用的就是這個),這個怎么理解呢?你可以想象現(xiàn)在有一個數(shù)組,這個數(shù)組里面包含了一些東西的引用,我們將這個數(shù)組叫做”GC Root“,然后我們根據(jù)這個數(shù)組中的引用去找到對應(yīng)的對象,看看這個對象中又引用了哪些對象,一直往下找,這樣就形成了很多線路,在這個線路上的對象就叫做”可達對象“,不在這個線路上的對象就是不可達對象,而不可達對象也就是我們要清理的對象;

其中可以作為GC Root的對象:

(1).類中的靜態(tài)變量,當它持有一個指向一個對象的引用時,它就作為root

(2).活動著的線程,可以作為root

(3).一個Java方法的參數(shù)或者該方法中的局部變量,這兩種對象可以作為root

(4).JNI方法中的局部變量或者參數(shù),這兩種對象可以作為root

(5).其它。

關(guān)于這個根搜索算法專業(yè)一點的說法就是:通過一系列的名為“GC Root”的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所有走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時(用圖論來說就是GC Root到這個對象不可達時),證明該對象是可以被回收的。

4.進行垃圾回收

前面已經(jīng)篩選出了我們要清理的對象,但是怎么清理比較快呢?難道要一個一個對象慢慢刪除嘛?就好像你要清理手機中的垃圾,你會一個應(yīng)用一個應(yīng)用去慢慢清理數(shù)據(jù)嗎?當然不可能,這也太浪費時間了!我們當然是用手機管家或者360管家先把要清理的東西給收集起來放在一起,然后我們一清理就是全部,一個字,爽!

ok,在這里也一樣,我們要想辦法把所有的要清理的對象給放在一起清理,有什么辦法呢?

1.標記-----清除算法:這種方法分為兩步,先標記然后清除,其實就是需要回收的對象標記一下,然后就是把有標記的對象全部清理即可;這種方式比較適合對象比較少的內(nèi)存,假如對象太多標記都要好半天,更別說清除了,而且用這種方法清除的內(nèi)存空間會東一塊西一塊,下次再創(chuàng)建一個大的對象可能會出問題1

2.復制算法:按內(nèi)存容量將內(nèi)存劃分為等大小的兩塊。每次只使用其中一塊,當這一塊內(nèi)存滿后將尚存活的對象復制到另一塊上去,把已經(jīng)使用的那塊內(nèi)存直接全部清理掉;這種方法最大的缺陷就是耗內(nèi)存啊,只能用總內(nèi)存的一半,而且如果對象很多復制都要花很多時間。

3.標記----整理算法:結(jié)合以上兩種方法優(yōu)缺點進行改良的一種方法,標記和第一種方法一樣把要清理的對象做好標記,然后把所有標記的對象移動到本內(nèi)存的一個小角落,最后集中力量對那個小角落進行消滅

4.分代收集算法:這是集中了上面三種方法的優(yōu)點所實現(xiàn)的一種最好的方法,是目前大部分JVM所采用的方法,這種算法的核心思想是根據(jù)對象存活的時間不同將內(nèi)存劃分為不同的域,一般情況下將GC堆劃分為新生代和老年代;新生代的特點是每次垃圾回收時都有大量垃圾需要被回收,少數(shù)對象存活,因此可以使用復制算法;老年代的特點是每次垃圾回收時只有少量對象需要被回收,可以選用”標記--清除方法“”或者標記--整理算法“

所以目前大部分JVM的GC都是使用分代收集算法。

5.執(zhí)行GC的步驟

前面說了這么多無非是介紹堆的內(nèi)部結(jié)構(gòu),然后怎么找到要被清理的對象,然后為了提高效率怎么清理最快!

現(xiàn)在我們就大概說說GC的清理步驟(詳細版):

1.我們創(chuàng)建對象的時候會進行一個判斷,極少數(shù)很大的對象直接放進老年代中,除此之外所有新創(chuàng)建的對象都放進新生代的Eden區(qū)中;

2.此時新生代中只有Eden區(qū)中有對象,兩個Survivor區(qū)中是空的;當我們創(chuàng)建了很多對象,使得Eden區(qū)快滿的時候第一次GC發(fā)生(就是執(zhí)行了一次Minior GC),Eden區(qū)和”From“區(qū)(此時“From”區(qū)是空的)存活的對象將會被移動到Surviver區(qū)的“To”區(qū),并且為每個對象設(shè)置一個計數(shù)器記錄年齡,初始值為1;每進行一次GC,會給那些存活的對象設(shè)置一個年齡+1 的操作,默認是當年齡達到15歲,下次GC就會直接把這種”老油條“丟到老年代中。

3.Minior GC之后,會進行一個比較厲害的操作,就是將”To“區(qū)和”From“換個名字,沒錯,就是換個名字,然后進行下一次Minior GC。

4.由于又創(chuàng)建了很多對象使得Eden區(qū)要滿了,于是又一次Minior GC,Eden區(qū)還存活的對象會直接移動到Surviver區(qū)的“To”區(qū),此時”From“區(qū)(這里就是交換名字之前的”To“區(qū))中的對象有兩個地方可以去,要么年齡滿15歲了去老年代,要么就移動到”To“區(qū)

5.此時我們看一下,只有”To“區(qū)的對象是活著的,Eden區(qū)都是垃圾對象可以直接全部清理,”From“區(qū)是空的;不管怎樣,在進行下一次Minior GC之前保證名為”To“的Survivor區(qū)域是空的就ok了

6.當老年代中快要裝滿之后,就會進行一次Major GC,這個清理事件很慢,至少比Minior GC慢十幾倍,甚至更多,所以我們盡量要少執(zhí)行Major GC

注意:如果在移動過程中”To“ 區(qū)被填滿了,剩余的對象會被直接移動到老年代中。還有在每次Minior GC之前會先進性判斷,只要老年代里面的連續(xù)空間大于新生代對象總大小或者歷次晉升的平均大小進行Minor GC,否則進行Major GC。

簡化版:

(1)Eden 區(qū)活著的對象 + From Survivor 存儲的對象被復制到 To Survivor ;

(2)清空 Eden 和 From Survivor ;

(3)顛倒 From Survivor 和 To Survivor 的邏輯關(guān)系: From 變 To , To 變 From 。

(4)老年代的Major GC執(zhí)行時間很長,盡量少執(zhí)行

只有在Eden空間快滿的時候才會觸發(fā) Minor GC 。而 Eden 空間占新生代的絕大部分,所以 Minor GC 的頻率得以降低。當然,使用兩個 Survivor 這種方式我們也付出了一定的代價,如 10% 的空間浪費、復制對象的開銷等。

6.知識點補充

通過查看了很多大佬的博客看到的很多有關(guān)的東西還是挺有趣的,于是簡單做個小筆記:

6.1.新創(chuàng)建的對象是在堆中的新生代的Eden區(qū),由于堆中內(nèi)存是所有線程共享,所以在堆中分配內(nèi)存需要加鎖。而Sun JDK為提升效率,會為每個新建的線程在Eden上分配一塊獨立的空間由該線程獨享,這塊空間稱為TLAB(Thread Local Allocation Buffer)。在TLAB上分配內(nèi)存不需要加鎖,因此JVM在給線程中的對象分配內(nèi)存時會盡量在TLAB上分配。如果對象過大或TLAB用完,則仍然在堆上Eden區(qū)或者老年代進行分配。如果Eden區(qū)內(nèi)存也用完了,則會進行一次Minor GC(young GC)。

6.2.很多人認為方法區(qū)(或者HotSpot虛擬機中的永久代)是沒有垃圾收集的,Java虛擬機規(guī)范中確實說過可以不要求虛擬機在方法區(qū)實現(xiàn)垃圾收集,而且在方法區(qū)進行垃圾收集的“性價比”一般比較低:在堆中,尤其是在新生代中,常規(guī)應(yīng)用進行一次垃圾收集一般可以回收70%~95%的空間,而永久代的垃圾收集效率遠低于此。

6.3對象調(diào)用.finalize方法被調(diào)用后,對象一定會被回收嗎?

在經(jīng)過可達性分析后,到GC Roots不可達的對象可以被回收(但并不是一定會被回收,至少要經(jīng)過兩次標記),此時對象被第一次標記,并進行一次判斷,如果該對象沒有調(diào)用過或者沒有重寫finalize()方法,那么在第二次標記后可以被回收了;否則,該對象會進入一個FQueue中,稍后由JVM建立的一個Finalizer線程中去執(zhí)行回收,此時若對象中finalize中“自救”,即和引用鏈上的任意一個對象建立引用關(guān)系,到GC Roots又可達了,在第二次標記時它會被移除“即將回收”的集合;如果finalize中沒有逃脫,那就面臨被回收。因此finalize方法被調(diào)用后,對象不一定會被回收。

6.4.如果在Survivor空間中相同年齡所有對象大小總和大于Survivor空間的一半,年齡大于或者等于該年齡的對象直接進入老年代。不需要等到15歲。

總結(jié)

這篇說的就是java虛擬機怎么去收集對內(nèi)存的垃圾,首先是要通過可達性分析判斷哪些對象是可達的,哪些是不可達的,那些不可達的對象就是我們要處理的對象!這些不可達對象可能在新生代和老年代都有,在新生代用復制算法去處理垃圾,老年代用標記整理算法處理垃圾,這種處理方式也可以叫做分代收集算法!而且還簡單說了一下Minor GC和Major GC的觸發(fā)方式!

基本的東西就這么多,假如要深入的話可以深入很多,比如我們可以控制新生代的大小,還有很多種垃圾處理器的實現(xiàn)產(chǎn)品等等,都是可以去慢慢了解的。

相關(guān)文章

  • 詳解IDEA中SpringBoot整合Servlet三大組件的過程

    詳解IDEA中SpringBoot整合Servlet三大組件的過程

    這篇文章主要介紹了詳解IDEA中SpringBoot整合Servlet三大組件的過程,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • 查看Java所支持的語言及相應(yīng)的版本信息

    查看Java所支持的語言及相應(yīng)的版本信息

    Java語言作為第一種支持國際化的語言,在Internet從一開始就具有其他語言無與倫比的國際化的本質(zhì)特性,查看Java所支持的語言及相應(yīng)的版本信息可以采用以下代碼進行查詢
    2014-01-01
  • Struts2+uploadify多文件上傳實例

    Struts2+uploadify多文件上傳實例

    這篇文章主要為大家詳細介紹了Struts2+uploadify多文件上傳實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • maven如何在tomcat8中實現(xiàn)自動部署

    maven如何在tomcat8中實現(xiàn)自動部署

    本篇文章主要介紹了maven如何在tomcat8中實現(xiàn)自動部署,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • 詳解使用Spring Boot開發(fā)Restful程序

    詳解使用Spring Boot開發(fā)Restful程序

    本篇文章主要介紹了詳解使用Spring Boot開發(fā)Restful程序,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Java 和 Kotlin Lambda 表達式示例詳解

    Java 和 Kotlin Lambda 表達式示例詳解

    Lambda 表達式是一種簡潔的函數(shù)表達方式,可以把函數(shù)作為一個方法的參數(shù),或者將代碼塊轉(zhuǎn)換為數(shù)據(jù)傳遞,這篇文章主要介紹了Java 和 Kotlin Lambda 表達式示例詳解,需要的朋友可以參考下
    2024-06-06
  • 基于接口實現(xiàn)java動態(tài)代理示例

    基于接口實現(xiàn)java動態(tài)代理示例

    這篇文章主要介紹了基于接口實現(xiàn)java動態(tài)代理示例,需要的朋友可以參考下
    2014-04-04
  • Java的Servlet及其生命周期詳解

    Java的Servlet及其生命周期詳解

    這篇文章主要介紹了Java的Servlet及其生命周期詳解,Servlet是用Java編寫的服務(wù)器端程序,一門用于開發(fā)動態(tài)web資源的技術(shù),其主要功能在與交互式的瀏覽和修改數(shù)據(jù),生成動態(tài)web內(nèi)容,需要的朋友可以參考下
    2023-11-11
  • 手動添加jar包進Maven本地庫內(nèi)的方法

    手動添加jar包進Maven本地庫內(nèi)的方法

    這篇文章主要介紹了手動添加jar包進Maven本地庫內(nèi)的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-08-08
  • java異常處理詳細介紹及實例

    java異常處理詳細介紹及實例

    這篇文章主要介紹了java異常處理詳細介紹及實例的相關(guān)資料,本文對java異常進行了知識層次的總結(jié),需要的朋友可以參考下
    2017-04-04

最新評論