java自動(dòng)內(nèi)存管理機(jī)制概述

1. java內(nèi)存區(qū)域與內(nèi)存溢出異常

? java虛擬機(jī)在執(zhí)行java程序的過程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。java 虛擬機(jī)管理的內(nèi)存包括以下幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域:程序計(jì)數(shù)器,java 虛擬機(jī)棧,本地方法棧,java堆,方法區(qū),運(yùn)行時(shí)常量區(qū)。

1.1運(yùn)行時(shí)數(shù)據(jù)區(qū)

1.1.1程序計(jì)數(shù)器(線程私有)

? 程序計(jì)數(shù)器是一塊較小的內(nèi)存區(qū)間,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支,循環(huán),跳轉(zhuǎn),異常處理,線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來完成。

? 為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)。

? 線程執(zhí)行java方法,計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址。執(zhí)行Native方法,計(jì)數(shù)器值為空(undefined)。

? 此內(nèi)存區(qū)域是虛擬機(jī)規(guī)范中唯一沒有規(guī)定任何oom情況的區(qū)域。

1.1.2 java虛擬機(jī)棧(私有)

? 虛擬機(jī)棧描述的是java方法執(zhí)行的內(nèi)存模型,每個(gè)方法執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(stack frame)用于存儲(chǔ)局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出入口等信息。方法從調(diào)用到執(zhí)行完成對(duì)應(yīng)著棧幀在虛擬機(jī)棧中入棧到出棧過程。

常說的棧內(nèi)存(stack)也就是虛擬機(jī)棧,或者說虛擬機(jī)棧中局部變量表部分。表中存放了編譯期可知的各種基本類型數(shù)據(jù)(boolean,byte,char,short,int,float,long,double),對(duì)象引用(reference類型,不等同于對(duì)象本身,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔?,也可能是指向一個(gè)代表對(duì)象的句柄或其他與此對(duì)象相關(guān)的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)。

? long,doube(64位長度)類型的數(shù)據(jù)占用2個(gè)局部變量空間,其余數(shù)據(jù)類型占用一個(gè)。表中所需的內(nèi)存在編譯期間完成分配,進(jìn)入一個(gè)方法時(shí),方法需要在棧中分配多大的局部變量空間是完全確定的,方法運(yùn)行期間不會(huì)改變局部變量表的大小。

對(duì)該區(qū)域規(guī)定了兩種異常狀況:如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,拋出StackOverflowError,如果虛擬機(jī)擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存,將會(huì)拋出OOMError。

1.1.3 本地方法棧(私有)

? 與虛擬機(jī)棧作用類似,虛擬機(jī)棧執(zhí)行java方法,本地方法棧執(zhí)行native方法。

1.1.4 java堆(共享)

? 虛擬機(jī)創(chuàng)建時(shí)啟動(dòng),目的是存放對(duì)象實(shí)例,幾乎所有對(duì)象的實(shí)例都在這里分配內(nèi)存。是垃圾收集器管理的主要區(qū)域,也稱為“(GC堆)”(garbage collection heap)。

從內(nèi)存回收角度,收集器基本都采用分代收集算法,java堆可分為新生代和老生代。

? java堆可處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可。

如果在堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法再擴(kuò)展時(shí),會(huì)拋出oom。

1.1.5 方法區(qū)(共享)

? 存儲(chǔ)已被虛擬機(jī)加載的類信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。

? 這個(gè)區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載。當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),會(huì)拋出oom。

1.1.6 運(yùn)行時(shí)常量池

屬于方法區(qū)的一部分,用于存放編譯期生成各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。

? 除了保存class文件中描述的符號(hào)引用外,還會(huì)把翻譯出來的直接引用也存儲(chǔ)在池中。此外,運(yùn)行時(shí)也可將新的常量放入池中(string的intern()方法)。

? 當(dāng)常量池?zé)o法申請(qǐng)內(nèi)存時(shí)拋出oom。

1.2 虛擬機(jī)對(duì)象

1.2.1 對(duì)象的創(chuàng)建

