深入了解JVM(Java虛擬機(jī))內(nèi)存結(jié)構(gòu)
JVM內(nèi)存結(jié)構(gòu)
Java虛擬機(jī)的內(nèi)存結(jié)構(gòu)分為5個(gè)部分:
- 程序計(jì)數(shù)器
- Java 虛擬機(jī)棧
- 本地方法棧
- 堆
- 方法區(qū)

JDK1.7與1.8的區(qū)別:
- 1.7中有有一個(gè)永久代,存儲的是類信息、靜態(tài)變量、常量、編譯后的代碼
- 1.8移除了永久代,把數(shù)據(jù)存儲到了本地內(nèi)存的元空間中,防止內(nèi)存溢出
程序計(jì)數(shù)器(PC寄存器)
程序計(jì)數(shù)器定義
線程私有的,每個(gè)線程一份,內(nèi)部保存的字節(jié)碼的行號。用于記錄正在執(zhí)行的字節(jié)碼指令的地址。
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,是當(dāng)前線程正在執(zhí)行的那條字節(jié)碼指令的地址。若當(dāng)前線程正在執(zhí)行的是一個(gè)本地方法,那么此時(shí)程序計(jì)數(shù)器為 Undefined 。
javap -v xx.class 打印堆棧大小,局部變量的數(shù)量和方法的參數(shù)。

程序計(jì)數(shù)器的作用
- 字節(jié)碼解釋器通過改變程序計(jì)數(shù)器來依次讀取指令,從而實(shí)現(xiàn)代碼的流程控制。
- 在多線程情況下,程序計(jì)數(shù)器記錄的是當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程切換回來時(shí),就知道上次線程執(zhí)行到哪了。
程序計(jì)數(shù)器的特點(diǎn)
- 是一塊較小的內(nèi)存空間。
- 線程私有,每條線程都有自己的程序計(jì)數(shù)器。
- 生命周期:隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而銷毀。
- 是唯一一個(gè)不會出現(xiàn)
OutOfMemoryError的內(nèi)存區(qū)域。
Java虛擬機(jī)棧
Java虛擬機(jī)棧的定義
Java 虛擬機(jī)棧是描述 Java 方法運(yùn)行過程的內(nèi)存模型。
Java Virtual machine Stacks (java 虛擬機(jī)棧)
- 每個(gè)線程運(yùn)行時(shí)所需要的內(nèi)存,稱為虛擬機(jī)棧,先進(jìn)后出
- 每個(gè)棧由多個(gè)棧幀(frame)組成,對應(yīng)著每次方法調(diào)用時(shí)所占用的內(nèi)存
- 每個(gè)線程只能有一個(gè)活動棧幀,對應(yīng)著當(dāng)前正在執(zhí)行的那個(gè)方法

垃圾回收是否涉及棧內(nèi)存?
垃圾回收主要指就是堆內(nèi)存,當(dāng)棧幀彈棧以后,內(nèi)存就會釋放
棧內(nèi)存分配越大越好嗎?
未必,默認(rèn)的棧內(nèi)存通常為1024k
棧幀過大會導(dǎo)致線程數(shù)變少,例如,機(jī)器總內(nèi)存為512m,目前能活動的線程數(shù)則為512個(gè),如果把棧內(nèi)存改為2048k,那么能活動的棧幀就會減半
方法內(nèi)的局部變量是否線程安全?
- 如果方法內(nèi)局部變量沒有逃離方法的作用范圍,它是線程安全的
- 如果是局部變量引用了對象,并逃離方法的作用范圍,需要考慮線程安全

虛擬機(jī)棧的組成
Java 虛擬機(jī)棧會為每一個(gè)即將運(yùn)行的 Java 方法創(chuàng)建一塊叫做“棧幀”的區(qū)域,用于存放該方法運(yùn)行過程中的一些信息,如:
- 局部變量表
- 操作數(shù)棧
- 動態(tài)鏈接
- 方法出口信息
- ......

