Java實戰(zhàn)之OutOfMemoryError異常問題及解決方法
在Java虛擬機規(guī)范的描述中,除了程序計數(shù)器外,虛擬機內(nèi)存的其他幾個運行時區(qū)域都有發(fā)生OutOfMemoryError (下文稱OOM)異常的可能。本篇主要結(jié)合著【深入理解Java虛擬機】一書當(dāng)中整理了本篇博客,感興趣的跟著小編一塊來學(xué)習(xí)呀!
本篇文章和上一篇寫到的 Java內(nèi)存區(qū)域劃分 息息相關(guān),如果您對Java內(nèi)存區(qū)域劃分不是很了解,建議了解一下,不然這篇文章讀起來會很痛苦。。。
一、簡言
本節(jié)內(nèi)容的目的有兩個:
- 第一,
通過代碼驗證Java虛擬機規(guī)范中描述的各個運行時區(qū)域儲存的內(nèi)容
; - 第二,希望讀者在工作中遇到實際的內(nèi)存溢出異常時,
能根據(jù)異常的信息快速判斷是哪個區(qū)域的內(nèi)存溢出
,知道怎樣的代碼可能會導(dǎo)致這些區(qū)域的內(nèi)存溢出,以及出現(xiàn)這些異常后該如何處理。
下面代碼的開頭都注釋了執(zhí)行時所需要設(shè)置的虛擬機啟動參數(shù)
(注釋中“VM Args”后面跟著的參數(shù)),這些參數(shù)對實驗的結(jié)果有直接影響,請讀者調(diào)試代碼的時候不要忽略掉。(本篇文章所有案例都采用了JDK1.8版本進行測試)
如果讀者使用控制臺命令來執(zhí)行程序,那直接跟在Java命令之后書寫就可以。如果讀者使用Eclipse IDE,可以在Debug/Run頁簽中的設(shè)置。
二、代碼實戰(zhàn)
1、Java堆溢出
Java堆用于儲存對象實例,我們只要不斷地創(chuàng)建對象,并且保證GC Roots到對象之間有可達路徑
來避免垃圾回收機制清除這些對象,就會在對象數(shù)量到達最大堆的容量限制后產(chǎn)生內(nèi)存溢出異常。
將Java堆設(shè)置大小為20MB,不可擴展(將堆的最小值-Xms參數(shù)與最大值-Xmx參數(shù)設(shè)置為一樣即可避免堆自動擴展),通過參數(shù)-XX:+HeapDumpOnOutOfMemoryError 可以讓虛擬機在出現(xiàn)內(nèi)存溢出異常時Dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲快照以便事后進行分析(內(nèi)存堆轉(zhuǎn)儲快照 指的是溢出后,內(nèi)存當(dāng)中的對象占用情況)。
我用的是ider:
設(shè)置啟動參數(shù):
Xms:最小堆內(nèi)存 Xmx:最大可擴展內(nèi)存
XX:+HeapDumpOnOutOfMemoryError:可以讓虛擬機在出現(xiàn)內(nèi)存溢出異常時Dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲快照以便事后進行分析
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
import java.util.ArrayList; import java.util.List; public class HeapOOM { static class OOMObject{ } public static void main(String[] args) { List<OOMObject> list = new ArrayList<>(); while (true){ list.add(new OOMObject()); } }
運行結(jié)果:
因為設(shè)置了-XX:+HeapDumpOnOutOfMemoryError參數(shù),所以生成了 這個報告??梢圆榭磳ο笳加脙?nèi)存。
Java堆內(nèi)存的OOM異常是實際應(yīng)用中最常見的內(nèi)存溢出異常情況。出現(xiàn)Java堆內(nèi)存溢出時,異常堆棧信息"java.lang.OutOfMemoryError”會跟著進一步提示“Java heapspace"。
要解決這個區(qū)域的異常,一般的手段是首先通過內(nèi)存映像分析工具
(如EclipseMemory Analyzer、Dier的jprofiler)對dump出來的堆轉(zhuǎn)儲快照進行分析
,重點是確認(rèn)內(nèi)存中的對象是否是必要的
,也就是要先分清楚到底是出現(xiàn)了內(nèi)存泄漏(Memory Leak)還是內(nèi)存溢出(Memory Overflow)。
如果是內(nèi)存泄漏,可進一步通過工具查看泄漏對象到GC Roots的引用鏈
。于是就能找到泄漏對象是通過怎樣的路徑與GC Roots相關(guān)聯(lián)并導(dǎo)致垃圾收集器無法自動回收它們的。掌握了泄漏對象的類型信息,以及GC Roots引用鏈的信息,就可以比較準(zhǔn)確地定位出泄漏代碼的位置。
如果不存在泄漏,換句話說就是內(nèi)存中的對象確實都還必須存活著,那就應(yīng)當(dāng)檢查虛擬機的堆參數(shù)
(-Xmx與-Xms),與機器物理內(nèi)存對比看是否還可以調(diào)大,從代碼上檢查是否存在某些對象生命周期過長、持有狀態(tài)時間過長
的情況,嘗試減少程序運行期的內(nèi)存消耗。
后面我會專門寫一篇關(guān)于內(nèi)存分析工具的博客,XX:+HeapDumpOnOutOfMemoryError這個只是有內(nèi)存占用情況,工具可以幫我們看到對象的引用鏈情況。
2、虛擬機棧和本地方法棧溢出
由于在HotSpot虛擬機中并不區(qū)分虛擬機棧和本地方法棧,因此對于HotSpot來說,-Xoss參數(shù)(設(shè)置本地方法棧大小)雖然存在,但實際上是無效的,棧容量只由-Xss參數(shù)設(shè)定。關(guān)于虛擬機棧和本地方法棧,在Java虛擬機規(guī)范中描述了兩種異常。
注意:HotSpot虛擬機的棧容量是不可以動態(tài)擴展的。
- 如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常
- 允許棧空間動態(tài)擴展時,當(dāng)
擴展時無法申請到足夠的內(nèi)存時會拋出OutOfMemoryError異常
public class JavaVMStackSOF { private int stackLength = 1; public void stackLeak() { stackLength++; stackLeak(); } public static void main(String[] args) { JavaVMStackSOF oom = new JavaVMStackSOF(); try { oom.stackLeak(); } catch (Throwable e) { System.out.println("stack length:" + oom.stackLength); throw e; } } }
- 當(dāng)聲明是基本類型的變量的時,其變量名及值(變量名及值是兩個概念)是放在JAVA虛擬機棧中
- 當(dāng)聲明的是引用變量時,所聲明的變量(該變量實際上是在方法中存儲的是內(nèi)存地址值)是放在JAVA虛擬機的棧中,該變量所指向的對象是放在堆類存中的。
實驗結(jié)果表明:在單個線程下,無論是由于棧幀太大,還是虛擬機棧容量太小,當(dāng)內(nèi)存無法分配的時候,虛擬機拋出的都是StackOverflowError異常。換成遠(yuǎn)古時代的Classic虛擬機,這款虛擬機可以支持動態(tài)擴展 棧內(nèi)存的容量
,這時候就會報StackOverflowError異常了。
也就是當(dāng)我設(shè)置-Xss128k和不設(shè)置都是報同樣的錯誤
,并沒有出現(xiàn)內(nèi)存溢出異常,原因就是 HotSpot虛擬機的棧容量是不可以動態(tài)擴展的
,但是值得注意的是我的電腦是16G運行內(nèi)存的,當(dāng)我設(shè)置-Xss128k的時候輸出的長度是將近1000,當(dāng)我不限制-Xss128k大小的時候輸出的長度是20000左右,也就意味著每個線程的棧幀大小默認(rèn)最大是2MB
。
如果測試時不限于單線程,通過不斷地建立線程的方式倒是可以產(chǎn)生內(nèi)存溢出異常,在這種情況下,給每個線程的棧分配的內(nèi)存越大,反而越容易產(chǎn)生內(nèi)存溢出異常。
原因其實不難理解,操作系統(tǒng)分配給每個進程的內(nèi)存是有限制的,譬如32位Windows的單個進程 最大內(nèi)存限制為2GB。HotSpot虛擬機提供了參數(shù)可以控制Java堆和方法區(qū)這兩部分的內(nèi)存的最大值。那么虛擬機棧和本地方法棧內(nèi)存如下:
虛擬機棧和本地方法棧內(nèi)存=2GB-最大堆容量-最大方法區(qū)容量-程序計數(shù)器容量
因此為每個線程分配到的棧內(nèi)存越大,可以建立的線程數(shù)量自 然就越少,建立線程時就越容易把剩下的內(nèi)存耗盡。
通過上面了解到,出現(xiàn)StackOverflowError異常時有錯誤堆??梢蚤喿x,相對來說,比較容易找到問題的所在(一般出現(xiàn)死循環(huán)可能會導(dǎo)致)。
如果是建立過多線程導(dǎo)致的內(nèi)存溢出,而不是棧溢出,在不能減少線程數(shù)或者更換64位虛擬機的情況下,就只能通過減少最大堆和減少棧容量來換取更多的線程
。如果沒有這方面的經(jīng)驗,這種通過“減少內(nèi)存”的手段來解決內(nèi)存溢出的方式會比較難以想到。
public class JavaVMStackOOM { private void dontStop(){ while (true){ } } public void stackLeakByThread(){ while (true){ Thread thread = new Thread(new Runnable() { @Override public void run() { dontStop(); } }); thread.start(); } } public static void main(String[] args) { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); } }
注意
重點提示一下,如果讀者要嘗試運行上面這段代碼,記得要先保存當(dāng)前的工作,由于在 Windows平臺的虛擬機中,Java的線程是映射到操作系統(tǒng)的內(nèi)核線程上,無限制地創(chuàng)建線程會對操 作系統(tǒng)帶來很大壓力,上述代碼執(zhí)行時有很高的風(fēng)險,可能會由于創(chuàng)建線程數(shù)量過多而導(dǎo)致操作系統(tǒng) 假死
(電腦可能直接死機)。
在32位操作系統(tǒng)下的運行結(jié)果:
原因:32位有進程大小內(nèi)存限制。
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread
注意:如果要測試上面內(nèi)存溢出代碼,記住先保存當(dāng)前的工作,避免電腦卡死帶來的麻煩。
3、運行時常量池溢出
由于運行時常量池是方法區(qū)的一部分
,所以這兩個區(qū)域的溢出測試可以放到一起進行。前面曾經(jīng) 提到HotSpot從JDK 7開始逐步“去永久代”的計劃,并在JDK 8中完全使用元空間來代替永久代
,在此我們就以測試代碼來觀察一下,使用“永久代”還是“元空間”來實現(xiàn)方法區(qū),對程序有什么 實際的影響。
String::intern()是一個本地方法
,它的作用是如果字符串常量池中已經(jīng)包含一個等于此String對象的 字符串,則返回代表池中這個字符串的String對象的引用
;否則,會將此String對象包含的字符串添加 到常量池中,并且返回此String對象的引用
。
import java.util.ArrayList; import java.util.List; public class RuntimeConstantPoolOOM { public static void main(String[] args) { // 使用List保持著常量池引用,避免Full GC回收常量池行為 List<String> list = new ArrayList<>(); // 10MB的PerSize在integer范圍內(nèi)足夠產(chǎn)生00M int i = 0; while (true){ list.add(String.valueOf(i++).intern()); } } }
- JDK7及以前(了解):-XX:PermSize設(shè)置永久代初始大小。-XX:MaxPermSize設(shè)置永久代最大可分配空間。(JDK7目前已經(jīng)很少用了,這兩個參數(shù)在JDK8及以后已經(jīng)沒有了,所以不必掌握,了解一下)
- JDK8及以后:可以使用-XX:MetaspaceSize和-XX:MaxMetaspaceSize設(shè)置元空間初始大小以及最大可分配大小。
使用JDK 7或更高版本的JDK來運行這段程序并不會得到相同的結(jié)果,無論是在JDK 7中繼續(xù)使 用-XX:MaxPermSize
參數(shù)或者在JDK 8及以上版本使用-XX:MaxMeta-spaceSize
參數(shù)把方法區(qū)容量同 樣限制在6MB,都不會出現(xiàn)溢出異常,循環(huán)將一直進行下去,永不停歇。出現(xiàn)這種變 化,是因為自JDK 7起,原本存放在永久代的字符串常量池被移至Java堆之中,所以在JDK 7及以上版 本,限制方法區(qū)的容量對該測試用例來說是毫無意義的
。
在JDK1.7中(包括1.7以上)常量池存儲的不再是對象,而是對象引用,真正的對象是存儲在堆中的。把RuntimeConstantPoolOOM.java運行時的VM參數(shù)改為如下(設(shè)置堆大?。┧荆?/p>
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
運行結(jié)果:
查看生成的堆內(nèi)存快照:
4、方法區(qū)溢出
方法區(qū)用于存放Class的相關(guān)信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。對于這個區(qū)域的測試,基本的思路是運行時產(chǎn)生大量的類去填滿方法區(qū),直到溢出。雖然直接使用Java SE API也可以動態(tài)產(chǎn)生類(如反射時的GeneratedConstructorAccessor 和動態(tài)代理等),但在本次實驗中操作起來比較麻煩。借助CGLib直接操作字節(jié)碼運行時,生成了大量的動態(tài)類。
值得特別注意的是,我們在這個例子中模擬的場景并非純粹是一個實驗,這樣的應(yīng)用經(jīng)常會出現(xiàn)在實際應(yīng)用中:當(dāng)前的很多主流框架,如Spring和Hibernate對類進行增強時,都會使用到CGLib這類字節(jié)碼技術(shù),增強的類越多,就需要越大的方法區(qū)來保證動態(tài)生成的Class可以加載人內(nèi)存
。
測試示例:
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class JavaMethodAreaOOM { public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }); enhancer.create(); } } static class OOMObject { } }
設(shè)置元空間最大空間,和初始化空間參數(shù):
類信息是都存在方法區(qū)的,方法區(qū)在jdk1.8將永久區(qū)改為了元空間。自此以后,常量池在元空間都是存儲的引用。實際對象是在堆中。
-XX:MaxMetaspaceSize=10m -XX:MetaspaceSize=10m
運行結(jié)果:
方法區(qū)溢出也是一種常見的內(nèi)存溢出異常,一個類如果要被垃圾收集器回收,要達成的條件是比 較苛刻的。在經(jīng)常運行時生成大量動態(tài)類的應(yīng)用場景里,就應(yīng)該特別關(guān)注這些類的回收狀況。這類場 景除了之前提到的程序使用了CGLib字節(jié)碼增強和動態(tài)語言外,常見的還有:大量JSP或動態(tài)產(chǎn)生JSP 文件的應(yīng)用(JSP第一次運行時需要編譯為Java類)、基于OSGi的應(yīng)用(即使是同一個類文件,被不同 的加載器加載也會視為不同的類)等。
5、本機直接內(nèi)存溢出
直接內(nèi)存(Direct Memory)并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分,也不是《Java虛擬機規(guī)范》中 定義的內(nèi)存區(qū)域。但是這部分內(nèi)存也被頻繁地使用,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。
直接內(nèi)存:可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆里面的DirectByteBuffer對象作為這塊內(nèi)存的引用`進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復(fù)制數(shù)據(jù)(但是有一點注意,雖然不占用堆內(nèi)存,但是他占用了服務(wù)器內(nèi)存)。
直接內(nèi)存(Direct Memory)的容量大小可通過-XX:MaxDirectMemorySize參數(shù)來指定,如果不 去指定,則默認(rèn)與Java堆最大值(由-Xmx指定)一致。
代碼示例:
越過了DirectByteBuffer類直接通 過反射獲取Unsafe實例進行內(nèi)存分配
(Unsafe類的getUnsafe()
方法指定只有引導(dǎo)類加載器才會返回實 例,體現(xiàn)了設(shè)計者希望只有虛擬機標(biāo)準(zhǔn)類庫里面的類才能使用Unsafe的功能,在JDK 10時才將Unsafe 的部分功能通過VarHandle開放給外部使用
),因為雖然使用DirectByteBuffer分配內(nèi)存也會拋出內(nèi)存溢 出異常,但它拋出異常時并沒有真正向操作系統(tǒng)申請分配內(nèi)存,而是通過計算得知內(nèi)存無法分配就會 在代碼里手動拋出溢出異常,真正申請分配內(nèi)存的方法是Unsafe::allocateMemory()
。
import sun.misc.Unsafe; import java.lang.reflect.Field; public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { unsafe.allocateMemory(_1MB); } } }
運行參數(shù):
-Xmx20M -XX:MaxDirectMemorySize=10M -XX:+HeapDumpOnOutOfMemoryError
運行結(jié)果:
我設(shè)置了-XX:+HeapDumpOnOutOfMemoryError發(fā)現(xiàn)運行完成之后并沒有發(fā)現(xiàn)有內(nèi)存快照。
由直接內(nèi)存導(dǎo)致的內(nèi)存溢出,一個明顯的特征是在Heap Dump文件中不會看見有什么明顯的異常 情況,如果讀者發(fā)現(xiàn)內(nèi)存溢出之后產(chǎn)生的Dump文件很小,而程序中又直接或間接使用了 DirectMemory(典型的間接使用就是NIO),那就可以考慮重點檢查一下直接內(nèi)存方面的原因了。
三、JVM常用的啟動參數(shù)
堆:
-Xms3550m
:設(shè)置JVM初始內(nèi)存為3550M。表示初始化JAVA堆的大小及該進程剛創(chuàng)建出來的時候,他的專屬JAVA堆的大小,一旦對象容量超過了JAVA堆的初始容量,JAVA堆將會自動擴容到-Xmx大小。-Xmx3550m
:設(shè)置JVM最大可用內(nèi)存為3550M。表示java堆可以擴展到的最大值,在很多情況下,通常將-Xms和-Xmx設(shè)置成一樣的,因為當(dāng)堆不夠用而發(fā)生擴容時,會發(fā)生內(nèi)存抖動影響程序運行時的穩(wěn)定性。
棧:
-Xss128k
:規(guī)定了每個線程虛擬機棧及堆棧的大小,一般情況下,256k是足夠的,此配置將會影響此進程中并發(fā)線程數(shù)的大?。ê投咽遣灰粯拥?,不支持動態(tài)擴展)。
方法區(qū):
- JDK7及以前(了解):
-XX:PermSize
設(shè)置永久代初始大小。 -XX:MaxPermSize
設(shè)置永久代最大可分配空間。(JDK7目前已經(jīng)很少用了,這兩個參數(shù)在JDK8及以后已經(jīng)沒有了,所以不必掌握,了解一下)-XX:MaxMetaspaceSize
=10m:設(shè)置元空間最大值,默認(rèn)是-1,即不限制,或者說只受限于本地內(nèi)存 大小。-XX:MetaspaceSize
=10m:指定元空間的初始空間大小,以字節(jié)為單位,達到該值就會觸發(fā)垃圾收集 進行類型卸載,同時收集器會對該值進行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放 了很少的空間,那么在不超過-XX:MaxMetaspaceSize(如果設(shè)置了的話)的情況下,適當(dāng)提高該值。-XX:MinMetaspaceFreeRatio
:作用是在垃圾收集之后控制最小的元空間剩余容量的百分比,可 減少因為元空間不足導(dǎo)致的垃圾收集的頻率。類似的還有-XX:Max-MetaspaceFreeRatio,用于控制最 大的元空間剩余容量的百分比。
內(nèi)存:
-XX:+HeapDumpOnOutOfMemoryError
可以讓虛擬機在出現(xiàn)內(nèi)存溢出異常時Dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲快照以便事后進行分析(內(nèi)存堆轉(zhuǎn)儲快照 指的是溢出后,內(nèi)存當(dāng)中的對象占用情況)
GC:
-XX:-PrintGCDetails
:每次GC時打印詳細(xì)信息。
四、面試題
public static void main(String[] args) { String str1 = new StringBuilder("計算機").append("軟件").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern() == str2); }
這段代碼在JDK 6中運行,會得到兩個false,而在JDK 7中運行,會得到一個true和一個false。在jdk1.8運行也是,true、false。
產(chǎn) 生差異的原因是,在JDK 6中,intern()方法會把首次遇到的字符串實例復(fù)制到永久代的字符串常量池 中存儲,
返回的也是永久代里面這個字符串實例的引用
,而由StringBuilder創(chuàng)建的字符串對象實例在 Java堆上
,所以必然不可能是同一個引用,結(jié)果將返回false。
而JDK 7(以及部分其他虛擬機,例如JRockit)的intern()方法實現(xiàn)就不需要再拷貝字符串的實例 到永久代了,既然字符串常量池已經(jīng)移到Java堆中,那只需要在常量池里記錄一下首次出現(xiàn)的實例引 用即可,因此intern()返回的引用和由StringBuilder創(chuàng)建的那個字符串實例就是同一個。
而對str2比較返 回false,這是因為“java
”這個字符串在執(zhí)行String-Builder.toString()之前就已經(jīng)出現(xiàn)過了
,字符串常量 池中已經(jīng)有它的引用,不符合intern()方法要求“首次遇到”的原則,“計算機軟件”這個字符串則是首次 出現(xiàn)的,因此結(jié)果返回true。(這塊說實話不好理解,說白了就是java是個特殊的字符串,他在常量池里面就一直存在)
總結(jié):在1.8之后通過intern()添加到常量池,只有字符串在常量池不存在的時候才會返回字符串的引用。
五、總結(jié)
到此為止,我們明白了虛擬機里面的內(nèi)存是如何劃分的,哪部分區(qū)域、什么樣的代碼和操作可能 導(dǎo)致內(nèi)存溢出異常。雖然Java有垃圾收集機制,但內(nèi)存溢出異常離我們并不遙遠(yuǎn),本章只是講解了各 個區(qū)域出現(xiàn)內(nèi)存溢出異常的原因,下一章將詳細(xì)講解Java垃圾收集機制為了避免出現(xiàn)內(nèi)存溢出異常都 做了哪些努力。
到此這篇關(guān)于Java實戰(zhàn)之OutOfMemoryError異常的文章就介紹到這了,更多相關(guān)java OutOfMemoryError異常內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Spring的ApplicationEvent實現(xiàn)本地事件驅(qū)動的實現(xiàn)方法
本文介紹了如何使用Spring的ApplicationEvent實現(xiàn)本地事件驅(qū)動,通過自定義事件和監(jiān)聽器,實現(xiàn)模塊之間的松耦合,提升代碼的可維護性和擴展性。同時還介紹了異步事件和事件傳遞的相關(guān)知識2023-04-04Java面試題 從源碼角度分析HashSet實現(xiàn)原理
這篇文章主要介紹了Java面試題 從源碼角度分析HashSet實現(xiàn)原理?,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-07-07Java通過SSH連接路由器輸入命令并讀取響應(yīng)的操作方法
最近需要讀取和修改華為路由器的配置,使用Java語言開發(fā),通過SSH連接,輸入命令并讀取響應(yīng),接下來通過本文給大家介紹下Java通過SSH連接路由器,輸入命令并讀取響應(yīng),需要的朋友可以參考下2024-01-01Spring使用IOC與DI實現(xiàn)完全注解開發(fā)
IOC也是Spring的核心之一了,之前學(xué)的時候是采用xml配置文件的方式去實現(xiàn)的,后來其中也多少穿插了幾個注解,但是沒有說完全采用注解實現(xiàn)。那么這篇文章就和大家分享一下,全部采用注解來實現(xiàn)IOC + DI2022-09-09Event?Sourcing事件溯源模式優(yōu)化業(yè)務(wù)系統(tǒng)
這篇文章主要為大家介紹了Event?Sourcing事件溯源模式優(yōu)化業(yè)務(wù)系統(tǒng)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07Spring Boot 項目設(shè)置網(wǎng)站圖標(biāo)的方法
這篇文章主要介紹了Spring Boot 項目設(shè)置網(wǎng)站圖標(biāo)的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02