? 虛擬機(jī)遇到new指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用所代表的類是否已經(jīng)被加載,解析和初始化過。如果沒有就去執(zhí)行類加載過程。類加載檢查通過后,虛擬機(jī)將為新生對(duì)象分配內(nèi)存,所需的內(nèi)存大小在類加載完成后便可確定,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從java堆中劃分出來。

? 內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值,保證了對(duì)象的實(shí)例字段在java代碼中可以不賦初始值就直接使用,程序?qū)⒃L問到對(duì)應(yīng)的零值。

next,虛擬機(jī)將把這個(gè)對(duì)象是那個(gè)類的實(shí)例,如何找到累的元數(shù)據(jù)信息,對(duì)象的哈希碼,GC分代年齡等信息放在對(duì)象頭中。

至此,從虛擬機(jī)角度看新對(duì)象已產(chǎn)生,但從java角度,對(duì)象創(chuàng)建剛剛開始,方法還沒執(zhí)行,所有字段都還為零。所以,一般來說,執(zhí)行new指令后會(huì)執(zhí)行方法,把對(duì)象進(jìn)行初始化來產(chǎn)生真正可用的對(duì)象。

1.2.2 對(duì)象的內(nèi)存布局

? 對(duì)象在內(nèi)存中布局可分為3塊區(qū)域:對(duì)象頭,實(shí)例數(shù)據(jù),對(duì)齊填充。

? 對(duì)象頭包括兩部分:

? 1.用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希嗎,GC分代年齡等。

2.類型指針,即對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。

實(shí)例數(shù)據(jù)是對(duì)象真正存儲(chǔ)的有效信息,也是程序代碼中所定義的各種類型的字段內(nèi)容。在父類中定義的變量會(huì)出現(xiàn)在子類之前。

? 3.對(duì)齊填充不是必然存在的。自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍。

1.2.3 對(duì)象的訪問定位

? java程序需要通過棧上的reference數(shù)據(jù)來操作操作堆上的具體對(duì)象。主流的訪問方式有使用句柄和直接指針兩種。


句柄訪問:java堆中劃分出一塊內(nèi)存作為句柄,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息。好處:reference中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象移動(dòng)(垃圾收集時(shí)移動(dòng)對(duì)象時(shí)非常普遍的行為)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,reference本身不需要修改。


指針訪問:reference數(shù)據(jù)中存儲(chǔ)的直接就是對(duì)象地址。好處:速度更快。對(duì)象訪問十分頻繁,積少成多后將成為非??陀^的執(zhí)行成本。

2 .垃圾收集器與內(nèi)存分配策略

? ?程序計(jì)數(shù)器,虛擬機(jī),本地方法棧等3個(gè)區(qū)域隨線程而生,隨線程而滅。每一個(gè)棧幀分配多少內(nèi)存基本上是類結(jié)構(gòu)確定下來時(shí)就已知的。

? java堆和方法區(qū)在程序處于運(yùn)行期間才會(huì)知道會(huì)創(chuàng)建哪些對(duì)象,這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的,垃圾收集器所關(guān)注的是這部分內(nèi)存。

2.1 判斷對(duì)象是否存活

? 引用計(jì)數(shù)算法:很少使用,難以解決對(duì)象之間相互循環(huán)引用的問題。

可達(dá)性分析算法:java,c#都用可達(dá)性分析判斷對(duì)象是否存活。思路:通過一系列稱為“GC Roots”的對(duì)象為起始點(diǎn),從這些節(jié)點(diǎn)向下搜索,搜索走過的路徑稱為引用連,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相關(guān)連時(shí),對(duì)象不可用。


? 可以作為GC Root 引用點(diǎn)的是:

? 虛擬機(jī)棧(棧幀中的本地變量表)中的引用的對(duì)象。

? 方法區(qū)中類靜態(tài)屬性引用的對(duì)象。

? 方法區(qū)中常量引用指向的對(duì)象。

Native方法中JNI(一般所說的native)引用的對(duì)象。

2.2 生存還是死亡

