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

JVM解密之解構類加載與GC垃圾回收機制詳解

 更新時間:2025年02月19日 11:00:22   作者:韻秋梧桐  
本文主要介紹了Java虛擬機(JVM)的內存劃分、類加載機制以及垃圾回收機制,JVM的內存劃分為程序計數器、棧、堆和方法區(qū),類加載機制包括類加載過程、加載器模型和雙親委派模型,垃圾回收機制主要包括標記-清除、標記-復制、標記-整理和分代回收算法

一. JVM內存劃分

JVM 其實是一個 Java 進程,該進程會從操作系統(tǒng)中申請一大塊內存區(qū)域,提供給 Java 代碼使用,申請的內存區(qū)域會進一步做出劃分,給出不同的用途。

其中最核心的是棧,堆,方法區(qū)這幾個區(qū)域:

  • 堆,用來放置 new 出來的對象,類成員變量。
  • 棧,維護方法之間的調用關系,放置局部變量。
  • 方法區(qū)(舊)/元數據區(qū)(新):放的是類加載之后的類對象(.class文件),靜態(tài)變量,二進制指令(方法)。

細分下來 JVM 的內存區(qū)域包括以下幾個:程序計數器,棧,堆,方法區(qū),圖中的元數據區(qū)可以理解為方法區(qū)。

程序計數器:內存最小的一塊區(qū)域,保存了下一條要執(zhí)行的指令(字節(jié)碼)的地址,每個線程都有一份

:儲存局部變量與方法之間的調用信息,每一個線程都有一份,但要注意“棧是線程私有的”這種說法是不準確的,私有的意思是我的你是用不了的,但實際上,一個線程棧上的內容,是可以被另一個線程使用到的。

棧在 JVM 區(qū)域劃分中分為兩種,一種是 Java 虛擬機棧,另外一種是本地方法棧,這兩種棧功能非常類似,當方法被調用時,都會同步創(chuàng)建棧幀來存儲局部變量表、操作數棧、動態(tài)連接、方法出口等信息。

只不過虛擬機棧是為虛擬機執(zhí)行 Java 方法(也就是字節(jié)碼)服務,而本地方法棧則是給 JVM 內部的本地(Native)方法服務的(JVM 內部通過 C++ 代碼實現(xiàn)的方法)。

:儲存對象以及對象的成員變量,一個 JVM 進程只有一個,多個線程共用一個堆,是內存中空間最大的區(qū)域,Java 堆是垃圾回收器管理的內存區(qū)域,后文介紹 GC 的時候細說。

方法區(qū): JDK 1.8 開始,叫做元數據區(qū),存儲了類對象,常量池,靜態(tài)成員變量,即時編譯器編譯后的代碼緩存等數據;所謂的“類對象”,就是被static修飾的變量或方法就成了類屬性,.java文件會被編譯成.class文件,.class會被加載到內存中,也就被 JVM 構造成類對象了,類對象描述了類的信息,如類名,類有哪些成員,每個成員叫什么名字,權限是什么,方法名等;同樣一個 JVM 進程只有一個元數據區(qū),多個線程共用一塊元數據區(qū)內存。

要注意 JVM 的線程和操作系統(tǒng)的線程是一對一的關系,每次在 Java 代碼中創(chuàng)建的線程,必然會在系統(tǒng)中有一個對應的線程。

二. 類加載機制

1. 類加載過程

類加載就是把.java文件使用javac編譯為.class文件,從文件(硬盤)被加載到內存中(元數據區(qū)),得到類對象的過程。(程序要想運行,就需要把依賴的“指令和數據”加載到內存中)。

這個圖片所示的類加載過程來自官方文檔,類加載包括三個步驟:Loading, LinkingInitialization。

官方文檔:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html

下面就來了解一下這三步是在干什么:

第一步,加載Loading),找到對應的.class文件,打開并讀取文件到內存中,同時通過解析文件初步生成一個代表這個類的 java.lang.Class 對象。

