詳解Java虛擬機(jī)(第⑤篇)——垃圾收集-創(chuàng)新互聯(lián)

詳解Java 虛擬機(jī)(第⑤篇)——垃圾收集

垃圾收集主要是針對(duì)堆和方法區(qū)進(jìn)行。程序計(jì)數(shù)器、虛擬機(jī)棧和本地方法棧這三個(gè)區(qū)域?qū)儆诰€程私有的,只存在于線程的生命周期內(nèi),線程結(jié)束之后就會(huì)消失,因此不需要對(duì)這三個(gè)區(qū)域進(jìn)行垃圾回收。

成都創(chuàng)新互聯(lián)公司成都網(wǎng)站建設(shè)按需制作,是成都網(wǎng)站營銷公司,為不銹鋼雕塑提供網(wǎng)站建設(shè)服務(wù),有成熟的網(wǎng)站定制合作流程,提供網(wǎng)站定制設(shè)計(jì)服務(wù):原型圖制作、網(wǎng)站創(chuàng)意設(shè)計(jì)、前端HTML5制作、后臺(tái)程序開發(fā)等。成都網(wǎng)站維護(hù)熱線:028-86922220

一、判斷一個(gè)對(duì)象是否可被回收

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

為對(duì)象添加一個(gè)引用計(jì)數(shù)器,當(dāng)對(duì)象增加一個(gè)引用時(shí)計(jì)數(shù)器加 1,引用失效時(shí)計(jì)數(shù)器減 1。引用計(jì)數(shù)為 0 的對(duì)象可被回收。

在兩個(gè)對(duì)象出現(xiàn)循環(huán)引用的情況下,此時(shí)引用計(jì)數(shù)器永遠(yuǎn)不為 0,導(dǎo)致無法對(duì)它們進(jìn)行回收。正是因?yàn)檠h(huán)引用的存在,因此 Java 虛擬機(jī)不使用引用計(jì)數(shù)算法。

public class Test {
    public Object instance = null;
    public static void main(String[] args) {
        Test a = new Test();
        Test b = new Test();
        a.instance = b;
        b.instance = a;
        a = null;
        b = null;
        doSomething();
    }
}

在上述代碼中,a 與 b 引用的對(duì)象實(shí)例互相持有了對(duì)象的引用,因此當(dāng)我們把對(duì) a 對(duì)象與 b 對(duì)象的引用去除之后,由于兩個(gè)對(duì)象還存在互相之間的引用,導(dǎo)致兩個(gè) Test 對(duì)象無法被回收。

  • 優(yōu)點(diǎn):執(zhí)行效率高,程序執(zhí)行受影響較小。
  • 缺點(diǎn):無法檢測出循環(huán)引用的情況,引起內(nèi)存泄漏。

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

通過判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá)來決定對(duì)象是否可以被回收。

以 GC Roots 為起始點(diǎn)進(jìn)行搜索,可達(dá)的對(duì)象都是存活的,不可達(dá)的對(duì)象可被回收。

Java 虛擬機(jī)使用該算法來判斷對(duì)象是否可被回收,GC Roots 一般包含以下內(nèi)容:

  • 虛擬機(jī)棧中局部變量表中引用的對(duì)象(棧幀中的本地方法變量表)
  • 本地方法棧中 JNI(Native方法) 中引用的對(duì)象
  • 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
  • 方法區(qū)中的常量引用的對(duì)象
  • 活躍線程的引用對(duì)象

3. 方法區(qū)的回收

因?yàn)榉椒▍^(qū)主要存放永久代對(duì)象,而永久代對(duì)象的回收率比新生代低很多,所以在方法區(qū)上進(jìn)行回收性價(jià)比不高。

主要是對(duì)常量池的回收和對(duì)類的卸載。

為了避免內(nèi)存溢出,在大量使用反射和動(dòng)態(tài)代理的場景都需要虛擬機(jī)具備類卸載功能。

