深入理解Java虛擬機:Java垃圾回收機制

本篇內容包括:JAVA 垃圾回收機制概述、有哪些內存需要回收、如何回收(標記-清除、標記-整理(標記-清除-壓縮)、復制(標記-復制-清除)、分代收集等算法) 以及 何時進行垃圾回收等內容!

一、概述

Java 與 C++ 之間有一堵由內存動態分配和垃圾收集技術所圍成的"高墻",墻外面的人想進去,墻里面的人卻想出來

說起垃圾收集(Garbage Collection,下文簡稱GC),有不少人把這項技術當作Java語言的伴生產物。事實上,垃圾收集的歷史遠遠比Java久遠,在1960年誕生于麻省理工學院的Lisp是第一門開始使用內存動態分配和垃圾收集技術的語言。

當 Lisp 還在胚胎時期是,其作者 John McCarthy 就思考過垃圾收集器需要完成的三件事情:

  1. 有哪些內存需要回收?
  2. 什么時候回收?
  3. 如何回收?

二、有哪些內存需要回收?——對象已死?

在堆里面存放著 Java 世界中幾乎所有的對象實例,垃圾收集器在對堆進行回收前,第一件事情就是要確定這些對象之中哪些還“存活”著,哪些已經“死去”(即不可能再被任何途徑 使用的對象)。

1、引用計數算法

引用計數是垃圾收集器中的早期策略。在這種方法中,堆中每個對象實例都有一個引用計數。當一個對象被創建時,就將該對象實例分配給一個變量,該變量計數設置為 1。當任何其它變量被賦值為這個對象的引用時,計數加 1(a = b,則b引用的對象實例的計數器+1),但當一個對象實例的某個引用超過了生命周期或者被設置為一個新值時,對象實例的引用計數器減 1。任何引用計數器為 0 的對象實例可以被當作垃圾收集。當一個對象實例被垃圾收集時,它引用的任何對象實例的引用計數器減 1。

優缺點:

  • 優點:引用計數收集器可以很快的執行,交織在程序運行中。對程序需要不被長時間打斷的實時環境比較有利。

  • 缺點:無法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能為0。

2、可達性分析算法

可達性分析算法是從離散數學中的圖論引入的,程序把所有的引用關系看作一張圖,從一個節點 GC ROOT 開始,尋找對應的引用節點,找到這個節點以后,繼續尋找這個節點的引用節點,當所有的引用節點尋找完畢之后,剩余的節點則被認為是沒有被引用到的節點,即無用的節點,無用的節點將會被判定為是可回收的對象。

img

在Java語言中,可作為 GC Roots 的對象包括下面幾種:

  • 棧幀中的局部變量表中的 reference 引用所引用的對象;
  • 方法區中 static 靜態引用的對象;
  • 方法區中 final 常量引用的對象;
  • 本地方法棧中 JNI(Native方法)引用的對象;
  • Java 虛擬機內部的引用, 如基本數據類型對應的 Class 對象, 一些常駐的異常對象(比如 NullPointExcepiton、OutOfMemoryError) 等, 還有系統類加載器;
  • 所有被同步鎖(synchronized關鍵字) 持有的對象;
  • 反映 Java 虛擬機內部情況的 JMXBean、 JVMTI 中注冊的回調、 本地代碼緩存等。

這個算法的基本思路就是通過一系列的稱為 GC Roots 的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots 沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots 到這個對象不可達)時,則證明此對象是不可用的。


三、如何回收?

在 JVM 中主要的垃圾收集算法有:標記-清除、標記-整理(標記-清除-壓縮)、復制(標記-復制-清除)、分代收集等算法。這幾種收集算法互相配合,針對不同的內存區域采取對應的收集算法實現(這里具體是由相應的垃圾收集器實現)。

垃圾回收涉及到大量的程序細節,而且各個平臺的虛擬機操作內存的方式也不一樣,但是他們進行垃圾回收的算法是通用的,所以這里我們也只介紹幾種通用算法。

1、標記-清除算法

算法實現:分為標記-清除兩個階段,首先根據上面的根搜索算法標記出所有需要回收的對象,在標記完成后,然后在統一回收掉所有被標記的對象。

缺點:

  • 效率低:標記和清除這兩個過程的效率都不高。
  • 容易產生內存碎片:因為內存的申請通常不是連續的,那么清除一些對象后,那么就會產生大量不連續的內存碎片,而碎片太多時,當有個大對象需要分配內存時,便會造成沒有足夠的連續內存分配而提前觸發垃圾回收,甚至直接拋出 OutOfMemoryExecption。
1120165-20190701213406016-286379646.png

2、復制算法

為了解決標記-清除算法的兩個缺點,復制算法誕生了。

算法實現:將可用內存按容量劃分為大小相等的兩塊區域,每次只使用其中一塊,當這一塊的內存用完了,就將還活著的對象復制到另一塊區域上,然后再把已使用過的內存空間一次性清理掉。