? 即時(shí)在可達(dá)性分析算法中不可達(dá)的對(duì)象也不是非死不可。宣告一個(gè)對(duì)象死亡至少經(jīng)歷兩次標(biāo)記過程:如果對(duì)象在進(jìn)行可達(dá)性分析后沒有與GC Root相連接的引用鏈,那它將會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選,篩選條件是此對(duì)象是否有必要執(zhí)行finalize()方法。

1.當(dāng)對(duì)象沒有覆蓋finalize(),finalize()已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況視為沒有必要執(zhí)行。

? 2.對(duì)象有必要執(zhí)行finalize()方法,這個(gè)對(duì)象會(huì)放在F-Queue的隊(duì)列中。稍后,GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模標(biāo)記,如果對(duì)象要在finalize()中拯救自己,只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可。如果這時(shí)候?qū)ο筮€沒有逃脫,那么他就真的被回收了。

2.3 垃圾回收算法

2.3.1 標(biāo)記—清除算法

? 最基礎(chǔ)的收集算法是“標(biāo)記-清除”算法:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。不足:1.標(biāo)記和清除兩個(gè)過程的效率不高。2.空間問題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行過程中需要分配大對(duì)象時(shí),無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。

2.3.2 復(fù)制算法

? 把內(nèi)存劃分成等量的兩塊,每次只使用一塊。一塊用完后把存活著的對(duì)象復(fù)制到另外一塊上面,然后把已使用過的內(nèi)存空間一次清理掉。優(yōu)點(diǎn):每次對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效。缺點(diǎn):內(nèi)存縮小為原來的一半,代價(jià)太大。

目前商業(yè)虛擬機(jī)都采用這種算法來回收新生代。因?yàn)樾律浅λ?,所以不需要按?:1的比例來劃分內(nèi)存空間而是劃分成較大的Eden空間和兩塊較小的survivor空間,每次使用Eden空間和一塊survivor空間?;厥諘r(shí)把Eden和survivor空間中存活的對(duì)象一次復(fù)制到另一個(gè)survivor空間,然后清理Eden和survivor空間。

2.3.3 標(biāo)記—整理算法

? 主要用于老年代,標(biāo)記過程與“標(biāo)記-清除”算法一樣,但后續(xù)不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊以外的內(nèi)存。

2.3.4 分代收集算法

? 當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”算法,根據(jù)對(duì)象存活周期把內(nèi)存分為幾塊,一般是把java堆分為新生代和老生代,這樣可以根據(jù)各個(gè)年代的特點(diǎn)采用最合適的收集算法。

在新生代中,每次垃圾收集時(shí)都有大批對(duì)象死去,之后少量存活,那就選用復(fù)制算法。老年代中因?yàn)閷?duì)象存活率高,沒有額外空間對(duì)它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清理”或者“標(biāo)記-整理”算法來進(jìn)行回收。

2.4 hotspot的算法實(shí)現(xiàn)

? hotspot虛擬機(jī)上實(shí)現(xiàn)以上算法的時(shí)候,必須對(duì)算法的執(zhí)行效率有嚴(yán)格的考量,才能保證虛擬機(jī)高效運(yùn)行。

2.4.1 枚舉根節(jié)點(diǎn)

如果在應(yīng)用中使用可達(dá)性分析從GC Root節(jié)點(diǎn)找引用鏈這個(gè)操作,會(huì)消耗很多時(shí)間。(可作為GC Root節(jié)點(diǎn)的主要在全局性引用(例如常量或類靜態(tài)屬性)與執(zhí)行上下文中(例如棧幀中的本地變量表))而且,可達(dá)性分析對(duì)執(zhí)行時(shí)間的敏感性還體現(xiàn)在GC停頓上,因?yàn)樵摲治龉ぷ鞣治銎陂g整個(gè)執(zhí)行系統(tǒng)看起來就像被凍住在某個(gè)時(shí)間點(diǎn)上,不可以出現(xiàn)分析過程中對(duì)象引用關(guān)系還在不斷變化的情況。這是導(dǎo)致GC進(jìn)行時(shí)必須停頓所有java執(zhí)行線程的其中一個(gè)重要原因。