類的卸載條件很多,需要滿足以下三個(gè)條件,并且滿足了條件也不一定會(huì)被卸載:

  • 該類所有的實(shí)例都已經(jīng)被回收,此時(shí)堆中不存在該類的任何實(shí)例。
  • 加載該類的 ClassLoader 已經(jīng)被回收。
  • 該類對(duì)應(yīng)的 Class 對(duì)象沒有在任何地方被引用,也就無法在任何地方通過反射訪問該類方法。

4. finalize()

類似 C++ 的析構(gòu)函數(shù),用于關(guān)閉外部資源。但是 try-finally 等方式可以做得更好,并且該方法運(yùn)行代價(jià)很高,不確定性大,無法保證各個(gè)對(duì)象的調(diào)用順序,因此最好不要使用。

當(dāng)一個(gè)對(duì)象可被回收時(shí),如果需要執(zhí)行該對(duì)象的 finalize() 方法,那么就有可能在該方法中讓對(duì)象重新被引用,從而實(shí)現(xiàn)自救。自救只能進(jìn)行一次,如果回收的對(duì)象之前調(diào)用了 finalize() 方法自救,后面回收時(shí)不會(huì)再調(diào)用該方法。

Object 的finalize()方法的作用是否與C++的析構(gòu)函數(shù)作用相同?

  • 與C++的析構(gòu)函數(shù)不同,析構(gòu)函數(shù)調(diào)用確定,而finalize()方法是不確定的;
  • 當(dāng)垃圾回收器要宣告一個(gè)對(duì)象死亡時(shí),至少要經(jīng)歷兩次標(biāo)記過程。如果對(duì)象在進(jìn)行可達(dá)性分析以后,沒有與GC Root直接相連接的引用量,就會(huì)被第一次標(biāo)記,并且判斷是否執(zhí)行finalize()方法;如果這個(gè)對(duì)象覆蓋了finalize()方法,并且未被引用,就會(huì)被放置于F-Queue隊(duì)列,稍后由虛擬機(jī)創(chuàng)建的一個(gè)低優(yōu)先級(jí)的finalize()線程去執(zhí)行觸發(fā)finalize()方法;
  • 由于線程的優(yōu)先級(jí)比較低,執(zhí)行過程隨時(shí)可能會(huì)被終止;
  • 給予對(duì)象最后一次重生的機(jī)會(huì)

二、引用類型

無論是通過引用計(jì)數(shù)算法判斷對(duì)象的引用數(shù)量,還是通過可達(dá)性分析算法判斷對(duì)象是否可達(dá),判定對(duì)象是否可被回收都與引用有關(guān)。

Java 提供了四種強(qiáng)度不同的引用類型。

1. 強(qiáng)引用

被強(qiáng)引用關(guān)聯(lián)的對(duì)象不會(huì)被回收。

使用 new 一個(gè)新對(duì)象的方式來創(chuàng)建強(qiáng)引用。

Object obj = new Object();

拋出OOM Error終止程序也不會(huì)回收具有強(qiáng)引用的對(duì)象,只有通過將對(duì)象設(shè)置為null來弱化引用,才能使其被回收。

2. 軟引用

表示對(duì)象處在有用但非必須的狀態(tài)。

被軟引用關(guān)聯(lián)的對(duì)象只有在內(nèi)存不夠的情況下才會(huì)被回收??梢杂脕韺?shí)現(xiàn)內(nèi)存敏感的高速緩存。

使用 SoftReference 類來創(chuàng)建軟引用。

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;  // 使對(duì)象只被軟引用關(guān)聯(lián)

3. 弱引用

表示非必須的對(duì)象,比軟引用更弱一些。適用于偶爾被使用且不影響垃圾收集的對(duì)象。

被弱引用關(guān)聯(lián)的對(duì)象一定會(huì)被回收,也就是說它只能存活到下一次垃圾回收發(fā)生之前。

使用 WeakReference 類來創(chuàng)建弱引用。

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;

4. 虛引用

