(三)---垃圾回收算法及回收器

前言

 在介紹了對(duì)象的創(chuàng)建、定位以后,具體的使用則需要根據(jù)不同的業(yè)務(wù)邏輯來確定,這部分是比較自由的。但正如前文所說,Java程序員將內(nèi)存的管理交托給了JVM。因此此章節(jié)將進(jìn)一步介紹Java垃圾收集器及內(nèi)存分配策略的相關(guān)知識(shí)。

Java語言出來之前,大家都在拼命的寫C或者C++的程序,而此時(shí)存在一個(gè)很大的矛盾,C++等語言創(chuàng)建對(duì)象要不斷的去開辟空間,不用的時(shí)候有需要不斷的去釋放控件,既要寫構(gòu)造函數(shù),又要寫析構(gòu)函數(shù),很多時(shí)候都在重復(fù)的allocated,然后不停的~析構(gòu)。于是,有人就提出,能不能寫一段程序在實(shí)現(xiàn)這塊功能,每次創(chuàng)建,釋放控件的時(shí)候復(fù)用這段代碼,而無需重復(fù)的書寫呢?

1960年 基于MIT的Lisp首先提出了垃圾回收的概念,用于處理C語言等不停的析構(gòu)操作,而這時(shí)Java還沒有出世呢!所以實(shí)際上GC并不是Java的專利,GC的歷史遠(yuǎn)遠(yuǎn)大于Java的歷史!

那究竟GC為我們做了什么操作呢?

1、 哪些內(nèi)存需要回收?

2、 什么時(shí)候回收?

3、 如何回收?

這時(shí)候有人就會(huì)疑惑了,既然GC已經(jīng)為我們解決了這個(gè)矛盾,我們還需要學(xué)習(xí)GC么?當(dāng)然當(dāng)然是肯定的,那究竟什么時(shí)候我們還需要用到的呢?

1、 排查內(nèi)存溢出

2、 排查內(nèi)存泄漏

3、 性能調(diào)優(yōu),排查并發(fā)瓶頸

因此這就是學(xué)習(xí)和了解GC機(jī)制的最主要原因。

一、哪些內(nèi)存需要回收?

  針對(duì)這個(gè)問題,我們先要明確一點(diǎn),JVM重點(diǎn)回收的是內(nèi)存共享的區(qū)域,也就是堆和方法區(qū),因?yàn)槌绦蛴?jì)數(shù)器、虛擬機(jī)棧、本地方法棧這三個(gè)區(qū)域都是線程私有的,因而隨線程而生,隨線程而滅;棧中的棧幀隨著方法的進(jìn)入和退出有條不紊地執(zhí)行出棧和入棧操作。每個(gè)棧幀分配多少內(nèi)存基本上以及在類結(jié)構(gòu)確定下來時(shí),便已知曉。因此隨著方法的結(jié)束,這些棧幀所分配的內(nèi)存自然而然被回收了。

  而Java堆和方法區(qū)則不一樣,一個(gè)接口中的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能不一樣,一個(gè)方法中的多個(gè)分支需要的內(nèi)存也可能不一樣,我們只有在程序運(yùn)行期間才能知道創(chuàng)建哪些對(duì)象,這部分內(nèi)存分配和回收都是動(dòng)態(tài)的,垃圾收集器所關(guān)注的正是這部分內(nèi)存。

二、什么時(shí)候回收

我們知道,GC主要處理的是對(duì)象的回收操作,那么什么時(shí)候會(huì)觸發(fā)一個(gè)對(duì)象的回收的呢?

1、 對(duì)象沒有引用

2、 作用域發(fā)生未捕獲異常

3、 程序在作用域正常執(zhí)行完畢

4、 程序執(zhí)行了System.exit()

5、 程序發(fā)生意外終止(被殺進(jìn)程等)

那么,緊接而來的問題便是,如何確定對(duì)象已經(jīng)“死去”以達(dá)到被回收的條件?

這里便需要提及的兩種判定對(duì)象是否“死去”的方法:

1、 引用計(jì)數(shù)算法

