主頁 > 後端開發 > 深入探究JVM之垃圾回收器

深入探究JVM之垃圾回收器

2020-09-28 18:12:15 後端開發

@

目錄
  • 前言
  • 正文
    • 一、垃圾收集演算法
      • 標記-復制
      • 標記-清除
      • 標記-整理
      • 分代回收
    • 二、常用的垃圾回收器
      • Serial/SerialOld
      • ParNew
      • Parallel Scavenge/ParallelOld
      • CMS
      • Garbage First
  • 總結

前言

JVM的自動記憶體管理得益于不斷發展的垃圾回收器,從最初的單執行緒收集到現在并發收集,垃圾回收器的開發者們一直在致力于如何降低GC程序中的停頓時間(STW)以及提高吞吐量,但直到現在也不存在一款完美的垃圾回收器,只能根據不同的場景選擇最合適的,所以需要了解每款垃圾回收器出現的背景、原因,并掌握各種垃圾回收器的設計原理、演算法實作細節以及各個垃圾回收器的優劣對比,這樣才能讓我們在調優時做出最合適的選擇,這部分內容博主準備分為兩篇文章進行總結講解,本篇主要是對垃圾收集演算法的思想以及目前穩定商用的垃圾回收器的講解,

正文

一、垃圾收集演算法

上文分析了JVM判斷物件存活的兩種演算法:參考計數可達性分析,因此垃圾收集演算法的實作也對應的分為參考計數式收集追蹤式收集,而目前JVM中都沒有使用參考計數演算法,所以后面講解的演算法都屬于追蹤式收集,其細分又分為標記-復制標記-清除標記-整理分代回收

標記-復制

復制演算法最初的理論是將可用記憶體分為1:1的兩塊,每次只使用其中一塊,當這塊記憶體滿后,就先標記存活物件并將其復制到另一塊記憶體,然后將滿的記憶體釋放掉,這種演算法非常簡單高效,只需要將標記的存活物件復制到另一半空間,同時記憶體始終保持規整,不會出現記憶體碎片,但缺點也很明顯,可用記憶體減少了一半,另外復制的物件不能太大,否則復制的效率會比較低,
因為新生代中的物件大多“朝生夕死”,在JVM新生代中的垃圾收集器都是采用的復制演算法,但是為避免浪費的空間太多,提出了一種更為優化的復制演算法,稱為Appel式回收,該演算法不再是簡單的“半區復制”,而是將新生代分為了三塊:一塊Eden區和兩塊Survivor區(分別標記為from和to),默認的分配比例是8:1:1(-XX:SurvivorRatio=8表示兩個Survivor區和Eden區比例為2:8,即每個Survivor占10%),每次分配物件都只使用Eden區和其中一塊Survivor區(from區),其中Eden區最大,新物件都在該區域創建,當Eden區滿后,會進行一次MinorGC,并將Eden區和from區中存活物件都復制到to區中,然后調換from和to指標,當然肯定是存在to區裝不下一次MinorGC存活物件的情況,這時就需要老年代進行分配擔保(相關概念在上一篇已經講過),
從上面的演算法程序中堵著門應該會有一個疑惑:為什么需要兩個Survivor區?這里以假設法進行分析,如果沒有Survivor區,那么新生代每次GC后存活物件會直接進入老年代,導致老年代迅速填滿,頻繁的觸發FullGC;如果只有一塊Survivor區,那么為了保證復制演算法的特性(記憶體規整和高效),Eden區經過一次MinorGC后會將物件復制到Survivor區,這時新物件只能在Survivor區創建,否則無法保證記憶體規整,但又由于Survivor區非常小,就會導致很快又觸發有一次MinorGC;而如果有兩塊Survivor區就很好的解決了上面所說的問題,而更多的Survivor區就沒有必要了,

標記-清除

標記清除是最早出現的垃圾回收演算法,由Lisp之父提出,這個演算法也很簡單,首先標記存活的物件,然后統一回收未被標記的物件,相較于復制演算法的缺點也很明顯,效率更低,同時會導致記憶體碎片,為什么效率更低了呢,好比你洗掉檔案,直接格式化檔案夾快還是去檔案夾中找到檔案一個個洗掉更快?另外記憶體碎片會導致堆中明明還有足夠的記憶體,但卻沒有足夠的連續記憶體來存放大物件,導致物件直接進入老年代,