又稱為幽靈引用或者幻影引用,一個(gè)對(duì)象是否有虛引用的存在,不會(huì)對(duì)其生存時(shí)間造成影響,也無法通過虛引用得到一個(gè)對(duì)象。

不會(huì)決定對(duì)象的生命周期,任何時(shí)候都可能被垃圾回收器回收。必須和引用隊(duì)列ReferenceQueue聯(lián)合使用。

為一個(gè)對(duì)象設(shè)置虛引用的唯一目的是能在這個(gè)對(duì)象被回收時(shí)收到一個(gè)系統(tǒng)通知,起哨兵作用。具體來說,就是通過判斷引用隊(duì)列ReferenceQueue是否加入虛引用來判斷被引用對(duì)象是否被GC回收。

使用 PhantomReference 來創(chuàng)建虛引用。

Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
PhantomReference<Object> pf = new PhantomReference<Object>(obj, queue);
obj = null;

引用隊(duì)列(ReferenceQueue):當(dāng)GC(垃圾回收線程)準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還僅有軟引用(或弱引用,或虛引用)指向它,就會(huì)在回收該對(duì)象之前,把這個(gè)軟引用(或弱引用,或虛引用)加入到與之關(guān)聯(lián)的引用隊(duì)列(ReferenceQueue)中。如果一個(gè)軟引用(或弱引用,或虛引用)對(duì)象本身在引用隊(duì)列中,就說明該引用對(duì)象所指向的對(duì)象被回收了。無實(shí)際的存儲(chǔ)結(jié)構(gòu),存儲(chǔ)邏輯依賴于內(nèi)部節(jié)點(diǎn)之間的關(guān)系來表達(dá)。

三、垃圾收集算法

1. 標(biāo)記 - 清除

在標(biāo)記階段,從根集合進(jìn)行掃描,會(huì)檢查每個(gè)對(duì)象是否為活動(dòng)對(duì)象,如果是活動(dòng)對(duì)象,則程序會(huì)在對(duì)象頭部打上標(biāo)記。

在清除階段,會(huì)進(jìn)行對(duì)象回收并取消標(biāo)志位,另外,還會(huì)判斷回收后的分塊與前一個(gè)空閑分塊是否連續(xù),若連續(xù),會(huì)合并這兩個(gè)分塊。回收對(duì)象就是把對(duì)象作為分塊,連接到被稱為 “空閑鏈表” 的單向鏈表,之后進(jìn)行分配時(shí)只需要遍歷這個(gè)空閑鏈表,就可以找到分塊。

在分配時(shí),程序會(huì)搜索空閑鏈表尋找空間大于等于新對(duì)象大小 size 的塊 block。如果它找到的塊等于 size,會(huì)直接返回這個(gè)分塊;如果找到的塊大于 size,會(huì)將塊分割成大小為 size 與 (block - size) 的兩部分,返回大小為 size 的分塊,并把大小為 (block - size) 的塊返回給空閑鏈表。

不足:

  • 標(biāo)記和清除過程效率都不高;
  • 會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導(dǎo)致無法給大對(duì)象分配內(nèi)存。

2. 標(biāo)記 - 整理

讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。

優(yōu)點(diǎn):

  • 不會(huì)產(chǎn)生內(nèi)存碎片

不足:

  • 需要移動(dòng)大量對(duì)象,處理效率比較低。

3. 復(fù)制

將內(nèi)存劃分為大小相等的兩塊,每次只使用其中一塊,當(dāng)這一塊內(nèi)存用完了就將還存活的對(duì)象復(fù)制到另一塊上面,然后再把使用過的內(nèi)存空間進(jìn)行一次清理。

主要不足是只使用了內(nèi)存的一半。

現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集算法回收新生代,但是并不是劃分為大小相等的兩塊,而是一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一塊 Survivor。在回收時(shí),將 Eden 和 Survivor 中還存活著的對(duì)象全部復(fù)制到另一塊 Survivor 上,最后清理 Eden 和使用過的那一塊 Survivor。