第二步,連接Linking),作用是建立多個實體之間的聯(lián)系,該過程有包含三個小過程:

  • 驗證Verification),主要就是驗證讀取到的內容是不是和規(guī)范中規(guī)定的格式完全匹配,如果不匹配,那么類加載失敗,并且會拋出異常;一個.class文件的格式如下:

  • 通過觀察.class文件結構,其實.class文件把.java文件的核心信息都保留了下來,只不過是使用二進制的方式重新進行組織了,.class文件是二進制文件,這里的格式有嚴格說明的,哪幾個字節(jié)表示什么,java官方文檔都有明確規(guī)定。 來自官方文檔:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1
  • 準備Preparation),給類對象分配內存空間(先在元數據區(qū)占個位置),并為類中定義的靜態(tài)變量分配內存,此時類變量初始值也就都為 0 值了。
  • 解析Resolution),針對字符串常量初始化,將符號引用轉為直接引用;字符串常量,得有一塊內存空間,存這個字符的實際內容,還得有一個引用來保存這個內存空間的起始地址;在類加載之前,字符串常量是在.class文件中的,此時這個引用記錄的并非是字符串常量真正的地址,而是它在文件的偏移量/占位符(符號引用),也就是說,此時常量之間只是知道它們彼此之間的相對位置,不知道自己在內存中的實際地址;在類加載之后,才會真正的把這個字符串常量給填充到特定的內存地址上中,這個引用才能被真正賦值成指定內存地址(直接引用),此時字符串常量之間相對位置還是一樣的;這個場景可以想象你看電影時拿著電影票入場入座。

第三步,初始化(Initialization),這里是真正地對類對象進行初始化,特別是靜態(tài)成員,調用構造方法,進行成員初始化,執(zhí)行代碼塊,靜態(tài)代碼塊,加載父類…

類加載的時機

類加載并不是 Java 程序(JVM)一運行就把所有類都加載了,而是真正用到哪個類才加載哪個;整體是一個“懶加載”的策略;只有需要用的時候才加載(非必要,不加載),就會觸發(fā)以下的加載:

  • 構造類的實例
  • 調用這個類的靜態(tài)方法/使用靜態(tài)屬性
  • 加載子類就會先加載其父類

一旦加載過后后續(xù)使用就不必加載了。

2. 雙親委派模型

雙親委派模型是類加載中的一個環(huán)節(jié),屬于加載階段,它是描述如何根據類的全限定名找到.class文件的過程。

在 JVM 里面提供了一組專門的對象,用來進行類的加載,即類加載器,當然既然雙親委派模型是類加載中的一部分,所以其所描述找.class文件的過程也是類加載器來負責的。

但是想要找全.class文件可不容易,畢竟.class文件可能在 jdk 目錄里面,可能在項目的目錄里面,還可能在其他特定的位置,因此 JVM 提供了多個類加載器,每一個類加載器負責在一個片區(qū)里面找。

默認的類加載器主要有三個:

  • BootStrapClassLoader,負責加載 Java 標準庫里面的類,如 String,Random,Scanner 等。
  • ExtensionClassLoader,負責加載 JVM 擴展庫中的類,是規(guī)范之外,由實現(xiàn) JVM 的組織(Sun/Oracle),提供的額外的功能。
  • ApplicationClassLoader,負責加載當前項目目錄中自己寫的類以及第三方庫中的類。

除了默認的幾個類加載器,程序員還可以自定義類加載器,來加載其他目錄的類,此時也不是非要遵守雙親委派模型,如 Tomcat 就自定義了類加載器,用來專門加載webapps目錄中的.class文件就沒有遵守。

雙親委派模型就描述了類加載過程中的找目錄的環(huán)節(jié),它的過程如下:

如果一個類加載器收到了類加載的請求,首先需要先給定一個類的全限定類名,如:“java.lang.String”。

根據類的全限定名找的過程中它不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此。

因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載(去自己的片區(qū)搜索)。

舉個例子:我們要去找標準庫里面的String.class文件,它的過程大致如下:

  • 首先ApplicationClassLoader類收到類加載請求,但是它先詢問父類加載器是否加載過,即詢問ExtensionClassLoader類是否加載過。
  • 如果ExtensionClassLoader類沒有加載過,請求就會向上傳遞到ExtensionClassLoader類,然后同理,詢問它的父加載器BootstrapClassLoader是否加載過。
  • 如果BootstrapClassLoader沒有加載過,則加載請求就會到BootstrapClassLoader加載器這里,由于BootstrapClassLoader加載器是最頂層的加載器,它就會去標準庫進行搜索,看是否有String類,我們知道String是在標準庫中的,因此可以找到,請求的加載任務完成,這個過程也就結束了。