? 主流的java虛擬機(jī)使用的是準(zhǔn)確式GC(虛擬機(jī)可以知道內(nèi)存中某個(gè)位置的數(shù)據(jù)具體是什么類型),所以當(dāng)系統(tǒng)停頓下來之后,不需要一個(gè)不漏的檢查完所有的執(zhí)行上下文和全覺得引用位置,虛擬機(jī)有辦法直接得知哪些位置存放著對(duì)象引用。在hotpot的實(shí)現(xiàn)中,用OopMap的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn),在類加載完成后,就把對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來,在jit編譯過程中也會(huì)在特定的位置記錄下棧和寄存器中哪些位置是引用。這樣,GC在掃描的時(shí)候就可以直接得知這些信息了。

2.4.2 安全點(diǎn)

? OopMap只在特定的位置記錄這些信息,這些位置稱為“安全點(diǎn)”。即程序執(zhí)行時(shí)并非在所有的地方都能停下來開始GC,只有在到達(dá)安全點(diǎn)時(shí)才能暫停。

? 安全點(diǎn)選取需要考慮兩點(diǎn):

? 1.安全點(diǎn)的選定不能讓GC等待時(shí)間人太長,也不能過于頻繁以至于過分增大運(yùn)行時(shí)的負(fù)荷。所以安全點(diǎn)的選定事宜程序是否具有讓程序長時(shí)間執(zhí)行的特征為標(biāo)準(zhǔn)進(jìn)行選定的。長時(shí)間的最明顯特征就是指令復(fù)用,例如方法調(diào)用,循環(huán)跳轉(zhuǎn),異常跳轉(zhuǎn)等,所有具有這些功能的指令才會(huì)產(chǎn)生安全點(diǎn)。

? 2.如何在GC發(fā)生時(shí)讓所有的線程都跑到安全點(diǎn)上再停下來。1.可選用搶先式中斷,不需要線程的執(zhí)行代碼去主動(dòng)配合,在GC發(fā)生時(shí),首先把所有線程全部中斷,如果發(fā)現(xiàn)有線程中斷的地方不在安全點(diǎn)上,就恢復(fù)線程,讓它跑到安全點(diǎn)上。2選用主動(dòng)式中斷,當(dāng)GC需要中斷線程時(shí),不直接對(duì)線程操作,設(shè)置一個(gè)標(biāo)志,各線程執(zhí)行時(shí)主動(dòng)去輪詢這個(gè)標(biāo)志,發(fā)現(xiàn)中斷標(biāo)志為真就自己中斷掛起,輪詢標(biāo)志的地方和安全點(diǎn)時(shí)重合的,另外再加上創(chuàng)建對(duì)象需要分配內(nèi)存的地方。

2.4.3 安全區(qū)域

? 當(dāng)程序不執(zhí)行時(shí),沒有分配cpu時(shí)間(比如線程處于sleep狀態(tài)活著blocked狀態(tài)),這時(shí)候線程時(shí)無法響應(yīng)jvm的中斷請(qǐng)求,走到安全點(diǎn)去中斷掛起,jvm也不太可能等待線程重新被分配cpu時(shí)間。這時(shí)需要安全區(qū)域來解決。

? 安全區(qū)域是指在一段代碼片段中,引用關(guān)系不會(huì)發(fā)生變化,這個(gè)區(qū)域中任意地方開始GC都是安全的。可以看作被擴(kuò)展了的安全點(diǎn)。

2.5垃圾收集器

2.5.1 serial收集器(單線程)

? 這是最基本,發(fā)展歷史最悠久的收集器。是虛擬機(jī)運(yùn)行在client模式下默認(rèn)的新生代收集器。

? 用一個(gè)cpu或一條收集線程完成垃圾收集工作,在進(jìn)行垃圾收集時(shí)必須暫停其他所有的工作線程,直到它收集結(jié)束。

? 優(yōu)點(diǎn):簡(jiǎn)單而高效。對(duì)于限定單個(gè)cpu的環(huán)境來說,沒有線程交互的開銷,專心做垃圾收集可以獲得最高的單線程收集效率。