HotSpot 虛擬機(jī)的 Eden 和 Survivor 大小比例默認(rèn)為 8:1,保證了內(nèi)存的利用率達(dá)到 90%。如果每次回收有多于 10% 的對(duì)象存活,那么一塊 Survivor 就不夠用了,此時(shí)需要依賴于老年代進(jìn)行空間分配擔(dān)保,也就是借用老年代的空間存儲(chǔ)放不下的對(duì)象。

4. 分代收集

Stop-the-World

  • JVM由于要執(zhí)行GC而停止了應(yīng)用程序的執(zhí)行;
  • 任何一種GC算法中都會(huì)發(fā)生;
  • 多數(shù)GC優(yōu)化通過減少Stop-the-world發(fā)生的時(shí)間來提升程序性能。

Safepoint

分析過程中對(duì)象引用關(guān)系不會(huì)發(fā)生變化的點(diǎn);
產(chǎn)生Safepoint的地方:方法調(diào)用;循環(huán)跳轉(zhuǎn);異常跳轉(zhuǎn)等
現(xiàn)在的商業(yè)虛擬機(jī)采用分代收集算法,它根據(jù)對(duì)象存活周期將內(nèi)存劃分為幾塊,不同塊采用適當(dāng)?shù)氖占惴ā?

一般將堆分為新生代和老年代。

  • 新生代使用:復(fù)制算法
  • 老年代使用:標(biāo)記 - 清除 或者 標(biāo)記 - 整理 算法

四、垃圾收集器

以上是 HotSpot 虛擬機(jī)中的 7 個(gè)垃圾收集器,連線表示垃圾收集器可以配合使用。

  • 單線程與多線程:單線程指的是垃圾收集器只使用一個(gè)線程,而多線程使用多個(gè)線程;
  • 串行與并行:串行指的是垃圾收集器與用戶程序交替執(zhí)行,這意味著在執(zhí)行垃圾收集的時(shí)候需要停頓用戶程序;并行指的是垃圾收集器和用戶程序同時(shí)執(zhí)行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式執(zhí)行。

1. Serial 收集器(-XX:+UseSerialGC)

Serial 翻譯為串行,也就是說它以串行的方式執(zhí)行。

它是單線程的收集器,只會(huì)使用一個(gè)線程進(jìn)行垃圾收集工作。

它的優(yōu)點(diǎn)是簡單高效,在單個(gè) CPU 環(huán)境下,由于沒有線程交互的開銷,因此擁有最高的單線程收集效率。

它是 Client 場景下的默認(rèn)新生代收集器,因?yàn)樵谠搱鼍跋聝?nèi)存一般來說不會(huì)很大。它收集一兩百兆垃圾的停頓時(shí)間可以控制在一百多毫秒以內(nèi),只要不是太頻繁,這點(diǎn)停頓時(shí)間是可以接受的。

2. ParNew 收集器(-XX:+UseParNewGC)

它是 Serial 收集器的多線程版本。

它是 Server 場景下默認(rèn)的新生代收集器,除了性能原因外,主要是因?yàn)槌?Serial 收集器,只有它能與 CMS 收集器配合使用。

3. Parallel Scavenge 收集器(-XX:+UseParallelGC)

與 ParNew 一樣是多線程收集器。

其它收集器目標(biāo)是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而它的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量,因此它被稱為“吞吐量優(yōu)先”收集器。這里的吞吐量指 CPU 用于運(yùn)行用戶程序的時(shí)間占總時(shí)間的比值。

停頓時(shí)間越短就越適合需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗(yàn)。而高吞吐量則可以高效率地利用 CPU 時(shí)間,盡快完成程序的運(yùn)算任務(wù),適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。

縮短停頓時(shí)間是以犧牲吞吐量和新生代空間來換取的:新生代空間變小,垃圾回收變得頻繁,導(dǎo)致吞吐量下降。