標記-整理

這個演算法就是建立在標記清除的基礎之上,多了一步整理的作業,標記完成后首先將存活的物件移動到一邊,然后清理掉另一邊的記憶體,解決了記憶體碎片帶來的問題,標記-清除標記-整理都適合用在老年代中,而前者相較于后者不用移動記憶體,而移動記憶體是一種非常“危險”的操作,需要暫停其它用戶執行緒的執行,確保記憶體指向的正確性,所以這就是STW出現的原因,就好比你不能在你媽媽打掃屋子的同時邊往地上扔垃圾,

分代回收

分代回收嚴格意義上并不算一種演算法,而是各回收演算法的實踐理論,它建立在兩個分代假說之上:

  • 弱分代假說(Weak Generational Hypothesis):絕大多數物件都是朝生夕滅的,
  • 強分代假說(Strong Generational Hypothesis):熬過越多次垃圾收集程序的物件就越難以消
    亡,

上面兩個假說共同確定了垃圾收集器一致的設計原則,即新生代老年代,在新生代中使用復制演算法,如上所說,大部分物件朝生夕滅,所以只需要將少量存活物件復制到另一塊區域后再統一格式化之前的區域;而老年代因為大量物件存活,只能采用標記清除標記整理演算法,
分代回收可以避免垃圾回收時總是進行全堆掃描,但是也帶來另外一個問題,不同代之間可能會存在參考,若沒有其它的處理手段,那么在進行新生代垃圾回收時除了遍歷GC Roots外,不得不再額外遍歷整個老年代中的物件,所以為解決這個問題,需要添加第三條經驗法則:

  • 跨代參考假說:跨代參考相對于同代參考來說僅占極少數,

兩個相互依賴的物件,基本上是應該是同生同滅的,所以跨代參考數應該是比較少的,那么JVM在掃描時自然就沒必要也不應該去掃描整個老年代,可以在收集區域維護一個資料結構,記錄從非收集區域指向收集區域的參考,那么在掃描時只需要額外掃描這個資料結構就行了,該結構被稱為記憶集(相關細節下一篇分析),

二、常用的垃圾回收器

垃圾回收器是垃圾回收演算法的實作,在虛擬機規范中并沒有定義要如何實作垃圾回收器,所以各大廠商對垃圾回收器的實作有很大差別,但都是在朝著一個方向努力:低延遲、高吞吐量,
在這里插入圖片描述

上圖中展示的就是目前主流的垃圾回收器,有連線的代表兩者可以搭配使用,而打“X”的表示在JDK9中已經廢棄的組合,另外從圖中我們還可以發現除了G1,其它垃圾回收器都只能作用于新生代老年代中的其中一個區域,那么G1是不是表示廢除了分代理論呢?下面來逐個介紹,

Serial/SerialOld

這兩個是最早出現的垃圾回收器,如其名,它們都是單執行緒的垃圾回收器,只適合幾十兆到一兩百兆的堆空間的垃圾回收,如果用于更大的堆空間會導致系統停頓時間較長,想象一下系統每隔一段時間就要停止處理請求幾分鐘甚至更長時間,你能接受么?下圖是他們的作業原理:
在這里插入圖片描述
可以看到新生代或老年代在進行垃圾回收時都會暫停所有的用戶執行緒,圖中的SafePoint表示執行緒能夠安全暫停的時機,即JVM要進行垃圾回收時,不可能隨意暫停所有的執行緒,必須要確保執行緒處于安全點才能暫停它,這里先有這個概念,細節在下一篇進行闡述,
該組合可以通過-XX:+UseSerialGC引數開啟,

ParNew