優缺點:

  • 優點:每次都是只對其中一塊內存進行回收,不用考慮內存碎片的問題,而且分配內存時,只需要移動堆頂指針,按順序進行分配即可,簡單高效。

  • 缺點:將內存分為兩塊,但是每次只能使用一塊,也就是說,機器的一半內存是閑置的,這資源浪費有點嚴重。并且如果對象存活率較高,每次都需要復制大量的對象,效率也會變得很低。

img

3、標記-整理算法

上面我們說過復制算法會浪費一半的內存,并且對象存活率較高時,會有過多的復制操作,效率低下。如果對象存活率很高,基本上不會進行垃圾回收時,標記-整理算法誕生了。

算法實現:首先標記出所有存活的對象,然后讓所有存活對象向一端進行移動,最后直接清理到端邊界以外的內存。

局限性:只有對象存活率很高的情況下,使用該算法才會效率較高。

1120165-20190701215456537-787486222.png

4、分代收集算法

當前商業虛擬機都是采用此算法,但是其實這不是什么新的算法,而是上面幾種算法的合集。

算法實現:根據對象的存活周期不同將內存分為幾塊,然后不同的區域采用不同的回收算法。

對于 HotSpot 虛擬機,它將堆空間分為老年代和新生代兩塊區域

  • 對于存活周期較短,每次都有大批對象死亡,只有少量存活的區域,采用復制算法,因為只需要付出少量存活對象的復制成本即可完成收集;

  • 對于存活周期較長,沒有額外空間進行分配擔保的區域,采用標記-整理算法,或者標記-清除算法。

堆有新生代和老年代兩塊區域組成,而新生代區域又分為三個部分,分別是 Eden、From Surivor、To Survivor,比例是8:1:1。

新生代采用復制算法,每次使用一塊 Eden 區和一塊 Survivor 區,當進行垃圾回收時,將 Eden 和一塊 Survivor 區域的所有存活對象復制到另一塊 Survivor 區域,然后清理到剛存放對象的區域,依次循環。

老年代采用標記-清除或者標記-整理算法,根據使用的垃圾回收器來進行判斷。

至于為什么要這樣,這是由于內存分配的機制導致的,新生代存的基本上都是朝生夕死的對象,而老年代存放的都是存活率很高的對象。


四、何時進行垃圾回收

理清了什么是垃圾,怎么回收垃圾,最后一點就是Java虛擬機何時進行垃圾回收呢?

程序員可以調用 System.gc() 方法,手動回收,但是調用此方法表示希望進行一次垃圾回收。但是它不能保證垃圾回收一定會進行,而且具體什么時候進行是取決于具體的虛擬機的,不同的虛擬機有不同的對策。

其次虛擬機會自行根據當前內存大小,判斷何時進行垃圾回收,比如前面所說的,新生代滿了,新產生的對象無法分配內存時,便會觸發垃圾回收機制。

這里需要說明的是宣告一個對象死亡,至少要經歷兩次標記,前面我們說過,如果對象與 GC Roots 不可達,那么此對象會被第一次標記并進行一次篩選,篩選的條件是此對象是否有必要執行 finalize() 方法,當對象沒有覆蓋 finalize() 方法,或者該方法已經執行了一次,那么虛擬機都將視為沒有必要執行 finalize() 方法。

如果這個對象有必要執行 finalize() 方法,那么該對象將會被放置在一個有虛擬機自動建立、低優先級,名為 F-Queue 隊列中,GC 會對 F-Queue進行第二次標記,如果對象在 finalize() 方法中成功拯救了自己(比如重新與GC Roots建立連接),那么第二次標記時,就會將該對象移除即將回收的集合,否則就會被回收。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/535484.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/535484.shtml
英文地址,請注明出處:http://en.pswp.cn/news/535484.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深入理解Java虛擬機:Java垃圾回收器

本篇內容包括:7 種 Jvm 垃圾回收器的介紹、對比 以及 對應的 Jvm 參數設置,這 7 種包括了:Serial、ParNew 以及 Parallel Scavenge 三種新生代回收器 和 :Serial Old、Parallel Old 以及 CMS 三種老年代回收器,此外還有…

oracle跨越千年處理

如果指定的兩位年份0-4950-99 如果當前 的兩位年 份是 0-49返回的日期是當前世紀返回的日期是上個世紀50-99返回的日期是下個世紀返回的日期是當前世紀 current yearSpecified DateRR FormatYY Format199527-OCT-9519951995199527-OCT-171951917200127-OCT-1720012017200127-OC…

網絡協議:什么是網絡分層的七四五

本篇內容包括:網絡分層七層、五層、四層網絡協議概念的介紹,IOS 體系結構的介紹與構成、TCP/IP體系結構的簡介及與IOS體系的關系 以及五層體系結構的介紹。 一、七層、五層、四層網絡協議概念 1、關于網絡協議 網絡協議,即是指計算機網絡中…

查看表空間相關命令

默認表空間數據文件大小根據DATA BLOCKS的大小有關,默認最大為32GB表空間達到32G,只能增加數據文件alter tablespace 表空間名 add datafile 數據文件路徑‘ size 500m autoextend on next 100m maxsize 10000M;未達到32G,修改數據文件的擴展…