可以通過一個(gè)開關(guān)參數(shù)打開 GC 自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics),就不需要手工指定新生代的大?。?Xmn)、Eden 和 Survivor 區(qū)的比例、晉升老年代對(duì)象年齡等細(xì)節(jié)參數(shù)了。虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者大的吞吐量。

4. Serial Old 收集器(-XX:+UseSerialOldGC)

是 Serial 收集器的老年代版本,也是給 Client 場景下的虛擬機(jī)使用。如果用在 Server 場景下,它有兩大用途:

  • 在 JDK 1.5 以及之前版本(Parallel Old 誕生以前)中與 Parallel Scavenge 收集器搭配使用。
  • 作為 CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生 Concurrent Mode Failure 時(shí)使用。

5. Parallel Old 收集器(-XX:+UseParallelOldGC)

是 Parallel Scavenge 收集器的老年代版本。

在注重吞吐量以及 CPU 資源敏感的場合,都可以優(yōu)先考慮 Parallel Scavenge 加 Parallel Old 收集器。

6. CMS 收集器(-XX:+UseConcMarkSweepGC)

CMS(Concurrent Mark Sweep),Mark Sweep 指的是標(biāo)記 - 清除算法。

分為以下六個(gè)流程:

  • 初始標(biāo)記:僅僅只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對(duì)象,速度很快,需要停頓。
  • 并發(fā)標(biāo)記:進(jìn)行 GC Roots Tracing 的過程,它在整個(gè)回收過程中耗時(shí)最長,不需要停頓。
  • 并發(fā)預(yù)清理:查找執(zhí)行并發(fā)標(biāo)記階段從年輕代晉升到老年代的對(duì)象
  • 重新標(biāo)記:為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,需要停頓。
  • 并發(fā)清除:清理垃圾對(duì)象,不需要停頓。
  • 并發(fā)重置:重置CMS收集器的數(shù)據(jù)結(jié)構(gòu),等待下一次垃圾回收。
    在整個(gè)過程中耗時(shí)最長的并發(fā)標(biāo)記和并發(fā)清除過程中,收集器線程都可以與用戶線程一起工作,不需要進(jìn)行停頓。

具有以下缺點(diǎn):

  • 吞吐量低:低停頓時(shí)間是以犧牲吞吐量為代價(jià)的,導(dǎo)致 CPU 利用率不夠高。
  • 無法處理浮動(dòng)垃圾,可能出現(xiàn) Concurrent Mode Failure。浮動(dòng)垃圾是指并發(fā)清除階段由于用戶線程繼續(xù)運(yùn)行而產(chǎn)生的垃圾,這部分垃圾只能到下一次 GC 時(shí)才能進(jìn)行回收。由于浮動(dòng)垃圾的存在,因此需要預(yù)留出一部分內(nèi)存,意味著 CMS 收集不能像其它收集器那樣等待老年代快滿的時(shí)候再回收。如果預(yù)留的內(nèi)存不夠存放浮動(dòng)垃圾,就會(huì)出現(xiàn) Concurrent Mode Failure,這時(shí)虛擬機(jī)將臨時(shí)啟用 Serial Old 來替代 CMS。
  • 標(biāo)記 - 清除算法導(dǎo)致的空間碎片,往往出現(xiàn)老年代空間剩余,但無法找到足夠大連續(xù)空間來分配當(dāng)前對(duì)象,不得不提前觸發(fā)一次 Full GC。

7. G1 收集器(-XX:+UseG1GC)

G1(Garbage-First),它是一款面向服務(wù)端應(yīng)用的垃圾收集器,在多 CPU 和大內(nèi)存的場景下有很好的性能。HotSpot 開發(fā)團(tuán)隊(duì)賦予它的使命是未來可以替換掉 CMS 收集器。

堆被分為新生代和老年代,其它收集器進(jìn)行收集的范圍都是整個(gè)新生代或者老年代,而 G1 可以直接對(duì)新生代和老年代一起回收。