該收集器就是Serial的多執行緒版本,但在單核處理器環境中表現還不如Serial(涉及執行緒的切換),它默認開啟的收集執行緒數與處理器核心數量相同,在處理器核心非常多的環境中,可以使用-XX:ParallelGCThreads引數來限制垃圾收集的執行緒數,
在這里插入圖片描述
另外需要注意的是它是除了Serial之外唯一可以與CMS配合的垃圾收集器,在激活CMS后(使用-XX:+UseConcMarkSweepGC選項)的默認新生代收集器,也可以使用-XX:+/-UseParNewGC選項來強制指定或者禁用它,在JDK9以后ParNew成為了CMS的一部分,

Parallel Scavenge/ParallelOld

Parallel Scavenge與其它垃圾收集器不同,其它的是追求盡可能小的GC停頓時間,而它主要關注吞吐量,所謂吞吐量就是代碼運行時間/(代碼運行時間 + 垃圾回收時間),比如虛擬機運行100分鐘,垃圾回收耗時1分鐘,那么吞吐量就是99%,但是這款收集器在JDK1.6之前比較尷尬,沒有與之對應的并行的老年代收集器,只能采用SerialOld老年代收集器,使得表現比不上PareNew+CMS的組合,直到ParallelOld出現后,Parallel Scavenge才能真正的展現它吞吐量的優勢,
在這里插入圖片描述
Parallel Scavenge有以下幾個重要的引數:

  • -XX:MaxGCPauseMillis:該引數的值是一個大于0的毫秒數,收集器盡量保證GC停頓時間不超過該值,但是不要天真的認為該值越小越好,該值設定的太小會導致每次GC的回收率降低,垃圾堆積,GC發生的越來越頻繁,比如原先需要100ms收集500M空間,現在設定為50ms,那么可能就只能回收300M或者更小的垃圾,
  • -XX:GCTimeRatio:控制垃圾回收時間比率,比如允許最大垃圾回收時間占總時間的5%,那么需要將該值設定為19(公式是1/(1 + 19)),
  • -XX:+UseAdaptiveSizePolicy:這個引數激活后,就不再需要我們手動設定新生代各區(Eden、from、to)的比例(-XX:SurvivorRatio),晉升老年代物件的大小(-XX:PretenureSizeThreshold),虛擬機會監控運行時的狀態,進行動態的調整,這種方式稱為垃圾收集的自適應調節策略(GC Ergonomics),

CMS

CMS(Concurrent Mark Sweep)是第一款并發垃圾收集器,并發是指垃圾收集可以和用戶執行緒同時進行,同時它也是唯一采用標記清除演算法對老年代進行回收的垃圾回收器,它包含了以下幾個階段:

  • 初始標記:STW,只標記與GC Roots直接關聯的物件
  • 并發標記:和用戶執行緒同時運行,進行可達性分析
  • 重新標記:STW,暫停用戶執行緒,修正上一階段變動的物件
  • 并發清除:最后是并發的清除掉垃圾

在這里插入圖片描述
從上面我們可以發現CMS的整個程序中只有初始標記重新標記是需要暫停用戶執行緒的,而初始標記只是標記與GC Roots直接關聯的物件,所以耗時只和GC Roots的數量有關,非常快;重新標記的耗時會比初始標記略長,但也遠遠比并發標記用時短,所以CMS就是通過細分GC的階段來降低GC的停頓時間,
你可能會好奇為什么需要重新標記并且暫停所有用戶執行緒,因為在與用戶執行緒并發執行的同時肯定會存在參考變動的情況,而要處理這個問題,都是必須要暫停用戶執行緒的,關于參考變動的處理在下一篇會詳細分析,
CMS可以說是一款跨時代的垃圾收集器,可以回收幾個G到-20G左右的堆空間,但它存在以下幾個明顯的缺點:

  • CPU敏感:雖然并發標記并發標記是和用戶執行緒并發執行的,但是也因此占用了系統的資源,導致應用程式忽然變慢,降低吞吐量,CMS默認啟動的執行緒數是(處理器核心數+3)/4,因此當核心數量大于等于4時,GC占用資源不超過25%,但核心數小于4時,就會占用大量系統資源,
  • 大量的記憶體碎片:因為CMS是使用標記清除演算法實作垃圾回收,所以會產生大量的記憶體碎片,為了避免這個問題,CMS采用了一個折中的辦法,即提供一個-XX:+UseCMS-CompactAtFullCollection引數,該引數默認開啟,控制CMS在進行FullGC的同時進行空間整理,但這樣又會導致停頓時間加長,所以還提供了-XX:CMSFullGCsBefore-Compaction引數,控制CMS在進行了多少次不帶整理的FullGC后進行一次帶整理的FullGC,默認值是0,即每次FullGC都會整理,該引數JDK9后被廢棄,
  • 浮動垃圾:因為最終清除的程序也是和用戶執行緒并發執行的,因此這個程序中必然會產生新的垃圾,這一部分垃圾需要預留空間來存放,等待下一次GC的時候再清理,因此會浪費一部分空間,在JDK5的默認配置下,當老年代使用空間超過68%時就會進行GC,到JDK6時,這個閾值就提高到了92%,另外也可以通過-XX:CMSInitiatingOccu-pancyFraction引數控制,但該值越高,那么并發清理程序中可使用的記憶體就越小,當放不下時,就會出現一次Concurrent Mode Failure,這時候虛擬機就會凍結執行緒并采用SerialOld進行垃圾回收,導致停頓時間變得更長,