網絡協議:一文搞懂Socket套接字

本篇內容包括:Socket 套接字的簡介、Socket 套接字的分類、Java 中的 Socket 即 java.net.ServerSocket、java.net.Socket 的使用,以及Java 使用套接字 Scoket 編程的Demo。 一、Socket 簡介 TCP(傳輸控制協議)是一種面向連接的、…

RESETLOGS

使用resetlogs選項,會把當前的日志序號(log sequence number)重設為1,并拋棄所有日志信息。在以下條件時需要使用resetlogs選項: 在不完全恢復(介質恢復); 使用備份控制文件。 使…

網絡協議:透徹解析HTTP協議

本篇內容包括:HTTP 協議定義及其特點概述、關于 URL 定義及分類概述、Request 請求、Response 響應 以及 瀏覽器訪問一個網站的全過程 等內容… 一、HTTP 協議概述 HTTP(HyperText Transfer Protocol) 即 超文本傳輸協議,它是一種…

oracle參數文件和口令文件

外部 審核 口令:記錄超級用戶的用戶名和口令,做sys用戶的安全審核 oracle9以后全部使用sys登錄,但需要使用as sysdba ,之前版本需要使用internal o7字典打開 只要用戶和密碼存在于口令文件,就可以以sysdba登錄&#…

innobackup備份恢復實操步驟--gtid復制(1)(1)

首先在主庫進行備份: 備份命令: Innobackupex --defaults-file/app/dbcluster/sgrdb/mysql/my19103.cnf --no-timestamp --userdbscale --passwordS6000dbscale --host10.157.43.224 --port19103 /data/backup 如果使用setsid: setsid …

Redis系列:Redis的概述與安裝

Redis(Remote Dictionary Server) 是一個使用 C 語言編寫的,開源的(BSD許可)高性能非關系型(NoSQL)的鍵值對數據庫。 本篇內容包括:Redis 簡介(為什么快?為什么單線程?優…

安裝LibreOffice和字體

#/bin/bash # Check if user is root if [ $(id -u) ! "0" ]; thenecho "Error: You must be root to run this script, please use root"exit 1 fi echo 安裝LibreOffice cd /home/ tar -zxvf LibreOffice_6.3.3_Linux_x86-64_rpm.tar.gz cd /home/LibreO…

xtrabackup備份腳本

#!/bin/sh #備份主機 remote_ip100.0.132.160 Master_ip100.20.132.158 VIP100.20.132.166 #備份用戶 userroot #密碼 password00000 # 返回年月日 backup_datedate %F # 返回時分秒 backup_timedate %H-%M-%S # 返回今天是這周的第幾天 backup_week_daydate %u backup_ok0 #備…

Redis系列:使用Redis實現分布式鎖及相關問題

分布式鎖其實就是,控制分布式系統不同進程共同訪問共享資源的一種鎖的實現。如果不同的系統或同一個系統的不同主機之間共享了某個臨界資源,往往需要互斥來防止彼此干擾,以保證一致性。 本篇內容包括:關于 Redis 與 分布式鎖&…

Redis系列:Redis持久化機制與Redis事務

Redis 是個基于內存的數據庫。那服務一旦宕機,內存中數據必將全部丟失。所以丟失數據的恢復對于 Redis 是十分重要的,我們首先想到是可以從數據庫中恢復,但是在由 Redis 宕機時(說明相關工作正在運行)且數據量很大情況…

Java基礎:Java程序設計環境

按應用范圍,Java 可分為 3 個體系,即 Java SE、Java EE 和 Java ME。Java 語言的開發運行,也離不開 Java 語言的運行環境 JRE。沒有 JRE 的支持,Java 語言便無法運行。當然,如果還想編譯 Java 程序,搞搞小開…

負載均衡策略

輪循均衡(Round Robin):每一次來自網絡的請求輪流分配給內部中的服務器,從1至N然后重新開始。此種均衡算法適合于服務器組中的所有服務器都有相同的軟硬件配置并且平均服務請求相對均衡的情況。 我們的業務web服務器都是同樣配置…

Java基礎:Java數據類型

Java 是一種強類型語言,這就意味著必須為每一個變量聲明一種類型。在 Java 中基本數據類型共有 8 種,包括 4 種整型、2 種浮點型、1 種用于表現 Unicode 編碼的字符單元的字符類型 char 和一種用于表示真值的 boolean 類型 ~ 本篇主要記錄內容包括&#…

TCP連接的建立與終止

TCP連接的建立與終止 1.三次握手 TCP是面向連接的,無論哪一方向另一方發送數據之前,都必須先在雙方之間建立一條連接。在TCP/IP協議中,TCP協議提供可靠的連接服務,連接是通過三次握手進行初始化的。三次握手的目的是同步連接雙方…

日常問題:MySQL排序字段數據相同不能分頁問題

【問題日期】 2022-11-14 22:45:12 【問題描述】 MySQL 排序字段數據相同不能分頁問題:在分頁查詢數據時,按創建時間排序,由于數據是批量創建的,導致部分數據創建時間一樣,而此時分頁查詢數據,翻頁后出現…