在JDK1.2之前,使用的是引用計(jì)數(shù)器算法,即當(dāng)這個(gè)類被加載到內(nèi)存以后,就會(huì)產(chǎn)生方法區(qū),堆棧、程序計(jì)數(shù)器等一系列信息,當(dāng)創(chuàng)建對(duì)象的時(shí)候,為這個(gè)對(duì)象在堆棧空間中分配對(duì)象,同時(shí)會(huì)產(chǎn)生一個(gè)引用計(jì)數(shù)器,同時(shí)引用計(jì)數(shù)器+1,當(dāng)有新的引用的時(shí)候,引用計(jì)數(shù)器繼續(xù)+1,而當(dāng)其中一個(gè)引用銷毀的時(shí)候,引用計(jì)數(shù)器-1,當(dāng)引用計(jì)數(shù)器被減為零的時(shí)候,標(biāo)志著這個(gè)對(duì)象已經(jīng)沒有引用了,可以回收了!這種算法在JDK1.2之前的版本被廣泛使用,但是隨著業(yè)務(wù)的發(fā)展,很快出現(xiàn)了一個(gè)問題

當(dāng)我們的代碼出現(xiàn)下面的情形時(shí),該算法將無法適應(yīng)

a) ObjA.obj = ObjB

b) ObjB.obj = ObjA

這樣的代碼會(huì)產(chǎn)生如下引用情形 objA指向objB,而objB又指向objA,這樣當(dāng)其他所有的引用都消失了之后,objA和objB還有一個(gè)相互的引用,也就是說兩個(gè)對(duì)象的引用計(jì)數(shù)器各為1,而實(shí)際上這兩個(gè)對(duì)象都已經(jīng)沒有額外的引用,已經(jīng)是垃圾了。



2、可達(dá)性分析算法

  根搜索算法是從離散數(shù)學(xué)中的圖論引入的,程序把所有的引用關(guān)系看作一張圖,從一個(gè)節(jié)點(diǎn)GC ROOT開始,尋找對(duì)應(yīng)的引用節(jié)點(diǎn),找到這個(gè)節(jié)點(diǎn)以后,繼續(xù)尋找這個(gè)節(jié)點(diǎn)的引用節(jié)點(diǎn),當(dāng)所有的引用節(jié)點(diǎn)尋找完畢之后,剩余的節(jié)點(diǎn)則被認(rèn)為是沒有被引用到的節(jié)點(diǎn),即無用的節(jié)點(diǎn)。


目前java中可作為GC Root的對(duì)象有

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

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

3、 方法區(qū)中常量引用的對(duì)象

4、 本地方法棧中引用的對(duì)象(Native對(duì)象)

說了這么多,其實(shí)我們可以看到,所有的垃圾回收機(jī)制都是和引用相關(guān)的,那我們來具體的來看一下引用的分類,到底有哪些類型的引用?每種引用都是做什么的呢?

Java中存在四種引用,每種引用如下:

1、 強(qiáng)引用

只要引用存在,垃圾回收器永遠(yuǎn)不會(huì)回收

Object obj = new Object();

//可直接通過obj取得對(duì)應(yīng)的對(duì)象 如obj.equels(new Object());

而這樣 obj對(duì)象對(duì)后面new Object的一個(gè)強(qiáng)引用,只有當(dāng)obj這個(gè)引用被釋放之后,對(duì)象才會(huì)被釋放掉,這也是我們經(jīng)常所用到的編碼形式。

2、 軟引用

非必須引用,內(nèi)存溢出之前進(jìn)行回收,可以通過以下代碼實(shí)現(xiàn)

????Object obj = new Object();

????SoftReference sf = new SoftReference(obj);

????obj = null;

????sf.get();//有時(shí)候會(huì)返回null

這時(shí)候sf是對(duì)obj的一個(gè)軟引用,通過sf.get()方法可以取到這個(gè)對(duì)象,當(dāng)然,當(dāng)這個(gè)對(duì)象被標(biāo)記為需要回收的對(duì)象時(shí),則返回null;

3、 弱引用

第二次垃圾回收時(shí)回收,可以通過如下代碼實(shí)現(xiàn)

????Object obj = new Object();

