內(nèi)存區(qū)域
虛擬機棧 生命周期與線程相同,描述的是Java 方法執(zhí)行的內(nèi)存模型拙友,每個方法在執(zhí)行的時候都會創(chuàng)建一個棧幀为狸,用于存取局部變量表、操作數(shù)棧遗契、動態(tài)鏈接辐棒、方法出口等信息 本地方法棧 與虛擬機棧作用相似,只不過本地方法棧是為虛擬機使用到的Native方法服務 程序計數(shù)器 內(nèi)存空間較小牍蜂,可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器漾根。此內(nèi)存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域 如果線程正在執(zhí)行的是一個Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址鲫竞;如果正在執(zhí)行的是Native方法辐怕,這個計數(shù)器值為空(Undefined) 堆 內(nèi)存區(qū)域最大的一塊,此內(nèi)存區(qū)域的唯一目的就是存放對象實例从绘,基本上所有的對象實例分配都是由其分配內(nèi)存寄疏。Java堆是垃圾收集器管理的區(qū)主要區(qū)域,因此有時也成為GC堆 方法區(qū) 也稱為非堆僵井,主要用來存取已經(jīng)被虛擬機加載的類信息陕截、常量、靜態(tài)變量批什、即時編譯器編譯后的代碼等數(shù)據(jù)
對象內(nèi)存布局
對象頭(Header) 用于存儲對象自身的運行時數(shù)據(jù)农曲,如HashCode、GC分代年齡渊季、鎖狀態(tài)標志朋蔫、線程持有的鎖罚渐、偏向線程ID却汉、偏向時間戳 類型指針 實例數(shù)據(jù)(Instance Data) 對象真正存儲的有效信息驯妄,也是在程序代碼中所定義的各種類型的字段內(nèi)容 對齊補充(Padding)
僅僅起到占位符的作用
對象訪問定位
句柄訪問 Java堆中將會劃分出一塊內(nèi)存來作為句柄池,reference中存儲的就是對象的句柄地址合砂,而句柄中包含了對象實例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息 直接指針訪問 Java堆對象的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關信息青扔,而reference中存儲的直接就是對象地址
虛擬機棧和本地方法棧異常
如果線程請求的棧深度大于虛擬機所允許的最大深度,將拋出StackOverflowError異常 如果虛擬機在拓展時無法申請到足夠的內(nèi)存空間翩伪,則拋出OutOfMemoryError(OOM)異常
對象已死
引用計數(shù)法 對象每引用一次就加1微猖,引用失效則減1,當引用次數(shù)為0的時候?qū)⑦M行回收缘屹,會出現(xiàn)循環(huán)依賴問題凛剥,因此虛擬機沒有使用此算法 可達性分析 使用GC ROOTS來判斷一個對象是否可達,不可達將其判斷為不可達的對象
回收算法
標記-清除算法 將要回收的對象進行標記轻姿,回收的時候直接將已標記的對象進行回收犁珠,但是很容易產(chǎn)生內(nèi)存碎片 標記-整理算法 將要回收的對象進行標記并移動到內(nèi)存區(qū)域的一端,減少內(nèi)存碎片的產(chǎn)生互亮,但是這很影響效率 復制算法 新生代的對象大部分都是朝夕生死的犁享,使用復制算法將不需要回收的對象移動到Survivor區(qū),為Eden區(qū)騰出空間豹休,因為對象優(yōu)先在Eden分配炊昆,年輕代中默認為Eden:Survivor為8:1,其中Survivor有兩個 分代收集算法 只是根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊威根,一般是將java堆分為新生代和老年代凤巨,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴?在新生代中,每次垃圾回收都有大批對象死去洛搀,只有少量存活敢茁,那就選中復制算法,只需要少量存活對象的復制成本就可以完成收集姥卢。而老年代中因為對象存活率高卷要,沒有額外空間對它進行分配擔保,就必須使用標記–清理或標記–整理算法
HotSpot算法實現(xiàn)
枚舉根節(jié)點 安全點(Safepoint) 安全點的選定基本上是以程序“是否具有讓程序長時間執(zhí)行的特征”為標準進行選定的——因為每條指令執(zhí)行的時間都是非常短暫独榴,程序不太可能因為指令長度太長這個原因而過長時間運行僧叉,“長時間運行”的最明顯的特征就是指令序列復用,如方法調(diào)用棺榔、循環(huán)跳轉(zhuǎn)瓶堕、異常跳轉(zhuǎn) 產(chǎn)生安全點 方法調(diào)用 循環(huán)跳轉(zhuǎn) 異常跳轉(zhuǎn) 安全區(qū)域(Safe Region) Safepoint機制保證了程序執(zhí)行時,在不太長的時間內(nèi)就會遇到可進入GC的Safepoint症歇。但是郎笆,程序“不執(zhí)行”的時候呢谭梗?所謂的程序不執(zhí)行就是沒有分配CPU時間,典型的例子就是線程處于Sleep狀態(tài)或者Blocked狀態(tài)宛蚓,這時候線程無法響應JVM的中斷請求激捏,“走”到安全的地方掛起,JVM也顯然不太可能等待線程重新被分配CPU時間凄吏。對于這種情況远舅,需要安全區(qū)域來解決 在一段代碼片段中,引用關系不會發(fā)生變化痕钢,在這個區(qū)域中的任意地方開始GC都是安全的图柏,可以把Safe Region看成是被拓展了的Safepoint
垃圾收集器
并行與并發(fā)的概念 并行(Parallel):指多條垃圾收集器線程并行工作,但此時用戶線程仍然處于等待狀態(tài) 并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時執(zhí)行(但不一定是并行的任连,可能會交替執(zhí)行)蚤吹,用戶程序在繼續(xù)執(zhí)行,而垃圾回收器程序運行于另一個CPU上 新生代 Serial(JDK1.3.1之前) 單線程收集器随抠,進行垃圾回收時必須暫停其他所有的工作線程裁着,知道它收集結(jié)束。優(yōu)點是簡單高效(與其他收集器的單線程相比)暮刃,沒有線程交互開銷 ParNew(JDK1.3) Serial的多線程版本跨算,除了多線程收集之外,其他與Serial收集器相比并沒有太多創(chuàng)新之處 Parallel Scavenge(JDK1.4) 使用復制算法的收集器椭懊,使用并行的多線程收集器诸蚕,為了達到一個可控制的吞吐量,即CPU用于執(zhí)行用戶代碼的時間與CPU總消耗時間的比值(吞吐量=用戶代碼執(zhí)行時間/(用戶代碼執(zhí)行時間+垃圾收集時間))氧猬,也稱為吞吐量優(yōu)先收集器 老年代 Serial Old Serial收集器的老年代版本背犯,單線程收集器,使用標記——整理算法 盅抚。主要意義也是在于給Client模式下的虛擬機使用漠魏,GC時需要STW Parallel Old(JDK1.6) Parallel Scavenge收集器的老年代版本,使用多線程和標記——整理算法 CMS(JDK1.5妄均,Concurrent Mark Sweep) 使用標記——清除算法柱锹,以獲取最短回收停頓時間為目標的收集器。優(yōu)點:并發(fā)收集丰包,低停頓禁熏,Sun公司也稱之為并發(fā)低停頓收集器(Concurrent Low Pause Collector) 運行步驟 初始標記(需要STW) 標記一下GC Roots能直接關聯(lián)到的對象,速度很快 并發(fā)標記 進行GC Roots Tracing的過程 重新標記(需要STW) 為了修正并發(fā)標記期間因用戶程序繼續(xù)運作而導致標記產(chǎn)生變動的那一部分對象的標記記錄邑彪,這個階段的停頓時間一般會比初始標記階段稍長一點瞧毙,但遠比并發(fā)標記的事件短 并發(fā)清除 缺點 對CPU資源非常敏感 無法處理浮動垃圾 由于采用了標記——清除算法,所以這很容易導致產(chǎn)生大量空間碎片 G1(JDK1.7,Garbage First) 運行步驟 初始標記 并發(fā)標記 最終標記 篩選回收 面向服務端應用 特點 并行與并發(fā) 充分利用多CPU宙彪、多核環(huán)境下的硬件優(yōu)勢矩动,使用多個CPU來縮短STW(Stop The World,GC進行時需停頓所有的Java執(zhí)行進程)停頓的時間 分代收集 空間整合 可預測的停頓
內(nèi)存分配與回收策略
新生代GC與老年代GC 新生代GC(Minor GC) 指發(fā)生在新生代的垃圾回收動作释漆,因為Java對象大多都具備朝生夕滅的特性悲没,所以Minor GC非常頻繁,一般回收速度也比較快 老年代(Major GC / Full GC) 指發(fā)生在老年代的GC灵汪,出現(xiàn)了Major GC檀训,經(jīng)常會伴隨至少一次的Minor GC(但非絕對的柑潦,在Parallel Scavenge收集器的收集策略里就有直接進行進行Major GC的策略選擇過程)享言。Major GG的速度一般會比Minor GC慢10倍以上 對象優(yōu)先在Eden分配 對象主要分配在新生代的Eden區(qū)上,如果啟動了本地線程分配緩沖渗鬼,將按線程優(yōu)先在TLAB(Thread Local Allocation Buffer 本地線程分配緩沖區(qū))上分配 大對象直接放入老年代 大對象指的是需要大量連續(xù)內(nèi)存空間的Java對象览露,最典型的大對象就是那種很長的字符串和數(shù)組,盡量避免出現(xiàn)朝生夕滅的大對象 長期存活的對象將進入老年代 虛擬機為每個對象定義了一個對象年齡(Age)計數(shù)器譬胎。如果對象在Eden出生并經(jīng)過一次Minor GC后仍然存活差牛,并且能被Survivor容納的話,將被移動到Survivor空間中堰乔,并且對象年齡設為1偏化。對象在Survivor區(qū)中每熬過一次Minor GC,年齡就增加1歲镐侯,當其年齡增加到一定程度(默認為15歲)侦讨,就會晉升到老年代。對象晉升老年代的年齡閾值苟翻,可以通過參數(shù)-XX:MaxTenuringThreshold設置 空間分配擔保 在發(fā)生Minor GC之前韵卤,虛擬機會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果這個條件成立崇猫,那么Minor GC可以確保是安全的沈条。如果不成立,則虛擬機會查看HandlerPromotionFailure設置值是否允許擔保失敗诅炉。如果允許蜡歹,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小涕烧;如果大于月而,將嘗試進行一次Minor GC,盡管這個Minor GC是有風險的澈魄;如果小于景鼠,或者HandlerPromotionFailure設置不允許冒險,那這時也要改為進行一次Full GC
JVM常用命令
jps 類似于Linux中的ps命令,列出正在執(zhí)行的虛擬機進程溯香,并顯示虛擬機執(zhí)行主類(Main Class浓恶,main()函數(shù)所在的類)名稱以及這些進程的本地虛擬機唯一ID(Local Virtual Machine Identifier,LVMID)昌渤,是使用頻率最高的JDK命令行工具演侯,因為其他的JDK工具大多需要輸入它查詢到的LVMID來確定要監(jiān)控的是哪一個虛擬機進程 jps [options] [hostid] options -q 只輸出LVMID,省略主類的名稱 -m 輸出虛擬機進程啟動時傳遞給主類main()函數(shù)的參數(shù) -l 輸入主類的全名勉痴,如果進程執(zhí)行的是Jar包树肃,輸出Jar包路徑 -v 輸出虛擬機進程啟動時的JVM參數(shù) hostid RMI注冊表中注冊的主機名 jstat 虛擬機統(tǒng)計信息監(jiān)視工具蒸矛,可以顯示本地或遠程虛擬機中的類加載、內(nèi)存胸嘴、垃圾收集雏掠、JIT編譯等運行數(shù)據(jù) jstat [option vmid [interval[s | ms] [count]] ] options 列舉2個 -class 監(jiān)視類裝載劣像、卸載數(shù)量、總空間以及類裝載所耗費的時間 -gc 監(jiān)視Java狀況耳奕,包括Eden區(qū)、兩個Survivor區(qū)时迫、老年代谓晌、永久代等的容量、已用空間纸肉、GC時間合計等信息 interval和count代表查詢間隔和次數(shù),如果省略這兩個參數(shù)姐刁,說明只查詢一次烦味;eg: jstat -gc 2764 250 20 需要每250ms查詢一次進程2764垃圾收集情況壁拉,一次查詢20次 jinfo Java配置信息工具柏靶,實時查看和調(diào)整虛擬機各項參數(shù) jinfo [options] pid options jinfo對于Windows平臺功能仍然有較大限制,只提供了最基本的-flag選項 eg:查詢CMSInitiatingOccupancyFraction參數(shù)值 jinfo -flag CMSInitiatingOccupancyFraction 1444 jmap Java內(nèi)存影像工具(Memory Map for Java)痘昌,jmap命令用于生成堆轉(zhuǎn)儲快照(一般稱為headdump或dump文件)炬转。和jinfo命令一樣,jmap有不少功能在Windows平臺都是受限的 jmap [ option ] vmid options列舉4個 -dump 用于生成Java堆轉(zhuǎn)儲快照 -finalizerinfo 顯示在F-Queue中等待Finalizer線程執(zhí)行finalize方法的對象驻啤。只在Linux/Solaris平臺才有效 -head 顯示Java堆詳細信息测僵,如使用哪種回收器、參數(shù)配置捍靠、分代狀況等森逮。只在Linux/Solaris平臺下有效 -histo 顯示堆中對象統(tǒng)計信息褒侧,包括類、實例數(shù)量闷供、合計容量 jhat 虛擬機堆轉(zhuǎn)儲快照分析工具,Sun JDK提供jhat(JVM Heap Analysis Tool)命令與jmap搭配使用钞艇,來分析jmap生成的堆轉(zhuǎn)儲快照豪硅,jhat內(nèi)置了一個微型的HTTP/HTML服務器,生成dump文件的分析結(jié)果后飘弧,可以在瀏覽器中查看。 jstack Java堆棧跟蹤工具蹋岩,jstack(Stack Trace for Java)命令用于生成虛擬機當前時刻的線程快照(一般稱為threaddump或者javacore文件)学少。線程快照就是當前虛擬機內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現(xiàn)長時間停頓的原因扣囊。如線程間死鎖绒疗、死循環(huán)、請求外部資源導致的長時間等待等都是導致線程長時間停頓的常見原因惕虑。線程出現(xiàn)停頓的時候通過jstack來查看各個線程的調(diào)用堆棧磨镶,就可以知道沒有響應的線程到底在后臺做些什么事情,或者等待些什么資源 jstack [option] vmid -F 當正常輸出的請求不被響應時琳猫,強制輸出線程堆棧 -l 除堆棧外,顯示關于鎖的附加信息 -m
如果調(diào)用到本地方法的話统刮,可以顯示C/C++的堆棧
類加載
加載 通過一個類的全限定名來獲取定義此類的二進制字節(jié)流 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu) 在內(nèi)存中生成一個代表這個類的java.lang.Class對象账千,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口 (Class對象比較特殊匀奏,存放在方法區(qū)里) 二進制流獲取路徑 從ZIP包中獲取,這很常見攒射,最終成為JAR会放、EAR、WAR格式的基礎 從網(wǎng)絡中獲取咧最,如從Applet應用中獲取 運行時計算生成御雕,如動態(tài)代理技術(JDK動態(tài)代理或cglib)酸纲,在java.lang.reflect.Proxy中瑟匆,就是用了ProxyGenerator.generateProxyClass來為特定接口生成形式為”*$Proxy“的代理類的二進制流 由其他文件生成,如JSP應用疾嗅,由JSP文件生成對應的Class類 從數(shù)據(jù)庫中讀取冕象,比較少見,有些中間件服務器(如SAP Netweaver)可以選擇把程序安裝到數(shù)據(jù)庫中來完成程序代碼在集群間的分發(fā) 驗證 文件格式驗證 驗證字節(jié)流是否符合Class文件格式的規(guī)范论悴,并且能被當前版本的虛擬機處 是否以魔數(shù)0xCAFEBABE開頭 主墓律、次版本號是否在當前虛擬機處理范圍之內(nèi) 常量池的常量中是否有不被支持的常量類型(檢查常量tag標志) 指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量 CONSTANT_Utf8_info型的常量中是否有不符合UTF8編碼的數(shù)據(jù) Class文件中各個部分及文件本身是否有被刪除的或附加的其他信息 元數(shù)據(jù)驗證 對字節(jié)碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規(guī)范的要求 這個類是否有父類(除了java.lang.Object之外玖像,所有的類應當有父類) 這個類的父類是否繼承了不允許繼承的類齐饮,如被final修飾的類 如果這個類是抽象類笤昨,是否實現(xiàn)了其父類或接口之中要求實現(xiàn)的所有方法 類中的字段瞒窒、方法是否與父類產(chǎn)生矛盾(如覆蓋了父類的final字段、或者出現(xiàn)不符合規(guī)則的方法重載崇裁、重寫) 字節(jié)碼驗證 驗證過程最為復雜,主要目的是通過數(shù)據(jù)流和控制流分析拔稳,以確定程序語義是合法的、符合邏輯的 保證任意時刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作术奖,例如不會出現(xiàn)類似這樣的情況:在操作數(shù)棧放置了一個int類型的數(shù)據(jù),使用時卻按long類型來加載入本地變量表中 保證跳轉(zhuǎn)指令不會調(diào)轉(zhuǎn)到方法體以外的字節(jié)碼指令上 保證方法體中的類型轉(zhuǎn)換是有效的佣耐,例如可以把一個子類對象賦值給父類數(shù)據(jù)類型唧龄,這是安全的,但是把父類對象賦值給子類數(shù)據(jù)類型讽挟,甚至把對象賦值給與它毫無繼承關系援制、完全不相干的一個數(shù)據(jù)類型,則是危險和不合法的 符號引用驗證 符號引用中通過字符串描述的全限定名是否能找到對應的類 在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段 符號引用中的類褐墅、字段洪己、方法的訪問性 (private、protected逝钥、public拱镐、default)是否可以被當前類訪問 準備 為類變量(被static修飾的變量,不包括實例變量哗咆,實例變量將會在對象實例化時隨著對象一起分配在Java堆中)分配內(nèi)存并設置類變量初始值的階段益眉,這些變量所使用的內(nèi)存都將在方法區(qū)進行分配 初始值通常情況下是數(shù)據(jù)類型的零值 如果類字段的字段屬性表中存在ConstantValue屬性,那么在準備階段變量value就會被初始化為ConstantValue屬性所指定的值(被final修飾的類變量在編譯時會生成ConstantValue屬性) eg:public static final int value = 123年碘; 在準備階段虛擬機就會根據(jù)ConstantValue的設置將value賦值給123 解析 解析階段是虛擬機將常量池內(nèi)的符號引用(在Class文件中以CONSTANT_Class_info展鸡、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等類型的常量)替換成直接引用的過程 符號引用(Symbolic References) 符號引用以一組符號來描述所引用的目標傲诵,符號可以是任意形式的字面量,只要使用時能無歧義地定位到目標即可悟衩。符號引用與虛擬機實現(xiàn)的內(nèi)存布局無關栓拜,引用的目標并不一定已經(jīng)加載到內(nèi)存中 直接引用(Direct References) 直接引用可以是直接指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄挑势。直接引用是和虛擬機實現(xiàn)的內(nèi)存布局相關的啦鸣,同一個符號引用在不同虛擬機實例上翻譯出來的直接引用一般不會相同。如果有了直接引用香拉,那引用的目標必定已經(jīng)在內(nèi)存中存在 類或接口的解析 字段解析 類方法解析 接口方法解析 初始化 必須立即對類進行初始化的5種情況 遇到new(實例化一個對象)中狂、getstatic(讀取一個類的靜態(tài)字段【被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外】)盛险、putstatic(設置一個類的靜態(tài)字段【被final修飾勋又、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外】)或invokestatic(調(diào)用一個類的靜態(tài)方法)字節(jié)碼指令時 使用java.lang.reflect包的方法對類進行反射調(diào)用的時候 當初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行初始化楔壤,則需要先觸發(fā)其父類的初始化 包含main方法的類(執(zhí)行的主類) 當使用JDK1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic、REF_putStatic端铛、REF_invokeStatic的方法句柄疲眷,并且這個方法句柄所對應的的類沒有進行過初始化,則需先觸發(fā)其初始化 主動引用 上述五種情況均為主動引用 被動引用 所有引用類的方法都不會觸發(fā)初始化 通過子類引用父類的靜態(tài)字段换淆,不會導致子類初始化 通過數(shù)組定義來引用類,不會觸發(fā)此類的初始化 常量在譯階段會存入調(diào)用類的常量池中讯屈,本質(zhì)上并沒有直接引用到定義常量的類县习,因此不會觸發(fā)定義常量的類的初始化 類構(gòu)造器()
可由類中的static{}語句塊產(chǎn)生 也可由接口中的定義的常量產(chǎn)生,接口中不能定義static{}語句塊叛本,需要注意的是彤钟,執(zhí)行接口的()方法不需要先執(zhí)行父接口的()方法,只有當父接口的常量使用時营搅,父接口才會初始化 它不要顯示調(diào)用地父類構(gòu)造器峡眶,虛擬機會保證在子類的()方法執(zhí)行之前,父類的()方法已經(jīng)執(zhí)行完畢峭拘∈ㄊ睿可以得出java.lang.Object是第一個先執(zhí)行()方法的類 虛擬機會保證一個類的()方法在多線程環(huán)境中被正確加鎖、同步拣展,如若多個線程同時去初始化一個類缔逛,那么只有一個線程執(zhí)行這個類的()方法褐奴,其他線程阻塞等待,直至這個線程執(zhí)行完()方法敦冬。其他線程喚醒之后不會再次進入()方法 ()方法不是必須的脖旱,如果類中沒有靜態(tài)語句塊介蛉,也沒有對變量的賦值操作溶褪,那么編譯器可以不為這個類生成()方法 實例構(gòu)造器() 使用 卸載
類加載器 類加載器分類 從JVM的角度上看 啟動類加載器 Bootstrap ClassLoader 由C++實現(xiàn),是虛擬機的一部分 所有其他的類加載器 由Java實現(xiàn)佳恬,獨立于虛擬機外部于游,并且全部繼承自抽象類ClassLoader 從Java開發(fā)人員角度上看 啟動類加載器 Boostrap ClassLoader 拓展類加載器 Extension ClassLoader 應用程序類加載器 Application ClassLoader 自定義類加載器 User ClassLoader 雙親委派模型 如果一個類加載器收到了類加載的請求贰剥,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成蚌成,每一個層次的類加載器都是如此担忧,因此所有的加載請求最終都是應該傳送到頂層的啟動類加載器中,只有當父類加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時最欠,子加載器才會嘗試自己去加載
下面附上我自己整理的路線圖:
最后:
上面的路線圖只是一部分惩猫,需要歡迎大家私信我:另外整理了1000多道將近500多頁pdf文檔的Java面試題資料關注后領取轧房。