壓棧出棧過程
當(dāng)方法運(yùn)行過程中需要?jiǎng)?chuàng)建局部變量時(shí),就將局部變量的值存入棧幀中的局部變量表中。
Java 虛擬機(jī)棧的棧頂?shù)臈钱?dāng)前正在執(zhí)行的活動棧,也就是當(dāng)前正在執(zhí)行的方法,PC 寄存器也會指向這個(gè)地址。只有這個(gè)活動的棧幀的本地變量可以被操作數(shù)棧使用,當(dāng)在這個(gè)棧幀中調(diào)用另一個(gè)方法,與之對應(yīng)的棧幀又會被創(chuàng)建,新創(chuàng)建的棧幀壓入棧頂,變?yōu)楫?dāng)前的活動棧幀。
方法結(jié)束后,當(dāng)前棧幀被移出,棧幀的返回值變成新的活動棧幀中操作數(shù)棧的一個(gè)操作數(shù)。如果沒有返回值,那么新的活動棧幀中操作數(shù)棧的操作數(shù)沒有變化。
由于 Java 虛擬機(jī)棧是與線程對應(yīng)的,數(shù)據(jù)不是線程共享的(也就是線程私有的),因此不用關(guān)心數(shù)據(jù)一致性問題,也不會存在同步鎖的問題。
局部變量表
定義為一個(gè)數(shù)字?jǐn)?shù)組,主要用于存儲方法參數(shù)、定義在方法體內(nèi)部的局部變量,數(shù)據(jù)類型包括各類基本數(shù)據(jù)類型,對象引用,以及 return address 類型。
局部變量表容量大小是在編譯期確定下來的。最基本的存儲單元是 slot,32 位占用一個(gè) slot,64 位類型(long 和 double)占用兩個(gè) slot。
對于 slot 的理解:
- JVM 虛擬機(jī)會為局部變量表中的每個(gè) slot 都分配一個(gè)訪問索引,通過這個(gè)索引即可成功訪問到局部變量表中指定的局部變量值。
- 如果當(dāng)前幀是由構(gòu)造方法或者實(shí)例方法創(chuàng)建的,那么該對象引用 this,會存放在 index 為 0 的 slot 處,其余的參數(shù)表順序繼續(xù)排列。
- 棧幀中的局部變量表中的槽位是可以重復(fù)的,如果一個(gè)局部變量過了其作用域,那么其作用域之后申明的新的局部變量就有可能會復(fù)用過期局部變量的槽位,從而達(dá)到節(jié)省資源的目的。
在棧幀中,與性能調(diào)優(yōu)關(guān)系最密切的部分,就是局部變量表,方法執(zhí)行時(shí),虛擬機(jī)使用局部變量表完成方法的傳遞局部變量表中的變量也是重要的垃圾回收根節(jié)點(diǎn),只要被局部變量表中直接或間接引用的對象都不會被回收。
操作數(shù)棧
- 棧頂緩存技術(shù):由于操作數(shù)是存儲在內(nèi)存中,頻繁的進(jìn)行內(nèi)存讀寫操作影響執(zhí)行速度,將棧頂元素全部緩存到物理 CPU 的寄存器中,以此降低對內(nèi)存的讀寫次數(shù),提升執(zhí)行引擎的執(zhí)行效率。
- 每一個(gè)操作數(shù)棧會擁有一個(gè)明確的棧深度,用于存儲數(shù)值,最大深度在編譯期就定義好。32bit 類型占用一個(gè)棧單位深度,64bit 類型占用兩個(gè)棧單位深度操作數(shù)棧。
- 并非采用訪問索引方式進(jìn)行數(shù)據(jù)訪問,而是只能通過標(biāo)準(zhǔn)的入棧、出棧操作完成一次數(shù)據(jù)訪問。
本地方法棧
本地方法棧的定義
本地方法棧是為 JVM 運(yùn)行 Native 方法準(zhǔn)備的空間,由于很多 Native 方法都是用 C 語言實(shí)現(xiàn)的,所以它通常又叫 C 棧。它與 Java 虛擬機(jī)棧實(shí)現(xiàn)的功能類似,只不過本地方法棧是描述本地方法運(yùn)行過程的內(nèi)存模型。
棧幀變化過程
本地方法被執(zhí)行時(shí),在本地方法棧也會創(chuàng)建一塊棧幀,用于存放該方法的局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口信息等。
方法執(zhí)行結(jié)束后,相應(yīng)的棧幀也會出棧,并釋放內(nèi)存空間。也會拋出 StackOverFlowError 和 OutOfMemoryError 異常。
如果 Java 虛擬機(jī)本身不支持 Native 方法,或是本身不依賴于傳統(tǒng)棧,那么可以不提供本地方法棧。如果支持本地方法棧,那么這個(gè)棧一般會在線程創(chuàng)建的時(shí)候按線程分配。
Java堆
堆的定義
線程共享的區(qū)域:主要用來保存對象實(shí)例,數(shù)組等,當(dāng)堆中沒有內(nèi)存空間可分配給實(shí)例,也無法再擴(kuò)展時(shí),則拋出OutOfMemoryError異常。