再比如,這里要加載我自己寫的的Test類,過程如下:

  • 首先ApplicationClassLoader類收到類加載請求,但是它先詢問父類加載器是否加載過,即詢問ExtensionClassLoader類是否加載過。
  • 如果ExtensionClassLoader類沒有加載過,請求就會向上傳遞到ExtensionClassLoader類,然后同理,詢問它的父加載器BootstrapClassLoader是否加載過。
  • 如果BootstrapClassLoader沒有加載過,則加載請求就會到BootstrapClassLoader加載器這里,由于BootstrapClassLoader加載器是最頂層的加載器,它就會去標準庫進行搜索,看是否有Test類,我們知道Test類不在標準庫,所以會回到子加載器里面搜索。
  • 同理,ExtensionClassLoader加載器也沒有Test類,會繼續(xù)向下,到ApplicationClassLoader加載器中尋找,由于ApplicationClassLoader加載器搜索的就是項目目錄,因此可以找到Test類,全過程結束。

如果在ApplicationClassLoader還沒有找到,就會拋出異常。

總的來說,雙親委派模型就是找.class文件的過程,其實也沒啥,就是名字挺哄人。

之所以有上述的查找順序,大概是因為 JVM 代碼是按照類似于遞歸的方式來實現(xiàn)的,就導致了從下到上,又從上到下過程,這個順序,最主要的目的,就是為了保證 Bootstrap 能夠先加載,Application 能夠后加載,這就可以避免說因為用戶創(chuàng)建了一些奇怪的類,引起不必要的 bug。

三. GC垃圾回收機制

在 C/C++ 中內存空間是需要進行手動釋放,如果沒有手動去釋放那么這塊內存空間就會持續(xù)存在,一直到進程結束,并且堆的內存生命周期比較長,不像棧隨著方法執(zhí)行結束自動銷毀釋放,堆默認是不能自動釋放的,這就可能導致內存泄露的問題,進一步導致后續(xù)的內存申請操作失敗。

而在 Java 中引入了 GC 垃圾回收機制,垃圾指的是我們不再使用的內存,垃圾回收就是把我們不用的內存自動釋放了。

GC的好處:

  • 非常省心,使程序員寫代碼更簡單一些,不容易出錯。

GC的壞處:

  • 需要消耗額外的系統(tǒng)資源,也有額外的性能開銷。
  • GC 這里還有一個嚴重的 STW(stop the world)問題,如果有時候,內存中的垃圾已經很多了,這個時候觸發(fā)一次 GC 就會消耗大量系統(tǒng)資源,其他程序可能就無法正常執(zhí)行了;GC 可能會涉及一些鎖操作,就可能導致業(yè)務代碼無法正常執(zhí)行;極端情況下可會卡頓幾十毫秒甚至上百毫秒。

GC 的實際工作過程包含兩部分:

  • 找到/判定垃圾。
  • 再進行垃圾的釋放。

1. 找到需要回收的內存

1.1 哪些內存需要回收?

Java 程序運行時,內存分為四個區(qū),分別是程序計數器,棧,堆,方法區(qū)。

對于程序計數器,它占據固定大小的內存,它是隨著線程一起銷毀的,不涉及釋放,那么也就用不到 GC;對于??臻g,函數執(zhí)行完畢,對應的棧幀自動銷毀釋放了,也不需要 GC;對于方法區(qū),主要進行類加載,雖然需要進行“類卸載”,此時需要釋放內存,但是這個操作的頻率是非常低的;最后對于堆空間,經常需要釋放內存,GC 也是主要針對堆進行釋放的。

在堆空間,內存的分布有三種,一是正在使用的內存,二是不用了但未回收的內存,三是未分配的內存,那內存中的對象,也有三種情況,對象內存全部在使用(相當于對象整體全部在使用),對象的內存部分在使用(相當于對象的一部分在使用),對象的內存不使用(對象也就使用完畢了),對于這三類對象,前兩類不需要回收,只有最后一類是需要回收的。

所以,垃圾回收的基本單位是對象,而不是字節(jié),對于如何找到垃圾,常用有引用計數法與可達性分析法兩種方式,關鍵思路是,抓住這個對象,看看到底有沒有“引用”指向它,沒有引用了,它就是需要被釋放的垃圾。