2.5.2 parnew收集器(多線程)

? 是serial收集器的多線程版本。時(shí)許多運(yùn)行在server模式下虛擬機(jī)中首選的新生代收集器。只有它能與cms收集器配合工作。

? 在單cpu情況下不如serial,但是多cpu有優(yōu)勢(shì)。

2.5.3 parallel scavenge 收集器 (多線程)

? 新生代收集器,使用復(fù)制算法,并行的多線程收集器。

? 達(dá)到可控制的吞吐量,可以高效的利用cpu時(shí)間,盡快完成運(yùn)算任務(wù)。適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。

2.5.4 serial old 收集器 (單線程)

? 是serial的老年代版本。

2.5.5 parallel old 收集器 (多線程)

? 是parallel的老年代版本。

2.5.6 cms收集器(多線程)

? 盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,良好的響應(yīng)速度能提升用戶體驗(yàn)。

2.5.7 G1收集器

?

2.6 內(nèi)存分配與回收策略

java內(nèi)存體系中所提倡的自動(dòng)內(nèi)存管理最終可以歸結(jié)為自動(dòng)化的解決了兩個(gè)問題:給對(duì)象分配內(nèi)存及回收分配給對(duì)象的內(nèi)存(垃圾收集器體系以及運(yùn)作原理)。

對(duì)象的內(nèi)存分配就是在堆上分配,對(duì)象主要分配在新生代的Eden區(qū)上,少數(shù)情況下也可能會(huì)直接分配在老年代中。

以下為集體哦啊最普遍的內(nèi)存分配規(guī)則:

2.6.1 對(duì)象優(yōu)先在Eden分配

新生代GC:指發(fā)生在新生代的垃圾收集動(dòng)作,因?yàn)閖ava對(duì)象大多都具備朝生夕滅的特性,所以新生代GC十分頻繁,一般回收速度也比較快。

老年代GC:指發(fā)生在老年代的GC,出現(xiàn)了老年代GC,經(jīng)常會(huì)伴隨至少一次的新生代GC。速度比新生代GC慢10倍以上。

大多數(shù)情況下,對(duì)象在新生代Eden區(qū)中分配,當(dāng)該區(qū)沒有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次新生代GC。

2.6.2 大對(duì)象直接進(jìn)入老年代

大對(duì)象是指需要大量連續(xù)內(nèi)存空間的java對(duì)象,最典型的就是那種長的字符串以及數(shù)組。

2.6.3 長期存活的對(duì)象將進(jìn)入老年代

虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡(age)計(jì)數(shù)器,如果對(duì)象在Eden出生并經(jīng)過第一次新生代GC后仍然存活,并且能被survivor容納的話,將被移動(dòng)到 survivor空間中,并且對(duì)象年齡設(shè)為1。每在survivor區(qū)中安過一次新生代GC,age都會(huì)增加1,年齡增加到一定程度(默認(rèn)15)會(huì)被晉升到老年代中。

2.6.4 動(dòng)態(tài)對(duì)象年齡判定

虛擬機(jī)并不是永遠(yuǎn)要求對(duì)象的年齡必須達(dá)到最大(15)才能晉升到老年代。如果survivor空間中相同年齡所有對(duì)象的大小總和大于survivor空間的一半, 年齡大于或等于該年齡的對(duì)象就可直接進(jìn)入老年代,不需要按照最大年齡來。

2.6.5 空間分配擔(dān)保

發(fā)生新生代GC之前,虛擬機(jī)會(huì)先檢查老年代中最大可用連續(xù)空間是否大于新生代所有對(duì)象總空間。成立則新生代GC確保是安全的。如果不成立,虛擬機(jī)會(huì)產(chǎn)看是否允許分配擔(dān)保失敗,如果允許會(huì)繼續(xù)檢查老年代中最大可用連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于嘗試進(jìn)行一次新生代GC(有風(fēng)險(xiǎn))。如果小于,或者不允許就會(huì)進(jìn)行老年代GC。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容