G1 把堆劃分成多個(gè)大小相等的獨(dú)立區(qū)域(Region),新生代和老年代不再物理隔離。

通過引入 Region 的概念,從而將原來的一整塊內(nèi)存空間劃分成多個(gè)的小空間,使得每個(gè)小空間可以單獨(dú)進(jìn)行垃圾回收。這種劃分方法帶來了很大的靈活性,使得可預(yù)測的停頓時(shí)間模型成為可能。通過記錄每個(gè) Region 垃圾回收時(shí)間以及回收所獲得的空間(這兩個(gè)值是通過過去回收的經(jīng)驗(yàn)獲得),并維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值大的 Region。

每個(gè) Region 都有一個(gè) Remembered Set,用來記錄該 Region 對(duì)象的引用對(duì)象所在的 Region。通過使用 Remembered Set,在做可達(dá)性分析的時(shí)候就可以避免全堆掃描。

如果不計(jì)算維護(hù) Remembered Set 的操作,G1 收集器的運(yùn)作大致可劃分為以下幾個(gè)步驟:

  • 初始標(biāo)記
  • 并發(fā)標(biāo)記
  • 最終標(biāo)記:為了修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄,虛擬機(jī)將這段時(shí)間對(duì)象變化記錄在線程的 Remembered Set Logs 里面,最終標(biāo)記階段需要把 Remembered Set Logs 的數(shù)據(jù)合并到 Remembered Set 中。這階段需要停頓線程,但是可并行執(zhí)行。
  • 篩選回收:首先對(duì)各個(gè) Region 中的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的 GC 停頓時(shí)間來制定回收計(jì)劃。此階段其實(shí)也可以做到與用戶程序一起并發(fā)執(zhí)行,但是因?yàn)橹换厥找徊糠?Region,時(shí)間是用戶可控制的,而且停頓用戶線程將大幅度提高收集效率。

具備如下特點(diǎn):

  • 并行和并發(fā)
  • 分代收集
  • 空間整合:整體來看是基于“標(biāo)記 - 整理”算法實(shí)現(xiàn)的收集器,從局部(兩個(gè) Region 之間)上來看是基于“復(fù)制”算法實(shí)現(xiàn)的,這意味著運(yùn)行期間不會(huì)產(chǎn)生內(nèi)存空間碎片。
  • 可預(yù)測的停頓:能讓使用者明確指定在一個(gè)長度為 M 毫秒的時(shí)間片段內(nèi),消耗在 GC 上的時(shí)間不得超過 N 毫秒。

五、降低停頓時(shí)間

1. 使用 CMS 收集器
CMS 收集器進(jìn)行垃圾回收,有 4 個(gè)步驟:

  • 初始標(biāo)記
  • 并發(fā)標(biāo)記
  • 重新標(biāo)記
  • 并發(fā)清除

其中初始標(biāo)記和重新標(biāo)記需要 “stop the world”,但耗時(shí)時(shí)間最長的并發(fā)標(biāo)記、并發(fā)清除過程中,GC 線程都可與用戶線程一起工作。整體上說,CMS 和用戶線程是并行的。

2. 增量算法

基本思路:若一次性將所有垃圾進(jìn)行處理,會(huì)造成系統(tǒng)長時(shí)間的停頓,則就讓 GC 線程與用戶線程交替執(zhí)行。每次 GC 線程只收集一小塊區(qū)域的內(nèi)存空間,接著切換到用戶線程,重復(fù)幾次,直至 GC 完成。

問題:存在線程切換和上下文切換,造成系統(tǒng)吞吐量下降。

名稱欄目:詳解Java虛擬機(jī)(第⑤篇)——垃圾收集-創(chuàng)新互聯(lián)
新聞來源:http://www.muchs.cn/article14/epdde.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序虛擬主機(jī)、網(wǎng)站排名、微信公眾號(hào)、靜態(tài)網(wǎng)站、自適應(yīng)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作