1.2 基于引用計數找垃圾(Java不采取該方案)

所謂基于引用計數判斷垃圾,就是給每一個對象分配一個計數器(整數),來記錄該對象被多少個引用變量所指,每次創(chuàng)建一個引用指向該對,,計數器就+1,每次該引用被銷毀了計數器就–1,如果這個計數器的值為0則表示該對象需要回收,比如有一個Test對象,它被三個引用所指,所以這個 Test 對象所帶計數器的值就是3。

//偽代碼:
Test t1 = new Test();
Test t2 = t1;
Test t3 = t1;

如果上述的偽代碼是在一個方法中,待方法執(zhí)行完畢,方法中的局部引用變量被銷毀,那么Test對象的引用計數變?yōu)?code>0,此時就會被回收。

由此可見,基于引用計數的方案非常簡單高效并且可靠,但是它擁有兩個致命缺陷:

  1. 內存空間浪費較多(利用率低), 需要給每個對象分配一個計數器,如果按照4個字節(jié)來算;代碼中的對象非常少時無所謂,但如果對象特別多了,占用的額外空間就會很多,尤其是每個對象都比較小的情況下。
  2. 存在循環(huán)引用的問題,會出現(xiàn)對象既不使用也不釋放的情況,看下面舉例子來分析一下。

有以下一段偽代碼:

class Test {
	Test t = null;
}

//main方法中:
Test t1 = new Test(); // 1號對象, 引用計數是1
Test t2 = new Test(); // 2號對象, 引用計數是1
t1.t = t2;            // t1.t指向2號對象, 此時2號對象引用計數是2
t2.t = t1;            // t1.t指向1號對象, 此時1號對象引用計數是2

執(zhí)行上述偽代碼,運行時內存圖如下:

然后,我們把變量t1與t2置為null,偽代碼如下:

//偽代碼:
t1 = null;
t2 = null;

執(zhí)行完上面?zhèn)未a,運行時內存圖如下:

此時 t1 和 t2 引用銷毀了,一號對象和二號對象的引用計數都-1,但由于兩個對象的屬性相互指向另一個對象,計數器結果都是1而不是0造成對象無法及時得到釋放,而實際上這個兩個對象已經獲取不到了(應該銷毀了)。

1.3 基于可達性分析找垃圾(Java采取方案)

Java 中的對象都是通過引用來指向并訪問的,一個引用指向一個對象,對象里的成員又指向別的對象。

所謂可達性分析,就是通過額外的線程,將整個 Java 程序中的對象用鏈式/樹形結構把所有對象串起來,從根節(jié)點出發(fā)去遍歷這個樹結構,所有能訪問到的對象,標記成“可達”,不能訪問到的,就是“不可達”,JVM 有一個所有對象的名單(每 new 一個對象,JVM 都會記錄下來,JVM 就會知道一共有哪些對象,每個對象的地址是什么),通過上述遍歷,將可達的標記出來,剩下的不可達的(未標記的)就可以作為垃圾進行回收了。

可達性分析的起點稱為GC Roots(就是一個Java對象),一個代碼中有很多這樣的起點,把每個起點都遍歷一遍就完成了一次掃描。

對于這個GCRoots,一般很難被回收,它來源可以分為以下幾種:

  • 在虛擬機棧(棧幀中的本地變量表)中引用的對象,例如各個線程被調用的方法堆棧中使用到的參數、局部變量、臨時變量等。
  • 在本地方法棧中 JNI(即通常所說的Native方法)引用的對象。
  • 常量池中引用所指向的對象。
  • 方法區(qū)中靜態(tài)成員所指向的對象。
  • 所有被同步鎖(synchronized 關鍵字)持有的對象。

可達性分析克服了引用計數的兩個缺點,但它有自己的問題:

  • 需要進行類似于 “樹遍歷”的過程,消耗更多的時間,但可達性分析操作并不需要一直執(zhí)行,只需要隔一段時間執(zhí)行一次尋找不可達對象,確定垃圾就可以,所以,慢一下點也是沒關系的,雖遲,但到。
  • 可達性分析過程,當前代碼中的對象的引用關系發(fā)生變化了,還比較麻煩,所以為了準確的完成這個過程,就需要讓其他的業(yè)務暫停工作(STW問題),但 Java 發(fā)展這么多年,垃圾回收機制也在不斷的更新優(yōu)化,STW 這個問題,現(xiàn)在已經能夠比較好的應對了,雖不能完全消除,但也已經可以讓 STW 的時間盡量短了。