????WeakReference wf = new WeakReference(obj);

????obj = null;

????wf.get();//有時(shí)候會(huì)返回null

????wf.isEnQueued();//返回是否被垃圾回收器標(biāo)記為即將回收的垃圾

弱引用是在第二次垃圾回收時(shí)回收,短時(shí)間內(nèi)通過弱引用取對(duì)應(yīng)的數(shù)據(jù),可以取到,當(dāng)執(zhí)行過第二次垃圾回收時(shí),將返回null。

弱引用主要用于監(jiān)控對(duì)象是否已經(jīng)被垃圾回收器標(biāo)記為即將回收的垃圾,可以通過弱引用的isEnQueued方法返回對(duì)象是否被垃圾回收器

4、 虛引用(幽靈/幻影引用)

垃圾回收時(shí)回收,無法通過引用取到對(duì)象值,可以通過如下代碼實(shí)現(xiàn)

????Object obj = new Object();

????PhantomReference pf = new PhantomReference(obj);

????obj=null;

????pf.get();//永遠(yuǎn)返回null

????pf.isEnQueued();//返回從內(nèi)存中已經(jīng)刪除

虛引用是每次垃圾回收的時(shí)候都會(huì)被回收,通過虛引用的get方法永遠(yuǎn)獲取到的數(shù)據(jù)為null,因此也被成為幽靈引用。

虛引用主要用于檢測(cè)對(duì)象是否已經(jīng)從內(nèi)存中刪除。

在上文中已經(jīng)提到了,我們的對(duì)象在內(nèi)存中會(huì)被劃分為5塊區(qū)域,而每塊數(shù)據(jù)的回收比例是不同的,根據(jù)IBM的統(tǒng)計(jì),數(shù)據(jù)如下圖所示:


我們知道,方法區(qū)主要存放類與類之間關(guān)系的數(shù)據(jù),而這部分?jǐn)?shù)據(jù)被加載到內(nèi)存之后,基本上是不會(huì)發(fā)生變更的

Java堆中的數(shù)據(jù)基本上是朝生夕死的,我們用完之后要馬上回收的,而Java棧和本地方法棧中的數(shù)據(jù),因?yàn)橛泻筮M(jìn)先出的原則,當(dāng)我取下面的數(shù)據(jù)之前,必須要把棧頂?shù)脑爻鰲?,因此回收率可認(rèn)為是100%;而程序計(jì)數(shù)器我們前面也已經(jīng)提到,主要用戶記錄線程執(zhí)行的行號(hào)等一些信息,這塊區(qū)域也是被認(rèn)為是唯一一塊不會(huì)內(nèi)存溢出的區(qū)域。在SunHostSpot的虛擬機(jī)中,對(duì)于程序計(jì)數(shù)器是不回收的,而方法區(qū)的數(shù)據(jù)因?yàn)榛厥章史浅P。杀居直容^高,一般認(rèn)為是“性價(jià)比”非常差的,所以Sun自己的虛擬機(jī)HotSpot中是不回收的!但是在現(xiàn)在高性能分布式J2EE的系統(tǒng)中,我們大量用到了反射、動(dòng)態(tài)代理、CGLIB、JSP和OSGI等,這些類頻繁的調(diào)用自定義類加載器,都需要?jiǎng)討B(tài)的加載和卸載了,以保證永久帶不會(huì)溢出,他們通過自定義的類加載器進(jìn)行了各項(xiàng)操作,因此在實(shí)際的應(yīng)用開發(fā)中,類也是被經(jīng)常加載和卸載的,方法區(qū)也是會(huì)被回收的!但是方法區(qū)的回收條件非??量?,只有同時(shí)滿足以下三個(gè)條件才會(huì)被回收!

1、所有實(shí)例被回收

2、加載該類的ClassLoader被回收

3、Class對(duì)象無法通過任何途徑訪問(包括反射)

三、如何回收?

好了,我們現(xiàn)在切入正題,Java1.2之前主要通過引用計(jì)數(shù)器來標(biāo)記是否需要垃圾回收,而1.2之后都使用根搜索算法來收集垃圾,而收集后的垃圾是通過什么算法來回收的呢?