堆是用來存放對象的內(nèi)存空間, 幾乎 所有的對象都存儲在堆中。

堆的特點(diǎn)
- 線程共享,整個(gè) Java 虛擬機(jī)只有一個(gè)堆,所有的線程都訪問同一個(gè)堆。而程序計(jì)數(shù)器、Java 虛擬機(jī)棧、本地方法棧都是一個(gè)線程對應(yīng)一個(gè)。
- 在虛擬機(jī)啟動時(shí)創(chuàng)建。
- 是垃圾回收的主要場所。
- 堆可分為新生代(Eden 區(qū):
From Survior,To Survivor)、老年代。 - Java 虛擬機(jī)規(guī)范規(guī)定,堆可以處于物理上不連續(xù)的內(nèi)存空間中,但在邏輯上它應(yīng)該被視為連續(xù)的。
- 關(guān)于 Survivor s0,s1 區(qū): 復(fù)制之后有交換,誰空誰是 to。
不同的區(qū)域存放不同生命周期的對象,這樣可以根據(jù)不同的區(qū)域使用不同的垃圾回收算法,更具有針對性。
堆的大小既可以固定也可以擴(kuò)展,但對于主流的虛擬機(jī),堆的大小是可擴(kuò)展的,因此當(dāng)線程請求分配內(nèi)存,但堆已滿,且內(nèi)存已無法再擴(kuò)展時(shí),就拋出 OutOfMemoryError 異常。
Java 堆所使用的內(nèi)存不需要保證是連續(xù)的。而由于堆是被所有線程共享的,所以對它的訪問需要注意同步問題,方法和對應(yīng)的屬性都需要保證一致性。
新生代與老年代
- 老年代比新生代生命周期長。
- 新生代與老年代空間默認(rèn)比例
1:2:JVM 調(diào)參數(shù),XX:NewRatio=2,表示新生代占 1,老年代占 2,新生代占整個(gè)堆的 1/3。 - HotSpot 中,Eden 空間和另外兩個(gè) Survivor 空間缺省所占的比例是:
8:1:1。 - 幾乎所有的 Java 對象都是在 Eden 區(qū)被 new 出來的,Eden 放不了的大對象,就直接進(jìn)入老年代了。
對象分配過程
- new 的對象先放在 Eden 區(qū),大小有限制
- 如果創(chuàng)建新對象時(shí),Eden 空間填滿了,就會觸發(fā) Minor GC,將 Eden 不再被其他對象引用的對象進(jìn)行銷毀,再加載新的對象放到 Eden 區(qū),特別注意的是 Survivor 區(qū)滿了是不會觸發(fā) Minor GC 的,而是 Eden 空間填滿了,Minor GC 才順便清理 Survivor 區(qū)
- 將 Eden 中剩余的對象移到 Survivor0 區(qū)
- 再次觸發(fā)垃圾回收,此時(shí)上次 Survivor 下來的,放在 Survivor0 區(qū)的,如果沒有回收,就會放到 Survivor1 區(qū)
- 再次經(jīng)歷垃圾回收,又會將幸存者重新放回 Survivor0 區(qū),依次類推
- 默認(rèn)是 15 次的循環(huán),超過 15 次,則會將幸存者區(qū)幸存下來的轉(zhuǎn)去老年區(qū) jvm 參數(shù)設(shè)置次數(shù) : -XX:MaxTenuringThreshold=N 進(jìn)行設(shè)置
- 頻繁在新生區(qū)收集,很少在養(yǎng)老區(qū)收集,幾乎不在永久區(qū)/元空間搜集
Full GC /Major GC 觸發(fā)條件
- 顯示調(diào)用
System.gc(),老年代的空間不夠,方法區(qū)的空間不夠等都會觸發(fā) Full GC,同時(shí)對新生代和老年代回收,F(xiàn)Ull GC 的 STW 的時(shí)間最長,應(yīng)該要避免 - 在出現(xiàn) Major GC 之前,會先觸發(fā) Minor GC,如果老年代的空間還是不夠就會觸發(fā) Major GC,STW 的時(shí)間長于 Minor GC
逃逸分析
標(biāo)量替換
- 標(biāo)量不可在分解的量,java 的基本數(shù)據(jù)類型就是標(biāo)量,標(biāo)量的對立就是可以被進(jìn)一步分解的量,而這種量稱之為聚合量。而在 JAVA 中對象就是可以被進(jìn)一步分解的聚合量
- 替換過程,通過逃逸分析確定該對象不會被外部訪問,并且對象可以被進(jìn)一步分解時(shí),JVM 不會創(chuàng)建該對象,而會將該對象成員變量分解若干個(gè)被這個(gè)方法使用的成員變量所代替。這些代替的成員變量在棧幀或寄存器上分配空間。
- 對象和數(shù)組并非都是在堆上分配內(nèi)存的
- 《深入理解 Java 虛擬機(jī)中》關(guān)于 Java 堆內(nèi)存有這樣一段描述:隨著 JIT 編譯期的發(fā)展與逃逸分析技術(shù)逐漸成熟,
棧上分配,標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些變化,所有的對象都分配到堆上也漸漸變得不那么"絕對"了。 - 這是一種可以有效減少 Java 內(nèi)存堆分配壓力的分析算法,通過逃逸分析,Java Hotspot 編譯器能夠分析出一個(gè)新的對象的引用的使用范圍從而決定是否要將這個(gè)對象分配到堆上。
- 當(dāng)一個(gè)對象在方法中被定義后,它可能被外部方法所引用,如作為調(diào)用參數(shù)傳遞到其他地方中,稱為
方法逃逸。 - 再如賦值給類變量或可以在其他線程中訪問的實(shí)例變量,稱為
線程逃逸 - 使用逃逸分析,編譯器可以對代碼做如下優(yōu)化:
- 同步省略:如果一個(gè)對象被發(fā)現(xiàn)只能從一個(gè)線程被訪問到,那么對于這個(gè)對象的操作可以不考慮同步。
- 將堆分配轉(zhuǎn)化為棧分配:如果一個(gè)對象在子程序中被分配,要使指向該對象的指針永遠(yuǎn)不會逃逸,對象可能是棧分配的候選,而不是堆分配。
- 分離對象或標(biāo)量替換:有的對象可能不需要作為一個(gè)連續(xù)的內(nèi)存結(jié)構(gòu)存在也可以被訪問到,那么對象的部分(或全部)可以不存儲在內(nèi)存,而是存儲在 CPU 寄存器中。
public?static?StringBuffer?createStringBuffer(String?s1,?String?s2)?{
????StringBuffer?s?=?new?StringBuffer();
????s.append(s1);
????s.append(s2);
????return?s;
}s 是一個(gè)方法內(nèi)部變量,上邊的代碼中直接將 s 返回,這個(gè) StringBuffer 的對象有可能被其他方法所改變,導(dǎo)致它的作用域就不只是在方法內(nèi)部,即使它是一個(gè)局部變量,但還是逃逸到了方法外部,稱為 方法逃逸 。
還有可能被外部線程訪問到,譬如賦值給類變量或可以在其他線程中訪問的實(shí)例變量,稱為 線程逃逸 。
- 在編譯期間,如果 JIT 經(jīng)過逃逸分析,發(fā)現(xiàn)有些對象沒有逃逸出方法,那么有可能堆內(nèi)存分配會被優(yōu)化成棧內(nèi)存分配。
- jvm 參數(shù)設(shè)置,
-XX:+DoEscapeAnalysis:開啟逃逸分析 ,-XX:-DoEscapeAnalysis: 關(guān)閉逃逸分析 - 從 jdk 1.7 開始已經(jīng)默認(rèn)開始逃逸分析。
TLAB
- TLAB 的全稱是 Thread Local Allocation Buffer,即線程本地分配緩存區(qū),是屬于 Eden 區(qū)的,這是一個(gè)線程專用的內(nèi)存分配區(qū)域,線程私有,默認(rèn)開啟的(當(dāng)然也不是絕對的,也要看哪種類型的虛擬機(jī))
- 堆是全局共享的,在同一時(shí)間,可能會有多個(gè)線程在堆上申請空間,但每次的對象分配需要同步的進(jìn)行(虛擬機(jī)采用 CAS 配上失敗重試的方式保證更新操作的原子性)但是效率卻有點(diǎn)下降
- 所以用 TLAB 來避免多線程沖突,在給對象分配內(nèi)存時(shí),每個(gè)線程使用自己的 TLAB,這樣可以使得線程同步,提高了對象分配的效率
- 當(dāng)然并不是所有的對象都可以在 TLAB 中分配內(nèi)存成功,如果失敗了就會使用加鎖的機(jī)制來保持操作的原子性
-XX:+UseTLAB使用 TLAB,-XX:+TLABSize設(shè)置 TLAB 大小
方法區(qū)
Java 虛擬機(jī)規(guī)范中定義方法區(qū)是堆的一個(gè)邏輯部分。方法區(qū)存放以下信息:
- 已經(jīng)被虛擬機(jī)加載的類信息
- 常量
- 靜態(tài)變量
- 即時(shí)編譯器編譯后的代碼
方法區(qū)的簡單理解:
- 方法區(qū)(Method Area)是各個(gè)線程共享的內(nèi)存區(qū)域
- 主要存儲類的信息、運(yùn)行時(shí)常量池
- 虛擬機(jī)啟動的時(shí)候創(chuàng)建,關(guān)閉虛擬機(jī)時(shí)釋放
- 如果方法區(qū)域中的內(nèi)存無法滿足分配請求,則會拋出OutOfMemoryError: Metaspace