2. 垃圾回收算法

垃圾回收的算法最常見的有以下幾種:

  • 標記-清除算法
  • 標記-復制算法
  • 標記-整理算法
  • 分代回收算法(本質就是綜合上述算法,在堆的不同區(qū)采取不同的策略)

2.1 標記-清除算法

標記其實就是可達性分析的過程,在可達性分析的過程中,會標記可達的對象,其不可達的對象,都會被視為垃圾進行回收。

比如經過一輪標記后,標記狀態(tài)和回收后狀態(tài)如圖:

我們發(fā)現(xiàn),內存是釋放了,但是回收后,未分配的內存空間是零散的不是連續(xù)的,我們知道申請內存的時候得到的內存得是連續(xù)的,雖然內存釋放后總的空閑空間很大,但由于未分配的內存是碎片化的,就有可能申請內存失敗;假設你的主機有 1GB 空閑內存,但是這些內存是碎片形式存在的,當申請 500MB 內存的時候,也可能會申請失敗,畢竟不能保證有一塊大于 500MB 的連續(xù)內存空間,這也是標記-清除算法的缺陷(內存碎片問題)。

2.2 標記-復制算法

為了解決標記-清除算法所帶來的內存碎片化的問題,引入了復制算法。

它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊,每次清理,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的這一塊內存空間一次清理掉。

復制算法的第一步還是要通過可達性分析進行標記,得到哪一部分需要進行回收,哪一部分需要保留,不能回收。

標記完成后,會將還在使用的內存連續(xù)復制到另外一塊等大的內存上,這樣得到的未分配內存一直都是連續(xù)的,而不是碎片化的。

但是,復制算法也有缺陷:

  • 空間利用率低。
  • 如果垃圾少,有效對象多,復制成本就比較大。

2.3 標記-整理算法

標記-整理算法針對復制算法做出進一步改進,其中的標記過程仍然與“標記-清除”算法一致,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向內存空間一端移動,然后直接清理掉邊界以外的內存。

回收時是將存活對象按照某一順序(比如從左到右,從上到下的順序)拷貝到非存活對象的內存區(qū)域,類似于順序表的刪除操作,會將后面的元素搬運到前面。

解決了標記-復制算法空間利用率低的問題,也沒有內存碎片的問題,但是復制的開銷問題并沒有得到解決。

2.4 分代回收

上述的回收算法都有一定的缺陷,分代回收就是將上述三種算法結合起來分區(qū)使用,分代回收會針對對象進行分類,以熬過的 GC 掃描輪數作為“年齡”,然后針對不同年齡采取不同的方案。

分代是基于一個經驗規(guī)律,如果一個東西存在時間長了,那么接下來大概率也會存在(要沒有早就沒有了)。

我們知道 GC 主要是回收堆上的無用內存,我們先來了解一下堆的劃分,堆包括新生代(Young)、老年代(Old),而新生代包括一個伊甸區(qū)(Eden)與兩個幸存區(qū)(Survivor),分代回收算法就會根據不同的代去采取不同的標記-xx算法。

在新生代,包括一個伊甸區(qū)與兩個幸存區(qū),伊甸區(qū)存儲的是未經受 GC 掃描的對象(年齡為 0),也就是剛剛 new 出來的對象。

幸存區(qū)存儲了經過若干輪 GC 掃描的對象,通過實際經驗得出,大部分的 Java 對象具有“朝生夕滅”的特點,生命周期非常短,也就是說只有少部分的伊甸區(qū)對象才能熬過第一輪的 GC 掃描到幸存區(qū),所以到幸存區(qū)的對象相比于伊甸區(qū)少的多,正因為大部分新生代的對象熬不過 GC 第一輪掃描,所以伊甸區(qū)與幸存區(qū)的分配比例并不是1:1的關系,HotSpot 虛擬機默認一個 Eden 和一個 Survivor 的大小比例是 8∶1,正因為新生代的存活率較小,所以新生代使用的垃圾回收算法為標記-復制算法最優(yōu),畢竟存活率越小,對于標記-復制算法,復制的開銷也就很小。

