Java虛擬機-第二篇-GC算法與內存分配策略

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

GC引入

在Java的運行時數據區中,程序計數器、虛擬機棧、本地方法棧三個區域都是線程私有的,隨線程而生,隨線程而滅,在方法結束或線程結束時,內存自然就跟著回收了,不需要過多考慮回收的問題。而Java堆和方法區則不一樣,一個接口中的多個實現類需要的內存可能不一樣,一個方法中的多個分支需要的內存也可能不一樣,我們只有在程序處于運行期間才能知道會創建哪些對象,這部分內存的分配和回收都是動態的,垃圾回收器關注的是這部分內存,后續討論的“內存”分配回收也是指這一塊,尤其需要注意。

GC主要回答了以下三個問題:

  • 哪些內存需要回收?
  • 什么時候回收?
  • 如何回收?

對象存活判定算法

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

引用計數算法

引用計數算法是在JVM中被摒棄的一種對象存活判定算法,不過它也有一些知名的應用場景(如Python、FlashPlayer),因此在這里也簡單介紹一下。

用引用計數器判斷對象是否存活的過程是這樣的:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器加1;當引用失效時,計數器減1;任何時刻計數器為0的對象就是不可能再被使用的。

引用計數算法的實現簡單,判定效率也很高,大部分情況下是一個不錯的算法。它沒有被JVM采用的原因是它很難解決對象之間循環引用的問題。例如以下例子:

/** * testGC()方法執行后,objA和objB會不會被GC呢? */
public class ReferenceCountingGC {public Object instance = null;private static final int _1MB = 1024 * 1024;/** * 這個成員屬性的唯一意義就是占點內存,以便在能在GC日志中看清楚是否有回收過 */private byte[] bigSize = new byte[2 * _1MB];public static void testGC() {ReferenceCountingGC objA = new ReferenceCountingGC();ReferenceCountingGC objB = new ReferenceCountingGC();objA.instance = objB;objB.instance = objA;objA = null;objB = null;// 假設在這行發生GC,objA和objB是否能被回收?System.gc();}

在上面這段代碼中,對象objA 和對象objB都有字段instance,賦值令objA.instance = objB;、objB.instance = objA;,除此之外,這兩個對象再無引用。如果JVM采用引用計數算法來管理內存,這兩個對象不可能再被訪問,但是他們互相引用著對方,導致它們引用計數不為0,所以引用計數器無法通知GC收集器回收它們。

可達性分析算法

在主流商用程序語言的實現中,都是通過可達性分析(tracing GC)來判定對象是否存活的。此算法的基本思路是:通過一系列的稱為“GC Roots”的對象作為起點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是GC Roots 到這個對象不可達)時,則證明此對象時不可用的。用下圖來加以說明:

上圖中,對象object 5、object 6、object 7雖然互有關聯,但是它們到GC Roots是不可達的,所以它們將會被判定為是可回收的對象。

可以看到,GC Roots在對象圖之外,是特別定義的**“起點”**,不可能被對象圖內的對象所引用。

準確地說,GC Roots其實不是一組對象,而通常是一組特別管理的指向引用類型對象的指針,這些指針是tracing GC的trace的起點。它們不是對象圖里的對象,對象也不可能引用到這些“外部”的指針這也是tracing GC算法不會出現循環引用問題的基本保證。因此也容易得出,只有引用類型的變量才被認為是Roots,值類型的變量永遠不被認為是Roots。只有深刻理解引用類型和值類型的內存分配和管理的不同,才能知道為什么root只能是引用類型。

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

虛擬機棧(棧幀中的局部變量表,Local Variable Table)中引用的對象。

方法區中類靜態屬性引用的對象。

方法區中常量引用的對象。

本地方法棧中JNI(即一般說的Native方法)引用的對象。

可以概括得出,可作為GC Roots的節點主要在全局性的引用執行上下文。要明確的是,tracing gc必須以當前存活的對象集為Roots,因此必須選取確定存活的引用類型對象。GC管理的區域是Java堆虛擬機棧、方法區和本地方法棧不被GC所管理,因此選用這些區域內引用的對象作為GC Roots,是不會被GC所回收的。其中虛擬機棧本地方法棧都是線程私有的內存區域,只要線程沒有終止,就能確保它們中引用的對象的存活。而方法區中類靜態屬性引用的對象是顯然存活的。常量引用的對象在當前可能存活,因此,也可能是GC roots的一部分。

兩次標記與 finalize()方法

即使在可達性分析算法中不可達的對象,也不是一定會死亡的,它們暫時都處于“緩刑”階段,要真正宣告一個對象“死亡”,至少要經歷兩次標記過程:

如果對象在進行可達性分析后發現沒有與 GC Roots相連接的引用鏈,那它將會被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執行**finaliza()**方法。當對象沒有覆蓋finaliza()方法,或者finaliza()方法已經被虛擬機調用過,虛擬機將這兩種情況都視為“沒有必要執行”。

如果這個對象被判定為有必要執行finaliza()方法,那么此對象將會放置在一個叫做 F-Queue 的隊列中,并在稍后由一個虛擬機自動建立的、低優先級的Finalizer線程去執行它。這里所謂的“執行”是指虛擬機會觸發此方法,但并不承諾會等待它運行結束,原因是:如果一個對象在finaliza()方法中執行緩慢,或者發生了死循環(更極端的情況),將很可能導致F-Queue 隊列中的其它對象永久處于等待,甚至導致整個內存回收系統崩潰。

finaliza()方法是對象逃脫死亡命運的最后一次機會,稍后GC將對F-Queue 隊列中的對象進行第二次小規模的標記。如果對象想在finaliza()方法中成功拯救自己,只要重新與引用鏈上的任何一個對象建立關聯即可例如把自己(this關鍵字)賦值給某個類變量或者對象的成員變量,這樣在第二次標記時它將被移出“即將回收”的集合;如果對象這時候還沒有逃脫,基本上它就真的被回收了。

需要說明的是,使用finalize()方法來“拯救”對象是不值得提倡的,因為它不是C/C++中的析構函數,而是Java剛誕生時為了使C/C++程序員更容易接受它所做的一個妥協。它的運行代價高昂,不確定性大,無法保證各個對象的調用順序。finalize() 能做的工作,使用try-finally或者其它方法都更適合、及時,所以筆者建議大家可以忘掉此方法存在。

回收方法區

很多人認為方法區沒有垃圾回收,Java虛擬機規范中確實說過不要求,而且在方法區中進行垃圾收集的“性價比”較低:在堆中,尤其是新生代,常規應用進行一次垃圾收集可以回收70%~95%的空間,而方法區的效率遠低于此。在JDK 1.8中,JVM摒棄了永久代,用元空間來作為方法區的實現,下面介紹的將是元空間的垃圾回收。

元空間的內存管理由元空間虛擬機來完成。先前,對于類的元數據我們需要不同的垃圾回收器進行處理,現在只需要執行元空間虛擬機的C++代碼即可完成。在元空間中,類和其元數據的生命周期和其對應的類加載器是相同的。換話句話說,只要類加載器存活,其加載的類的元數據也是存活的,因而不會被回收掉。

我們從行文到現在提到的元空間稍微有點不嚴謹。準確的來說**,每一個類加載器的存儲區域都稱作一個元空間,所有的元空間合在一起就是我們一直說的元空間**。當一個類加載器被垃圾回收器標記為不再存活,其對應的元空間會被回收。在元空間的回收過程中沒有重定位和壓縮等操作。但是元空間內的元數據會進行掃描來確定Java引用。

垃圾收集算法

標記-清除(Mark-Sweep)算法

標記-清除(Mark-Sweep)算法是最基礎的垃圾收集算法,后續的收集算法都是基于它的思路并對其不足進行改進而得到的。顧名思義,算法分成“標記”、“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收所有被標記的對象,標記過程在前一節講述對象標記判定時已經講過了。

標記-清除算法的不足主要有以下兩點:

  • 空間問題標記清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致以后在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不觸發另一次垃圾收集動作