1、 標(biāo)記-清除算法

2、 復(fù)制算法

3、 標(biāo)記-整理算法

我們來逐一過一下

1、 標(biāo)記-清除算法

? ?標(biāo)記-清除算法采用從根集合進(jìn)行掃描,對(duì)存活的對(duì)象對(duì)象標(biāo)記,標(biāo)記完畢后,再掃描整個(gè)空間中未被標(biāo)記的對(duì)象,進(jìn)行回收,如上圖所示。

 標(biāo)記-清除算法不需要進(jìn)行對(duì)象的移動(dòng),并且僅對(duì)不存活的對(duì)象進(jìn)行處理,在存活對(duì)象比較多的情況下極為高效,但由于標(biāo)記-清除算法直接回收不存活的對(duì)象,因此會(huì)造成內(nèi)存碎片!

2、 復(fù)制算法


????復(fù)制算法采用從根集合掃描,并將存活對(duì)象復(fù)制到一塊新的,沒有使用過的空間中,這種算法當(dāng)控件存活的對(duì)象比較少時(shí),極為高效,但是帶來的成本是需要一塊內(nèi)存交換空間用于進(jìn)行對(duì)象的移動(dòng)。也就是我們前面提到的s0 s1等空間。

3、 標(biāo)記-整理算法

 標(biāo)記 -整理算法采用標(biāo)記-清除算法一樣的方式進(jìn)行對(duì)象的標(biāo)記,但在清除時(shí)不同,在回收不存活的對(duì)象占用的空間后,會(huì)將所有的存活對(duì)象往左端空閑空間移動(dòng),并更新對(duì)應(yīng)的指針。標(biāo)記-整理算法是在標(biāo)記-清除算法的基礎(chǔ)上,又進(jìn)行了對(duì)象的移動(dòng),因此成本更高,但是卻解決了內(nèi)存碎片的問題。

  我們知道,JVM為了優(yōu)化內(nèi)存的回收,進(jìn)行了分代回收的方式,對(duì)于新生代內(nèi)存的回收(minor GC)主要采用復(fù)制算法,下圖展示了minor GC的執(zhí)行過程。


????對(duì)于新生代和舊生代, JVM可使用很多種垃圾回收器進(jìn)行垃圾回收,下圖展示了不同生代不通垃圾回收器,其中兩個(gè)回收器之間有連線表示這兩個(gè)回收器可以同時(shí)使用。

  而這些垃圾回收器又分為串行回收方式、并行回收方式合并發(fā)回收方式執(zhí)行,分別運(yùn)用于不同的場(chǎng)景。如下圖所示


????下面我們來逐一介紹一下每個(gè)垃圾回收器。


Serial收集器

????收集器是歷史最悠久的一個(gè)回收器,JDK1.3之前廣泛使用這個(gè)收集器,目前也是ClientVM下 ServerVM 4核4GB以下機(jī)器的默認(rèn)垃圾回收器。串行收集器并不是只能使用一個(gè)CPU進(jìn)行收集,而是當(dāng)JVM需要進(jìn)行垃圾回收的時(shí)候,需要中斷所有的用戶線程,知道它回收結(jié)束為止,因此又號(hào)稱“Stop The World” 的垃圾回收器。注意,JVM中文名稱為java虛擬機(jī),因此它就像一臺(tái)虛擬的電腦一樣在工作,而其中的每一個(gè)線程就被認(rèn)為是JVM的一個(gè)處理器,因此大家看到圖中的CPU0、CPU1實(shí)際為用戶的線程,而不是真正機(jī)器的CPU,大家不要誤解哦。

????串行回收方式適合低端機(jī)器,是Client模式下的默認(rèn)收集器,對(duì)CPU和內(nèi)存的消耗不高,適合用戶交互比較少,后臺(tái)任務(wù)較多的系統(tǒng)。

Serial收集器默認(rèn)新舊生代的回收器搭配為Serial+ SerialOld

????1、單線程收集器

????2、進(jìn)行垃圾回收時(shí)必須暫停用戶所有的工作線程。