不妨我們將第一個 Survivor 稱為活動空間,第二個 Survivor 稱為空閑空間,一旦發(fā)生 GC,會將 10% 的活動區(qū)間與另外 80% 伊甸區(qū)中存活的對象復制到 10% 的空閑空間,接下來,將之前 90% 的內存全部釋放,以此類推。

在后續(xù)幾輪 GC 中,幸存區(qū)對象在兩個 Survivor 中進行標記-復制算法,此處由于幸存區(qū)體積不大,浪費的空間也是可以接受的。

在繼續(xù)持續(xù)若干輪 GC 后(這個對象已經再兩個幸存區(qū)中來回考貝很多次了),幸存區(qū)的對象就會被轉移到老年代,老年代中都是年齡較老的對象,根據經驗,一個對象越老,繼續(xù)存活的可能性就越大(要掛早掛了),因此老年代的 GC 掃描頻率遠低于新生代,所以老年代采用標記-整理的算法進行內存回收,畢竟老年代存活率高,對于標記-整理算法,復制轉移的開銷很低。

還要注意一個特殊情況,如果對象非常大,就直接進入老年代,因為大對象進行復制算法,成本比較高,而且大對象也不會很多。

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • 淺談Java消息隊列總結篇(ActiveMQ、RabbitMQ、ZeroMQ、Kafka)

    淺談Java消息隊列總結篇(ActiveMQ、RabbitMQ、ZeroMQ、Kafka)

    這篇文章主要介紹了淺談Java消息隊列總結篇(ActiveMQ、RabbitMQ、ZeroMQ、Kafka),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-05-05
  • java并發(fā)編程工具類JUC之LinkedBlockingQueue鏈表隊列

    java并發(fā)編程工具類JUC之LinkedBlockingQueue鏈表隊列

    大家都知道LinkedBlockingQueue 隊列是BlockingQueue接口的實現(xiàn)類,所以它具有BlockingQueue接口的一切功能特點,他還提供了兩種構造函數,本文中通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧
    2021-06-06
  • Java8接口中引入default關鍵字的本質原因詳析

    Java8接口中引入default關鍵字的本質原因詳析

    Default方法是在java8中引入的關鍵字,也可稱為Virtual extension methods—虛擬擴展方法,這篇文章主要給大家介紹了關于Java8接口中引入default關鍵字的本質原因,需要的朋友可以參考下
    2022-01-01
  • java實現(xiàn)動態(tài)上傳多個文件并解決文件重名問題

    java實現(xiàn)動態(tài)上傳多個文件并解決文件重名問題

    這篇文章主要為大家詳細介紹了java實現(xiàn)動態(tài)上傳多個文件,并解決文件重名問題的方法,感興趣的小伙伴們可以參考一下
    2016-03-03
  • SpringMVC實現(xiàn)上傳下載文件

    SpringMVC實現(xiàn)上傳下載文件

    這篇文章主要為大家詳細介紹了SpringMVC實現(xiàn)上傳下載文件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • Spring Boot集成tablesaw插件快速入門示例代碼

    Spring Boot集成tablesaw插件快速入門示例代碼

    Tablesaw是一款Java的數據可視化庫,數據解析庫,主要用于加載數據,對數據進行操作(轉化,過濾,匯總等),類比Python中的Pandas庫,本文介紹Spring Boot集成tablesaw插件快速入門Demo,感興趣的朋友一起看看吧
    2024-06-06
  • spring cloud整合ribbon問題及解決方案

    spring cloud整合ribbon問題及解決方案

    很多小伙伴在整合ribbon都出了相同的問題,今天特地為大家整理了該問題的解決方案,文中有非常詳細的圖文解說,對出現(xiàn)同樣問題的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05
  • Java對象深復制與淺復制實例詳解

    Java對象深復制與淺復制實例詳解

    這篇文章主要介紹了 Java對象深復制與淺復制實例詳解的相關資料,需要的朋友可以參考下
    2017-05-05
  • Spring Boot2如何構建可部署的war包

    Spring Boot2如何構建可部署的war包

    這篇文章主要介紹了Spring Boot2如何構建可部署的war包,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • Spring Boot Admin Server管理客戶端過程詳解

    Spring Boot Admin Server管理客戶端過程詳解

    這篇文章主要介紹了Spring Boot Admin Server管理客戶端過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03

最新評論