方法區(qū)的特點(diǎn)
- 線程共享。 方法區(qū)是堆的一個(gè)邏輯部分,因此和堆一樣,都是線程共享的。整個(gè)虛擬機(jī)中只有一個(gè)方法區(qū)。
- 永久代。 方法區(qū)中的信息一般需要長期存在,而且它又是堆的邏輯分區(qū),因此用堆的劃分方法,把方法區(qū)稱為“永久代”。
- 內(nèi)存回收效率低。 方法區(qū)中的信息一般需要長期存在,回收一遍之后可能只有少量信息無效。主要回收目標(biāo)是:對常量池的回收;對類型的卸載。
- Java 虛擬機(jī)規(guī)范對方法區(qū)的要求比較寬松。 和堆一樣,允許固定大小,也允許動態(tài)擴(kuò)展,還允許不實(shí)現(xiàn)垃圾回收。
運(yùn)行時(shí)常量池
常量池可以看作是一張表,虛擬機(jī)指令根據(jù)這張常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量等信息。
常量池是 *.class 文件中的,當(dāng)該類被加載,它的常量池信息就會放入運(yùn)行時(shí)常量池,并把里面的符號地址變?yōu)檎鎸?shí)地址
查看字節(jié)碼結(jié)構(gòu)(類的基本信息、常量池、方法定義)
javap -v Application.class