  • 效率問題,因為內存碎片的存在,操作會變得更加費時,因為查找下一個可用空閑塊已不再是一個簡單操作。

標記-清除算法的執行過程如下圖所示:

復制(Copying)算法

為了解決標記-清除算法的效率問題,一種稱為“復制”(Copying)的收集算法出現了,思想為:它將可用內存按容量分成大小相等的兩塊每次只使用其中的一塊當這一塊內存用完,就將還存活著的對象復制到另一塊上面,然后再把已使用過的內存空間一次清理掉。

這樣做使得每次都是對整個半區進行內存回收內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種算法的代價是將內存縮小為原來的一半,代價可能過高了。復制算法的執行過程如下圖所示:

Minor GC與復制算法

現在的商業虛擬機都使用復制算法來回收新生代。新生代的GC又叫“Minor GC”,IBM公司的專門研究表明:新生代中的對象98%是**“朝生夕死”**的,所以Minor GC非常頻繁,一般回收速度也比較快,同時“朝生夕死”的特性也使得Minor GC使用復制算法時不需要按照1:1的比例來劃分新生代內存空間。

Minor GC過程

事實上,新生代將內存分為一塊較大的Eden空間兩塊較小的Survivor空間(From Survivor和To Survivor)每次Minor GC都使用Eden和From Survivor,當回收時,將Eden和From Survivor中還存活著的對象都一次性地復制到另外一塊To Survivor空間上,最后清理掉Eden和剛使用的Survivor空間一次Minor GC結束的時候,Eden空間和From Survivor空間都是空的,而To Survivor空間里面存儲著存活的對象在下次MinorGC的時候,兩個Survivor空間交換他們的標簽,現在是空的“From” Survivor標記成為“To”,“To” Survivor標記為“From”。因此,在MinorGC結束的時候,Eden空間是空的,兩個Survivor空間中的一個是空的,而另一個存儲著存活的對象。

HotSpot虛擬機默認的Eden : Survivor的比例是8 : 1,由于一共有兩塊Survivor,所以每次新生代中可用內存空間為整個新生代容量的90%(80%+10%),只有10%的容量會被“浪費”。

分配擔保

上文說的98%的對象可回收只是一般場景下的數據,我們沒有辦法保證每次回收都只有不多于10%的對象存活,當Survivor空間不夠用時需要依賴老年代內存進行分配擔保(Handle Promotion)。如果另外一塊Survivor上沒有足夠空間存放上一次新生代收集下來的存活對象,這些對象將直接通過分配擔保機制進入老年代

標記-整理(Mark-Compact)算法

復制算法在對象存活率較高時要進行較多的復制操作效率將會變低。更關鍵的是:如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用復制算法

根據老年代的特點,標記-整理(Mark-Compact)算法被提出來,主要思想為:此算法的標記過程與標記-清除算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉邊界以外的內存。具體示意圖如下所示

分代收集(Generational Collection)算法

當前商業虛擬機的垃圾收集都采用分代收集(Generational Collection)算法,此算法相較于前幾種沒有什么新的特征,主要思想為:根據對象存活周期的不同將內存劃分為幾塊,一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適合的收集算法:

  • 新生代 在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。