Serial收集器對(duì)于運(yùn)行在Client模式下的虛擬機(jī)來說是一個(gè)很棒的選擇

ParNew收集器

????ParNew收集器其實(shí)就是多線程版本的Serial收集器,其運(yùn)行示意圖如下


 同樣有 Stop The World的問題,他是多CPU模式下的首選回收器(該回收器在單CPU的環(huán)境下回收效率遠(yuǎn)遠(yuǎn)低于Serial收集器,所以一定要注意場(chǎng)景哦),也是Server模式下的默認(rèn)收集器。

  1、Serial的多線程版本

  2、是運(yùn)行在Server模式下的首選新生代收集器,只有它可以與CMS收集器配合工作。通過-XX:ParallelGCThreads參數(shù)來限制垃圾收集的線程數(shù)

Parallel Scavenge收集器

ParallelScavenge又被稱為是吞吐量?jī)?yōu)先的收集器,器運(yùn)行示意圖如下


????所提到的吞吐量=程序運(yùn)行時(shí)間/(JVM執(zhí)行回收的時(shí)間+程序運(yùn)行時(shí)間),假設(shè)程序運(yùn)行了100分鐘,JVM的垃圾回收占用1分鐘,那么吞吐量就是99%。在當(dāng)今網(wǎng)絡(luò)告訴發(fā)達(dá)的今天,良好的響應(yīng)速度是提升用戶體驗(yàn)的一個(gè)重要指標(biāo),多核并行云計(jì)算的發(fā)展要求程序盡可能的使用CPU和內(nèi)存資源,盡快的計(jì)算出最終結(jié)果,因此在交互不多的云端,比較適合使用該回收器。

   1、是并行的多線程收集器

  2、目的是達(dá)到一個(gè)可控制的吞吐量(Throughput)

  3、-XX:MaxGCPauseMillis :最大垃圾手機(jī)停頓時(shí)間

  -XX:GCTimeRatio:設(shè)置吞吐量大小

  -XX:+UseAdaptiveSizePolicy 打開后,虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行狀況收集性能監(jiān)控信息。

Serial Old 收集器

   SerialOld是舊生代Client模式下的默認(rèn)收集器,單線程執(zhí)行;在JDK1.6之前也是ParallelScvenge回收新生代模式下舊生代的默認(rèn)收集器,同時(shí)也是并發(fā)收集器CMS回收失敗后的備用收集器。其運(yùn)行示意圖如下


?Serial的老年代版本,采用“標(biāo)記-整理”算法

Parallel Old 收集器

  ParallelOld是老生代并行收集器的一種,使用標(biāo)記整理算法、是老生代吞吐量?jī)?yōu)先的一個(gè)收集器。這個(gè)收集器是JDK1.6之后剛引入的一款收集器,我們看之前那個(gè)圖之間的關(guān)聯(lián)關(guān)系可以看到,早期沒有ParallelOld之前,吞吐量?jī)?yōu)先的收集器老生代只能使用串行回收收集器,大大的拖累了吞吐量?jī)?yōu)先的性能,自從JDK1.6之后,才能真正做到較高效率的吞吐量?jī)?yōu)先。其運(yùn)行示意圖如下。


Parallel Scavenge收集器的老年代版本,采用“標(biāo)記-整理”算法

1、在注重吞吐量以及CPU資源敏感結(jié)合,可以考慮Parallel Scavenge加Parallel Old收集器

CMS(Concurrent Mark Sweep)收集器

  CMS又稱響應(yīng)時(shí)間優(yōu)先(最短回收停頓)的回收器,使用并發(fā)模式回收垃圾,使用標(biāo)記-清除算法,CMS對(duì)CPU是非常敏感的,它的回收線程數(shù)=(CPU+3)/4,因此當(dāng)CPU是2核的實(shí)惠,回收線程將占用的CPU資源的50%,而當(dāng)CPU核心數(shù)為4時(shí)僅占用25%。他的運(yùn)行示意圖如下


 CMS收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。使用與B/S系統(tǒng)服務(wù)器上。是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的。

運(yùn)作過程:

(1)初始標(biāo)記