直接內(nèi)存
直接內(nèi)存:并不屬于JVM中的內(nèi)存結(jié)構(gòu),不由JVM進(jìn)行管理。是虛擬機(jī)的系統(tǒng)內(nèi)存,常見于 NIO 操作時(shí),用于數(shù)據(jù)緩沖區(qū),它分配回收成本較高,但讀寫性能高。
直接內(nèi)存的大小不受 Java 虛擬機(jī)控制,但既然是內(nèi)存,當(dāng)內(nèi)存不足時(shí)就會拋出 OutOfMemoryError 異常。
常規(guī)IO的數(shù)據(jù)拷貝流程

NIO數(shù)據(jù)拷貝流程

直接內(nèi)存與堆內(nèi)存比較
- 直接內(nèi)存申請空間耗費(fèi)更高的性能
- 直接內(nèi)存讀取 IO 的性能要優(yōu)于普通的堆內(nèi)存
- 直接內(nèi)存作用鏈: 本地 IO -> 直接內(nèi)存 -> 本地 IO
- 堆內(nèi)存作用鏈:本地 IO -> 直接內(nèi)存 -> 非直接內(nèi)存 -> 直接內(nèi)存 -> 本地 IO
服務(wù)器管理員在配置虛擬機(jī)參數(shù)時(shí),會根據(jù)實(shí)際內(nèi)存設(shè)置 -Xmx 等參數(shù)信息,但經(jīng)常忽略直接內(nèi)存,使得各個(gè)內(nèi)存區(qū)域總和大于物理內(nèi)存限制,從而導(dǎo)致動態(tài)擴(kuò)展時(shí)出現(xiàn) OutOfMemoryError 異常。
以上就是深入了解JVM(Java虛擬機(jī))內(nèi)存結(jié)構(gòu)的詳細(xì)內(nèi)容,更多關(guān)于JVM內(nèi)存結(jié)構(gòu)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Netty分布式NioEventLoop任務(wù)隊(duì)列執(zhí)行源碼分析
這篇文章主要為大家介紹了Netty分布式NioEventLoop任務(wù)隊(duì)列執(zhí)行源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
Maven項(xiàng)src/main/java目錄下配置文件無法被導(dǎo)出或者生效的問題和處理方案
這篇文章主要介紹了Maven項(xiàng)src/main/java目錄下配置文件無法被導(dǎo)出或者生效的問題和處理方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Spring @Profile注解實(shí)現(xiàn)多環(huán)境配置
這篇文章主要介紹了Spring @Profile注解實(shí)現(xiàn)多環(huán)境配置,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
Java中final關(guān)鍵字的使用與注意總結(jié)
這篇文章主要給大家介紹了關(guān)于Java中final關(guān)鍵字的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
詳解Spring Boot 定制HTTP消息轉(zhuǎn)換器
本篇文章主要介紹了詳解Spring Boot 定制HTTP消息轉(zhuǎn)換器,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
IDEA導(dǎo)入外部項(xiàng)目報(bào)Error:java: 無效的目標(biāo)發(fā)行版: 11的解決方法
這篇文章主要介紹了IDEA導(dǎo)入外部項(xiàng)目報(bào)Error:java: 無效的目標(biāo)發(fā)行版: 11,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
Java通過匿名類來實(shí)現(xiàn)回調(diào)函數(shù)實(shí)例總結(jié)
這篇文章主要介紹了Java通過匿名類來實(shí)現(xiàn)回調(diào)函數(shù)的例子,回調(diào)函數(shù)就是一種函數(shù)簽名(若干個(gè)輸入?yún)?shù)、一個(gè)輸出參數(shù))的規(guī)范,java雖不存在函數(shù)聲明,但是java可以用接口來強(qiáng)制規(guī)范。具體操作步驟大家可查看下文的詳細(xì)講解,感興趣的小伙伴們可以參考一下。2017-08-08