  • 老年代 在老年代中,因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清除”或“標記-整理”算法來進行回收。

HotSpot的算法實現

枚舉根節點

從可達性分析中從GC Roots節點找引用鏈這個操作為例,可作為GC Roots的節點**主要在全局性的引用(例如常量或類靜態屬性)與執行上下文(例如棧幀中的局部變量表)**中,現在很多應用僅僅方法區就有數百兆,如果要逐個檢查這里面的引用,那么必然會消耗很多時間。

GC停頓(”Stop The World”)

另外,可達性分析工作必須在一個能確保一致性的快照中進行——這里“一致性”的意思是指在整個分析期間整個執行系統看起來就像被凍結在某個時間點上,不可以出現分析過程中對象引用關系還在不斷變化的情況,這是保證分析結果準確性的基礎。這點是導致GC進行時必須停頓所有Java執行線程(Sun將這件事情稱為“Stop The World”)的其中一個重要原因,即使是在號稱(幾乎)不會發生停頓的CMS收集器中,枚舉根節點時也是必須要停頓的。

準確式GC與OopMap

由于目前的主流Java虛擬機使用的都是準確式GC(即使用準確式內存管理,虛擬機知道內存中某個位置的數據具體是什么類型),所以當執行系統停頓下來后,并不需要一個不漏地檢查完所有執行上下文和全局的引用位置,虛擬機應當是有辦法直接得知哪些地方存放著對象引用。在HotSpot的實現中,是使用一組稱為OopMap的數據結構來達到這個目的的,在類加載完成的時候,HotSpot就把對象內什么偏移量上是什么類型的數據計算出來,在JIT編譯過程中,也會在特定的位置記錄下棧和寄存器中哪些位置是引用。這樣,GC在掃描時就可以直接得知這些信息了。

安全點(Safepoint)——進行GC時程序停頓的位置

在OopMap的協助下,HotSpot可以快速且準確地完成GC Roots枚舉,但一個很現實的問題隨之而來:可能導致引用關系變化,或者說OopMap內容變化的指令非常多,如果為每一條指令都生成對應的OopMap,那將會需要大量的額外空間,這樣GC的空間成本將會變得很高。

為此,HotSpot選擇不為每條指令都生成OopMap,而是只在**“特定的位置”記錄這些信息,這些位置便被稱為安全點(Safepoint)。也就是說,程序執行時并非在所有地方都能停頓下來開始GC,只有在到達安全點時才能暫停。Safepoint的選定既不能太少以致于讓GC等待時間太長,也不能過于頻繁以致于過分增大運行時的負荷。所以,安全點的選定基本上是以程序“是否具有讓程序長時間執行的特征”為標準進行選定的——因為每條指令執行的時間都非常短暫,程序不太可能因為指令流長度太長這個原因而過長時間運行,“長時間執行”的最明顯特征就是指令序列復用,例如方法調用、循環跳轉、異常跳轉等**,所以具有這些功能的指令才會產生Safepoint。

對于Sefepoint,另一個需要考慮的問題是如何在GC發生時讓所有線程(這里不包括執行JNI調用的線程)都“跑”到最近的安全點上再停頓下來。這里有兩種方案可供選擇:

  • 搶先式中斷(Preemptive Suspension) 搶先式中斷不需要線程的執行代碼主動去配合,在GC發生時,首先把所有線程全部中斷,如果發現有線程中斷的地方不在安全點上,就恢復線程,讓它“跑”到安全點上。現在幾乎沒有虛擬機實現采用搶先式中斷來暫停線程從而響應GC事件。

  • 主動式中斷(Voluntary Suspension): 主動式中斷的思想是當GC需要中斷線程的時候,不直接對線程操作,僅僅簡單地設置一個標志,各個線程執行時主動去輪詢這個標志,發現中斷標志為真時就自己中斷掛起。輪詢標志的地方和安全點是重合的,另外再加上創建對象需要分配內存的地方。

安全區域(Safe Region)

Safepoint機制保證了程序執行時,在不太長的時間內就會遇到可進入GC的Safepoint。但是,程序“不執行”的時候(如線程處于Sleep狀態或Blocked狀態),這時線程無法響應JVM的中斷請求,“走到”安全的地方去中斷掛起,這時候就需要安全區域(Safe Region)來解決。

安全區域是指在一段代碼片段之中,引用關系不會發生變化。在這個區域中的任意地方開始GC都是安全的。我們也可以把Safe Region看做是被擴展了的Safepoint。

在線程執行到Safe Region中的代碼時,首先標識自己已經進入了Safe Region,那樣,當在這段時間里JVM要發起GC時,就不用管標識自己為Safe Region狀態的線程了。在線程要離開Safe Region時,它要檢查系統是否已經完成了根節點枚舉(或者是整個GC過程),如果完成了,那線程就繼續執行,否則它就必須等待直到收到可以安全離開Safe Region的信號為止。

內存分配策略

Java的自動內存管理最終可以歸結為自動化地解決了兩個問題:

  • 給對象分配內存
  • 回收分配給對象的內存

對象的內存分配通常是在堆上分配(除此以外還有可能經過JIT編譯后被拆散為標量類型并間接地棧上分配),對象主要分配在新生代的Eden區上,如果啟動了本地線程分配緩沖,將按線程優先在TLAB上分配。少數情況下也可能會直接分配在老年代中,分配的規則并不是固定的,實際取決于垃圾收集器的具體組合以及虛擬機中與內存相關的參數的設置。

下面以使用Serial/Serial Old收集器為例,介紹內存分配的策略。

對象優先在Eden區分配

大多數情況下,對象在新生代的Eden區中分配。當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC。

大對象直接進入老年代

所謂的大對象是指,需要大量連續內存空間的Java對象最典型的大對象就是很長的字符串以及數組。大對象對虛擬機的內存分配來說是一個壞消息(尤其是遇到朝生夕滅的“短命大對象”,寫程序時應避免),經常出現大對象容易導致內存還有不少空間時就提前觸發GC以獲取足夠的連續空間來安置它們。

虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大于這個設置值的對象直接在老年代分配。這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的內存復制(新生代采用復制算法回收內存)。

長期存活的對象將進入老年代

既然虛擬機采用了分代收集的思想來管理內存,那么內存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。為了做到這點,虛擬機給每個對象定義了一個對象年齡(Age)計數器。如果對象在Eden出生并經過第一次Minor GC后仍然存活,并且能被Survivor容納的話,將被移動到Survivor空間中,并且對象年齡設為1。對象在Survivor區中每“熬過”一次Minor GC,年齡就增加1歲當它的年齡增加到一定程度(默認為15歲),就將會被晉升到老年代中。對象晉升老年代的年齡閾值,可以通過參數-XX:MaxTenuringThreshold設置。

動態對象年齡判定

為了能更好地適應不同程序的內存狀況,虛擬機并不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

空間分配擔保

在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大于新生代所有對象總空間,如果這個條件成立,那么Minor GC可以確保是安全的。如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那么會繼續檢查老年代最大可用的連續空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試著進行一次Minor GC,盡管這次Minor GC是有風險的;如果小于,或者HandlePromotionFailure設置不允許冒險,那這時也要改為進行一次Full GC。

前面提到過,新生代使用復制收集算法,但為了內存利用率,只使用其中一個Survivor空間來作為輪換備份,因此當出現大量對象在Minor GC后仍然存活的情況(最極端的情況就是內存回收后新生代中所有對象都存活),就需要老年代進行分配擔保,把Survivor無法容納的對象直接進入老年代。與生活中的貸款擔保類似,老年代要進行這樣的擔保,前提是老年代本身還有容納這些對象的剩余空間,一共有多少對象會活下來在實際完成內存回收之前是無法明確知道的,所以只好取之前每一次回收晉升到老年代對象容量的平均大小值作為經驗值,與老年代的剩余空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間。

取平均值進行比較其實仍然是一種動態概率的手段,也就是說,如果某次Minor GC存活后的對象突增,遠遠高于平均值的話,依然會導致擔保失敗(Handle Promotion Failure)。如果出現了HandlePromotionFailure失敗,那就只好在失敗后重新發起一次Full GC。雖然擔保失敗時繞的圈子是最大的,但大部分情況下都還是會將HandlePromotionFailure開關打開,避免Full GC過于頻繁。

Full GC的觸發條件

對于Minor GC,其觸發條件非常簡單,當Eden區空間滿時,就將觸發一次Minor GC。而Full GC則相對復雜,因此本節我們主要介紹Full GC的觸發條件。

調用System.gc()

此方法的調用是建議JVM進行Full GC,雖然只是建議而非一定,但很多情況下它會觸發 Full GC,從而增加Full GC的頻率,也即增加了間歇性停頓的次數。因此強烈建議能不使用此方法就不要使用,讓虛擬機自己去管理它的內存,可通過-XX:+ DisableExplicitGC來禁止RMI調用System.gc()。

老年代空間不足

老年代空間不足的常見場景為前文所講的大對象直接進入老年代、長期存活的對象進入老年代等,當執行Full GC后空間仍然不足,則拋出如下錯誤: Java.lang.OutOfMemoryError: Java heap space 為避免以上兩種狀況引起的Full GC,調優時應盡量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要創建過大的對象及數組。

空間分配擔保失敗

前文介紹過,使用復制算法的Minor GC需要老年代的內存空間作擔保,如果出現了HandlePromotionFailure擔保失敗,則會觸發Full GC。

JDK 1.7及以前的永久代空間不足

在JDK 1.7及以前,HotSpot虛擬機中的方法區是用永久代實現的,永久代中存放的為一些class的信息、常量、靜態變量等數據,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被占滿,在未配置為采用CMS GC的情況下也會執行Full GC。如果經過Full GC仍然回收不了,那么JVM會拋出如下錯誤信息: java.lang.OutOfMemoryError: PermGen space 為避免PermGen占滿造成Full GC現象,可采用的方法為增大PermGen空間或轉為使用CMS GC。

在JDK 1.8中用元空間替換了永久代作為方法區的實現,元空間是本地內存,因此減少了一種Full GC觸發的可能性。

Concurrent Mode Failure

執行CMS GC的過程中同時有對象要放入老年代,而此時老年代空間不足(有時候“空間不足”是CMS GC時當前的浮動垃圾過多導致暫時性的空間不足觸發Full GC),便會報Concurrent Mode Failure錯誤,并觸發Full GC。

轉載于:https://my.oschina.net/jiansin/blog/2872510

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

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

相關文章

python在函數內部有沒有辦法定義全局變量_主函數內部的全局變量python

你想要什么是不可能的*。你可以在全局命名空間中創建一個變量: myglobal "UGHWTF" def main(): global myglobal # prevents creation of a local variable called myglobal myglobal "yu0 fail it" anotherfunc() def anotherfunc(): print…

軟件項目經理應該具備的心態

我們(項目經理)必須認識到有些現實是無法改變的: 1.市場前期都會過度承諾 2.公司是要賺錢的,僅僅有虛名但不賺錢的事情公司是不會真正持久的 3.任何公司都是資源不足的 4.任何公司都有或多或少的管理問題,沒有問題…

Caffe學習記錄(十一) ICNet分割網絡學習

ICNet 是一個既考慮性能,又考慮準確率的分割網絡,包含了語義分割和邊緣精確分割,因為偶然看到就簡單的了解一下,記錄下來 論文是: ICNet for Real_time Semantic Segmentation on High Resolution Images,整篇文章都在…

如何利用python自動化辦公項目_python辦公自動化:自動進行word文檔處理和排版

上節python辦公自動化:自動打開word文檔我們一起學會了在python里打開并保存一個word文檔。這節我們將會學會如何利用python進行文本處理和將其在word里進行排版等技巧。python進行文本處理和將其在word里進行排版等技巧 使用文本 要有效地處理文本,首先要了解一些塊…

不同項目組織結構間的區別

項目組織結構分 職能型 項目型 矩陣型 弱矩陣型 平衡矩陣 強矩陣 職能型 場景舉例: 去飯店吃飯。 飯店A,門口宣傳接待一組人,進店了,換另一組人安排就坐點餐,過一會兒,有專人上菜...... 這是職能型&#x…

PMO在組織結構中的作用

項目管理辦公室是企業設立的一個職能機構名稱,也有的稱作項目管理部、項目辦公室或項目管理中心等,英文為: Project Management Office ,縮寫簡稱:PMO。 PMO是在組織內部將實踐、過程、運作形式化和標準化的部門&…

python支持多種編程范式嗎_Python3學習之路~6.1 編程范式:面向過程 VS 面向對象...

編程范式 編程是程序員用特定的語法數據結構算法組成的代碼來告訴計算機如何執行任務的過程,一個程序是程序員為了得到一個任務結果而編寫的一組指令的集合,正所謂條條大路通羅馬,實現一個任務的方式有很多種不同的方式,對這些不同…

Spring框架知識復習之二

Spring使用注解對Bean進行管理 1 使用注解需配置aop相關xsd文件的約束和命名空間 xsd文件名為&#xff1a;spring-aop-4.2.xsd 2 注解組件掃描配置 示例如下&#xff1a;base-package屬性 設置掃描指定包下的所有子孫包 <context:component-scan base-package"cn.itma.…

比較合理的項目組織架構

&#xff08;從自己從業經驗中總結所得&#xff09; PMO就像是操作系統&#xff0c;項目組1...N就像進程&#xff08;開啟多個軟件&#xff09;&#xff0c;項目任務就像線程&#xff0c;項目組成員就像CPU&#xff08;具體干活的&#xff09;&#xff0c;CPU資源是有限的&…

python培訓一般要多久_入門 Python 要多久時間?該如何學習?

入門 Python 要多久?該如何學習? 學Python和學其他的語言其實是相同的&#xff0c;我給新同事講課的時候就說學編程和練武功其實是很相似&#xff0c;入門大致這樣幾步: 找本靠譜的書, 找個靠譜的師傅&#xff0c; 找一個地方開始練習。 學語言也是的&#xff1a;選一本通俗易…

小計 合計 總計 共計 怎么解釋?

排列順序&#xff1a;小計、總計、合計。共計分開使用。 小計&#xff1a;小計就是簡單核算一下單個體&#xff0c;可理解為日小計 合計&#xff1a;合計就是把所有小計加起來&#xff0c;可理解為月合計 總計&#xff1a;總計就是把合計加起來&#xff0c;可理解為年總計 …

巧用位運算存狀態

2019獨角獸企業重金招聘Python工程師標準>>> 某種場景需要,一個事物 有多種狀態并列存在的時候,或者權限,我們可以使用 |,&,~ 來處理,具體代碼如下: package com.example.demo;/*** 權限狀態處理*/ public class StatusUtil {/*** 判斷狀態是否開啟* param fl…

工時單位天與人天的區別?

售前工作時&#xff0c;向甲方提供報價單是必需的&#xff0c;報價要有依據。 當提供的報價單的工時單位是&#xff08;人/天&#xff09;的時候&#xff0c;需要向甲方額外解釋下&#xff0c;人/天人數*天數。比如&#xff1a;編碼開發&#xff0c;40人天&#xff0c;投入1位…

python怎么做項目_聽說你沒有python項目可做,我教你個方法

原標題&#xff1a;聽說你沒有python項目可做&#xff0c;我教你個方法 學習了一段時間的Python&#xff0c;最近出現了“饑荒”&#xff0c;感覺需要多看些代碼&#xff0c;多學習學習別人做些什么&#xff0c;但卻不知道做點什么來進行練習。 說到看代碼&#xff0c;我就想到…

Linux系統CPU相關信息查詢

Linux系統CPU相關信息查詢 作者&#xff1a;尹正杰 版權聲明&#xff1a;原創作品&#xff0c;謝絕轉載&#xff01;否則將追究法律責任。 一.lscpu常用參數介紹 1>.查看幫助信息 [rootnode105 ~]# lscpu -h-a, –all: 包含上線和下線的cpu的數量&#xff0c;此選項只能與選…

excel下拉讓函數參數部分不變

原理 使用相對引用就會變&#xff0c;bai使用絕對引用du就不變 A1是相對引用&#xff0c;上下拉公式的時候會zhi變成A2,A3…… $A$1是行列絕對引用dao&#xff0c;上下拉公式的時候不會變化 $A1&#xff0c;是行絕對引用&#xff0c;上下拉公式的時候會變化&#xff0c;$A2&am…

select * 排除字段_編寫 SQL 的排除聯接

有兩個表&#xff0c;就叫作源表和目標表吧。它們有一個相同的字段&#xff0c;通過該字段可以把源表和目標表關聯在一起&#xff0c;我們希望從源表中檢索到的記錄里的關聯字段的值沒有存在目標表中。舉個例子&#xff0c;源表 dept&#xff0c;目標表 emp&#xff0c;獲取 de…

JS中調用bignumber處理高精度小數運算

ignumber.js用于數字精度要求較高的計算。 bignumber.js源碼地址&#xff1a;https://github.com/MikeMcl/bignumber.js 1.下載bignumber.js。 下載地址&#xff1a;https://github.com/MikeMcl/bignumber.js/releases 2.引入js。 3.示例 html: <div><label>數1&am…

centos8安裝搜狗輸入法_搜狗拼音輸入法去廣告版

搜狗輸入法是搜狗公司2006年6月推出的一款漢字輸入法工具。與傳統輸入法不同&#xff0c;搜狗輸入法是第一款為互聯網而生的輸入法——它通過搜索引擎技術&#xff0c;將互聯網變成了一個巨大的“活”詞庫。應該有許多人電腦上都裝了吧&#xff0c;不過一般下載的版本天天彈廣告…

Project查看資源分配情況

選擇 資源圖表&#xff0c;點擊 格式&#xff0c;在圖表里選擇