Garbage First

G1是目前最前沿且可商用的垃圾收集器,另外還有ZGC等更為前沿的垃圾收集器還處于試驗階段,它與其它垃圾收集器不同的是,他將堆空間化整為零,將記憶體區域劃分為多個大小相等的獨立區域(Region),使得它可以回收堆中的任何一個區域,而不是像其它的垃圾收集器要么只能回收新生代,要么只能回收老年代,但不是說G1就沒有新生代和老年代了,它的每個Region都可以根據需要扮演Eden、Survivor或老年代,垃圾收集器也會針對不同角色的Region采用不同的策略去處理,
在這里插入圖片描述
每個Region的大小可以通過-XX:G1HeapRegionSize設定,取值范圍為1M~32M,且必須為2的N次冪,超過單個Region一半容量的物件即為大物件,而對于超過整個Region的物件將會使用多個連續的Humongous空間存放,G1大多數情況下都把Humongous作為老年代一部分看待,
在這里插入圖片描述
G1的運行程序如上,它也包含了以下4個步驟:

  • 初始標記:STW,也是只標記GC Roots直接關聯的物件,并修改TAMS的指標值(G1為每一個Region設計了兩個名為TAMS(Top at Mark Start)的指標,把Region中的一部分空間劃分出來用于并發回收程序中的新物件分配,并發回收時新分配的物件地址都必須要在這兩個指標位置以上,垃圾回收時也不會回收這部分空間),這個程序耗時很短,而且是借用進行 Minor GC 的時候同步完成的,所以 G1 收集器在這個階段實際并沒有額外的停頓,
  • 并發標記:可達性分析找出要回收的物件,在物件掃描完成后,由于是與用戶執行緒并發執行的,所以存在參考變動的物件,這部分物件會由SATB演算法來解決(原始快照,下一篇詳細分析),
  • 最終標記:STW,處理并發階段遺留的少量遺留的SATB記錄,
  • 篩選回收:根據用戶設定的-XX:MaxGCPauseMillis最大GC停頓時間對Region進行排序,并回收價值最大的Region,盡量保證滿足引數設定的值(該值效果和Parallel Scavenge部分講解的是一樣的),這里的回收演算法就是講存活的物件復制到空的Region中,即G1區域Region之間采用的是復制演算法,而整體上采用的是標記整理演算法

G1適合上百G的堆空間回收,與CMS的權衡在6~8G之間,較大的堆記憶體才能凸顯G1的優勢,可以通過-XX:+UseG1GC引數開啟,

總結

本篇是對常用垃圾收集器的實作原理的整體性分析比較,這一部分是必須掌握的,下一篇則是關于演算法的實作細節,如三色標記是什么、并發標記程序中參考變動如何解決、跨代參考如何處理等等一系列問題,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/134850.html

標籤:Java

上一篇:java培訓主要包含哪些方面的學習?

下一篇:Spring Security 實戰干貨:圖解用戶是如何登錄的

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more