(2)并發(fā)標(biāo)記

(3)重新標(biāo)記

(4)并發(fā)清除


????在初始標(biāo)記的時(shí)候,需要中斷所有用戶線程,在并發(fā)標(biāo)記階段,用戶線程和標(biāo)記線程并發(fā)執(zhí)行,而在這個(gè)過程中,隨著內(nèi)存引用關(guān)系的變化,可能會(huì)發(fā)生原來標(biāo)記的對(duì)象被釋放,進(jìn)而引發(fā)新的垃圾,因此可能會(huì)產(chǎn)生一系列的浮動(dòng)垃圾,不能被回收。

  CMS 為了確保能夠掃描到所有的對(duì)象,避免在Initial Marking 中還有未標(biāo)識(shí)到的對(duì)象,采用的方法為找到標(biāo)記了的對(duì)象,并將這些對(duì)象放入Stack 中,掃描時(shí)尋找此對(duì)象依賴的對(duì)象,如果依賴的對(duì)象的地址在其之前,則將此對(duì)象進(jìn)行標(biāo)記,并同時(shí)放入Stack 中,如依賴的對(duì)象地址在其之后,則僅標(biāo)記該對(duì)象。

  在進(jìn)行Concurrent Marking 時(shí)minor GC 也可能會(huì)同時(shí)進(jìn)行,這個(gè)時(shí)候很容易造成舊生代對(duì)象引用關(guān)系改變,CMS 為了應(yīng)對(duì)這樣的并發(fā)現(xiàn)象,提供了一個(gè)Mod Union Table 來進(jìn)行記錄,在這個(gè)Mod Union Table中記錄每次minor GC 后修改了的Card 的信息。這也是ParallelScavenge不能和CMS一起使用的原因。

  CMS產(chǎn)生浮動(dòng)垃圾的情況請(qǐng)見如下示意圖。


????在運(yùn)行回收過后,c就變成了浮動(dòng)垃圾。

????由于CMS會(huì)產(chǎn)生浮動(dòng)垃圾,當(dāng)回收過后,浮動(dòng)垃圾如果產(chǎn)生過多,同時(shí)因?yàn)槭褂脴?biāo)記-清除算法會(huì)產(chǎn)生碎片,可能會(huì)導(dǎo)致回收過后的連續(xù)空間仍然不能容納新生代移動(dòng)過來或者新創(chuàng)建的大資源,因此會(huì)導(dǎo)致CMS回收失敗,進(jìn)而觸發(fā)另外一次FULL GC,而這時(shí)候則采用SerialOld進(jìn)行二次回收。

????同時(shí)CMS因?yàn)榭赡墚a(chǎn)生浮動(dòng)垃圾,而CMS在執(zhí)行回收的同時(shí)新生代也有可能在進(jìn)行回收操作,為了保證舊生代能夠存放新生代轉(zhuǎn)移過來的數(shù)據(jù),CMS在舊生代內(nèi)存到達(dá)全部容量的68%就觸發(fā)了CMS的回收!

主要的三個(gè)缺點(diǎn):

(1)CMS收集器對(duì)CPU資源非常敏感,因?yàn)槭遣⑿袠?biāo)記和并行清除的,多CPU性能存在影響。

(2)CMS收集器無法處理浮動(dòng)垃圾,因?yàn)橛脩艟€程還在運(yùn)行,運(yùn)行過程中還會(huì)產(chǎn)生垃圾

(3)空間碎片,因?yàn)椴捎玫氖恰皹?biāo)記-清除”算法

G1收集器

G1與CMS比較的兩個(gè)改進(jìn)的地方:

(1)使用“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器,不會(huì)產(chǎn)生空間碎片

(2)非常精準(zhǔn)的控制停頓,既能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不超過N毫秒。

(3)G1可以實(shí)現(xiàn)基本不犧牲吞吐量的情況下完成低停頓的內(nèi)存回收。

(4)G1將新生代和老年代劃分為多個(gè)大小固定的獨(dú)立區(qū)域(Region),并且跟蹤這些區(qū)域里面的垃圾堆積程度,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表。

?著作權(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ù)。

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