JVM優(yōu)化原理及基本知識(shí)

1败富、我們?yōu)槭裁匆獙?duì)jvm做優(yōu)化讥此?

在本地開發(fā)環(huán)境中我們很少會(huì)遇到需要對(duì)jvm進(jìn)行優(yōu)化的需求拢锹,但是到了生產(chǎn)環(huán)境,我們可能將有下面的需求:

  • 運(yùn)行的應(yīng)用“卡住了”萄喳,日志不輸出卒稳,程序沒有反應(yīng)
  • 服務(wù)器的CPU負(fù)載突然升高
  • 在多線程應(yīng)用下,如何分配線程的數(shù)量他巨?
  • ……

我們不僅要讓程序能跑起來充坑,而且是可以跑的更快!可以分析解決在生產(chǎn)環(huán)境中所遇到的各種“棘手”的問題染突。

說明:使用的jdk版本為1.8捻爷。

2、jvm的運(yùn)行參數(shù)

在jvm中有很多的參數(shù)可以進(jìn)行設(shè)置份企,這樣可以讓jvm在各種環(huán)境中都能夠高效的運(yùn)行也榄。絕大部分的參數(shù)保持默認(rèn)即可。

2.1司志、三種參數(shù)類型

jvm的參數(shù)類型分為三類手蝎,分別是:

  • 標(biāo)準(zhǔn)參數(shù)
    • -help
    • -version
  • -X參數(shù) (非標(biāo)準(zhǔn)參數(shù))
    • -Xint
    • -Xcomp
  • -XX參數(shù)(使用率較高)
    • -XX:newSize
    • -XX:+UseSerialGC

2.2榕莺、標(biāo)準(zhǔn)參數(shù)

jvm的標(biāo)準(zhǔn)參數(shù),一般都是很穩(wěn)定的棵介,在未來的JVM版本中不會(huì)改變钉鸯,可以使用java -help檢索出所有的標(biāo)準(zhǔn)參數(shù)。

[root@node01 ~]# java -help
用法: java [-options] class [args...]
           (執(zhí)行類)
   或  java [-options] -jar jarfile [args...]
           (執(zhí)行 jar 文件)
其中選項(xiàng)包括:
    -d32      使用 32 位數(shù)據(jù)模型 (如果可用)
    -d64      使用 64 位數(shù)據(jù)模型 (如果可用)
    -server   選擇 "server" VM
                  默認(rèn) VM 是 server,
                  因?yàn)槟窃诜?wù)器類計(jì)算機(jī)上運(yùn)行邮辽。


    -cp <目錄和 zip/jar 文件的類搜索路徑>
    -classpath <目錄和 zip/jar 文件的類搜索路徑>
                  用 : 分隔的目錄, JAR 檔案
                  和 ZIP 檔案列表, 用于搜索類文件唠雕。
    -D<名稱>=<值>
                  設(shè)置系統(tǒng)屬性
    -verbose:[class|gc|jni]
                  啟用詳細(xì)輸出
    -version      輸出產(chǎn)品版本并退出
    -version:<值>
                  警告: 此功能已過時(shí), 將在
                  未來發(fā)行版中刪除。
                  需要指定的版本才能運(yùn)行
    -showversion  輸出產(chǎn)品版本并繼續(xù)
    -jre-restrict-search | -no-jre-restrict-search
                  警告: 此功能已過時(shí), 將在
                  未來發(fā)行版中刪除吨述。
                  在版本搜索中包括/排除用戶專用 JRE
    -? -help      輸出此幫助消息
    -X            輸出非標(biāo)準(zhǔn)選項(xiàng)的幫助
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  按指定的粒度啟用斷言
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  禁用具有指定粒度的斷言
    -esa | -enablesystemassertions
                  啟用系統(tǒng)斷言
    -dsa | -disablesystemassertions
                  禁用系統(tǒng)斷言
    -agentlib:<libname>[=<選項(xiàng)>]
                  加載本機(jī)代理庫 <libname>, 例如 -agentlib:hprof
                  另請(qǐng)參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help
    -agentpath:<pathname>[=<選項(xiàng)>]
                  按完整路徑名加載本機(jī)代理庫
    -javaagent:<jarpath>[=<選項(xiàng)>]
                  加載 Java 編程語言代理, 請(qǐng)參閱 java.lang.instrument
    -splash:<imagepath>
                  使用指定的圖像顯示啟動(dòng)屏幕

2.2.1岩睁、實(shí)戰(zhàn)

實(shí)戰(zhàn)1:查看jvm版本

[root@node01 ~]# java -version
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

# -showversion參數(shù)是表示,先打印版本信息揣云,再執(zhí)行后面的命令捕儒,在調(diào)試時(shí)非常有用,后面會(huì)使用到邓夕。

實(shí)戰(zhàn)2:通過-D設(shè)置系統(tǒng)屬性參數(shù)


public class TestJVM {

    public static void main(String[] args) {
        String str = System.getProperty("str");
        if (str == null) {
            System.out.println("hello world");
        } else {
            System.out.println(str);
        }
    }
}

進(jìn)行編譯刘莹、測試:

#編譯
[root@node01 test]# javac TestJVM.java

#測試
[root@node01 test]# java TestJVM
hello world
[root@node01 test]# java -Dstr=123 TestJVM
123

2.2.2、-server與-client參數(shù)

可以通過-server或-client設(shè)置jvm的運(yùn)行參數(shù)焚刚。

  • 它們的區(qū)別是Server VM的初始堆空間會(huì)大一些点弯,默認(rèn)使用的是并行垃圾回收器,啟動(dòng)慢運(yùn)行快矿咕。
  • Client VM相對(duì)來講會(huì)保守一些抢肛,初始堆空間會(huì)小一些,使用串行的垃圾回收器碳柱,它的目標(biāo)是為了讓JVM的啟動(dòng)速度更快捡絮,但運(yùn)行速度會(huì)比Serverm模式慢些。
  • JVM在啟動(dòng)的時(shí)候會(huì)根據(jù)硬件和操作系統(tǒng)自動(dòng)選擇使用Server還是Client類型的JVM莲镣。
  • 32位操作系統(tǒng)
    • 如果是Windows系統(tǒng)锦援,不論硬件配置如何,都默認(rèn)使用Client類型的JVM剥悟。
    • 如果是其他操作系統(tǒng)上灵寺,機(jī)器配置有2GB以上的內(nèi)存同時(shí)有2個(gè)以上CPU的話默認(rèn)使用server模式,否則使用client模式区岗。
  • 64位操作系統(tǒng)
    • 只有server類型略板,不支持client類型。

測試:

[root@node01 test]# java -client -showversion TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

hello world

[root@node01 test]# java -server -showversion TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

hello world

#由于機(jī)器是64位系統(tǒng)慈缔,所以不支持client模式

2.3叮称、-X參數(shù)

jvm的-X參數(shù)是非標(biāo)準(zhǔn)參數(shù),在不同版本的jvm中,參數(shù)可能會(huì)有所不同瓤檐,可以通過java -X查看非標(biāo)準(zhǔn)參數(shù)赂韵。

[root@node01 test]# java -X
    -Xmixed           混合模式執(zhí)行 (默認(rèn))
    -Xint             僅解釋模式執(zhí)行
    -Xbootclasspath:<用 : 分隔的目錄和 zip/jar 文件>
                      設(shè)置搜索路徑以引導(dǎo)類和資源
    -Xbootclasspath/a:<用 : 分隔的目錄和 zip/jar 文件>
                      附加在引導(dǎo)類路徑末尾
    -Xbootclasspath/p:<用 : 分隔的目錄和 zip/jar 文件>
                      置于引導(dǎo)類路徑之前
    -Xdiag            顯示附加診斷消息
    -Xnoclassgc       禁用類垃圾收集
    -Xincgc           啟用增量垃圾收集
    -Xloggc:<file>    將 GC 狀態(tài)記錄在文件中 (帶時(shí)間戳)
    -Xbatch           禁用后臺(tái)編譯
    -Xms<size>        設(shè)置初始 Java 堆大小
    -Xmx<size>        設(shè)置最大 Java 堆大小
    -Xss<size>        設(shè)置 Java 線程堆棧大小
    -Xprof            輸出 cpu 配置文件數(shù)據(jù)
    -Xfuture          啟用最嚴(yán)格的檢查, 預(yù)期將來的默認(rèn)值
    -Xrs              減少 Java/VM 對(duì)操作系統(tǒng)信號(hào)的使用 (請(qǐng)參閱文檔)
    -Xcheck:jni       對(duì) JNI 函數(shù)執(zhí)行其他檢查
    -Xshare:off       不嘗試使用共享類數(shù)據(jù)
    -Xshare:auto      在可能的情況下使用共享類數(shù)據(jù) (默認(rèn))
    -Xshare:on        要求使用共享類數(shù)據(jù), 否則將失敗。
    -XshowSettings    顯示所有設(shè)置并繼續(xù)
    -XshowSettings:all
                      顯示所有設(shè)置并繼續(xù)
    -XshowSettings:vm 顯示所有與 vm 相關(guān)的設(shè)置并繼續(xù)
    -XshowSettings:properties
                      顯示所有屬性設(shè)置并繼續(xù)
    -XshowSettings:locale
                      顯示所有與區(qū)域設(shè)置相關(guān)的設(shè)置并繼續(xù)

-X 選項(xiàng)是非標(biāo)準(zhǔn)選項(xiàng), 如有更改, 恕不另行通知挠蛉。

2.3.1祭示、-Xint、-Xcomp谴古、-Xmixed

  • 在解釋模式(interpreted mode)下质涛,-Xint標(biāo)記會(huì)強(qiáng)制JVM執(zhí)行所有的字節(jié)碼,當(dāng)然這會(huì)降低運(yùn)行速度掰担,通常低10倍或更多汇陆。
  • -Xcomp參數(shù)與它(-Xint)正好相反,JVM在第一次使用時(shí)會(huì)把所有的字節(jié)碼編譯成本地代碼带饱,從而帶來最大程度的優(yōu)化毡代。
    • 然而,很多應(yīng)用在使用-Xcomp也會(huì)有一些性能損失勺疼,當(dāng)然這比使用-Xint損失的少教寂,原因是-xcomp沒有讓JVM啟用JIT編譯器的全部功能。JIT編譯器可以對(duì)是否需要編譯做判斷恢口,如果所有代碼都進(jìn)行編譯的話孝宗,對(duì)于一些只執(zhí)行一次的代碼就沒有意義了穷躁。
  • -Xmixed是混合模式耕肩,將解釋模式與編譯模式進(jìn)行混合使用,由jvm自己決定问潭,這是jvm默認(rèn)的模式猿诸,也是推薦使用的模式。

示例:強(qiáng)制設(shè)置運(yùn)行模式

#強(qiáng)制設(shè)置為解釋模式
[root@node01 test]# java  -showversion -Xint TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, interpreted mode)



#強(qiáng)制設(shè)置為編譯模式
[root@node01 test]# java  -showversion -Xcomp TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, compiled mode)


#注意:編譯模式下狡忙,第一次執(zhí)行會(huì)比解釋模式下執(zhí)行慢一些梳虽,注意觀察。


#默認(rèn)的混合模式
[root@node01 test]# java  -showversion TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

hello world

2.4灾茁、-XX參數(shù)

-XX參數(shù)也是非標(biāo)準(zhǔn)參數(shù)窜觉,主要用于jvm的調(diào)優(yōu)和debug操作。

-XX參數(shù)的使用有2種方式北专,一種是boolean類型禀挫,一種是非boolean類型:

  • boolean類型
    • 格式:-XX:[+-]<name> 表示啟用或禁用<name>屬性
    • 如:-XX:+DisableExplicitGC 表示禁用手動(dòng)調(diào)用gc操作,也就是說調(diào)用System.gc()無效
  • 非boolean類型
    • 格式:-XX:<name>=<value> 表示<name>屬性的值為<value>
    • 如:-XX:NewRatio=1 表示新生代和老年代的比值

用法:

[root@node01 test]# java -showversion -XX:+DisableExplicitGC TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

hello world

2.5拓颓、-Xms與-Xmx參數(shù)

-Xms與-Xmx分別是設(shè)置jvm的堆內(nèi)存的初始大小和最大大小语婴。

-Xmx2048m:等價(jià)于-XX:MaxHeapSize,設(shè)置JVM最大堆內(nèi)存為2048M。

-Xms512m:等價(jià)于-XX:InitialHeapSize砰左,設(shè)置JVM初始堆內(nèi)存為512M匿醒。

適當(dāng)?shù)恼{(diào)整jvm的內(nèi)存大小,可以充分利用服務(wù)器資源缠导,讓程序跑的更快廉羔。

示例:

[root@node01 test]# java -Xms512m -Xmx2048m TestJVM
hello world

2.6、查看jvm的運(yùn)行參數(shù)

有些時(shí)候我們需要查看jvm的運(yùn)行參數(shù)酬核,這個(gè)需求可能會(huì)存在2種情況:

第一蜜另,運(yùn)行java命令時(shí)打印出運(yùn)行參數(shù);

第二嫡意,查看正在運(yùn)行的java進(jìn)程的參數(shù)举瑰;

2.6.1、運(yùn)行java命令時(shí)打印參數(shù)

運(yùn)行java命令時(shí)打印參數(shù)蔬螟,需要添加-XX:+PrintFlagsFinal參數(shù)即可此迅。

[root@node01 test]# java -XX:+PrintFlagsFinal -version
[Global flags]
    uintx AdaptiveSizeDecrementScaleFactor          = 4                                   {product}
    uintx AdaptiveSizeMajorGCDecayTimeScale         = 10                                  {product}
    uintx AdaptiveSizePausePolicy                   = 0                                   {product}
    uintx AdaptiveSizePolicyCollectionCostMargin    = 50                                  {product}
    uintx AdaptiveSizePolicyInitializingSteps       = 20                                  {product}
    uintx AdaptiveSizePolicyOutputInterval          = 0                                   {product}
    uintx AdaptiveSizePolicyWeight                  = 10                                  {product}
    uintx AdaptiveSizeThroughPutPolicy              = 0                                   {product}
    uintx AdaptiveTimeWeight                        = 25                                  {product}
     bool AdjustConcurrency                         = false                               {product}
     bool AggressiveOpts                            = false                               {product}
     intx AliasLevel                                = 3                                   {C2 product}
     bool AlignVector                               = true                                {C2 product}
     intx AllocateInstancePrefetchLines             = 1                                   {product}
     intx AllocatePrefetchDistance                  = 256                                 {product}
     intx AllocatePrefetchInstr                     = 0                                   {product}
     
     …………………………略…………………………………………
     
    
     bool UseXmmI2D                                 = false                               {ARCH product}
     bool UseXmmI2F                                 = false                               {ARCH product}
     bool UseXmmLoadAndClearUpper                   = true                                {ARCH product}
     bool UseXmmRegToRegMoveAll                     = true                                {ARCH product}
     bool VMThreadHintNoPreempt                     = false                               {product}
     intx VMThreadPriority                          = -1                                  {product}
     intx VMThreadStackSize                         = 1024                                {pd product}
     intx ValueMapInitialSize                       = 11                                  {C1 product}
     intx ValueMapMaxLoopSize                       = 8                                   {C1 product}
     intx ValueSearchLimit                          = 1000                                {C2 product}
     bool VerifyMergedCPBytecodes                   = true                                {product}
     bool VerifySharedSpaces                        = false                               {product}
     intx WorkAroundNPTLTimedWaitHang               = 1                                   {product}
    uintx YoungGenerationSizeIncrement              = 20                                  {product}
    uintx YoungGenerationSizeSupplement             = 80                                  {product}
    uintx YoungGenerationSizeSupplementDecay        = 8                                   {product}
    uintx YoungPLABSize                             = 4096                                {product}
     bool ZeroTLAB                                  = false                               {product}
     intx hashCode                                  = 5                                   {product}
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

由上述的信息可以看出,參數(shù)有boolean類型和數(shù)字類型旧巾,值的操作符是=或:=耸序,分別代表默認(rèn)值和被修改的值。

示例:

java -XX:+PrintFlagsFinal -XX:+VerifySharedSpaces -version

     intx ValueMapInitialSize                       = 11                                  {C1 product}
     intx ValueMapMaxLoopSize                       = 8                                   {C1 product}
     intx ValueSearchLimit                          = 1000                                {C2 product}
     bool VerifyMergedCPBytecodes                   = true                                {product}
     bool VerifySharedSpaces                       := true                                {product}
     intx WorkAroundNPTLTimedWaitHang               = 1                                   {product}
    uintx YoungGenerationSizeIncrement              = 20                                  {product}
    uintx YoungGenerationSizeSupplement             = 80                                  {product}
    uintx YoungGenerationSizeSupplementDecay        = 8                                   {product}
    uintx YoungPLABSize                             = 4096                                {product}
     bool ZeroTLAB                                  = false                               {product}
     intx hashCode                                  = 5                                   {product}
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

#可以看到VerifySharedSpaces這個(gè)參數(shù)已經(jīng)被修改了鲁猩。

2.6.2坎怪、查看正在運(yùn)行的jvm參數(shù)

如果想要查看正在運(yùn)行的jvm就需要借助于jinfo命令查看。

首先廓握,啟動(dòng)一個(gè)tomcat用于測試搅窿,來觀察下運(yùn)行的jvm參數(shù)。

 cd /tmp/
 rz 上傳
 tar -xvf apache-tomcat-7.0.57.tar.gz 
 cd apache-tomcat-7.0.57
 cd bin/
 ./startup.sh
 
 #http://192.168.40.133:8080/ 進(jìn)行訪問

訪問成功:
1536488631901.png
#查看所有的參數(shù)隙券,用法:jinfo -flags <進(jìn)程id>

#通過jps 或者  jps -l 查看java進(jìn)程
[root@node01 bin]# jps
6346 Jps
6219 Bootstrap
[root@node01 bin]# jps -l
6358 sun.tools.jps.Jps
6219 org.apache.catalina.startup.Bootstrap
[root@node01 bin]#

[root@node01 bin]# jinfo -flags 6219
Attaching to process ID 6219, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=31457280 -XX:MaxHeapSize=488636416 -XX:MaxNewSize=162529280 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=10485760 -XX:OldSize=20971520 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC 
Command line:  -Djava.util.logging.config.file=/tmp/apache-tomcat-7.0.57/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/tmp/apache-tomcat-7.0.57/endorsed -Dcatalina.base=/tmp/apache-tomcat-7.0.57 -Dcatalina.home=/tmp/apache-tomcat-7.0.57 -Djava.io.tmpdir=/tmp/apache-tomcat-7.0.57/temp

#查看某一參數(shù)的值男应,用法:jinfo -flag <參數(shù)名> <進(jìn)程id>
[root@node01 bin]# jinfo -flag MaxHeapSize 6219
-XX:MaxHeapSize=488636416

3、jvm的內(nèi)存模型

jvm的內(nèi)存模型在1.7和1.8有較大的區(qū)別娱仔,雖然本套課程是以1.8為例進(jìn)行講解沐飘,但是我們也是需要對(duì)1.7的內(nèi)存模型有所了解,所以接下里牲迫,我們將先學(xué)習(xí)1.7再學(xué)習(xí)1.8的內(nèi)存模型耐朴。

3.1、jdk1.7的堆內(nèi)存模型

1536501289198.png
  • Young 年輕區(qū)(代)

    Young區(qū)被劃分為三部分盹憎,Eden區(qū)和兩個(gè)大小嚴(yán)格相同的Survivor區(qū)筛峭,其中,Survivor區(qū)間中脚乡,某一時(shí)刻只有其中一個(gè)是被使用的蜒滩,另外一個(gè)留做垃圾收集時(shí)復(fù)制對(duì)象用滨达,在Eden區(qū)間變滿的時(shí)候, GC就會(huì)將存活的對(duì)象移到空閑的Survivor區(qū)間中俯艰,根據(jù)JVM的策略捡遍,在經(jīng)過幾次垃圾收集后,任然存活于Survivor的對(duì)象將被移動(dòng)到Tenured區(qū)間竹握。

  • Tenured 年老區(qū)

    Tenured區(qū)主要保存生命周期長的對(duì)象画株,一般是一些老的對(duì)象,當(dāng)一些對(duì)象在Young復(fù)制轉(zhuǎn)移一定的次數(shù)以后啦辐,對(duì)象就會(huì)被轉(zhuǎn)移到Tenured區(qū)谓传,一般如果系統(tǒng)中用了application級(jí)別的緩存,緩存中的對(duì)象往往會(huì)被轉(zhuǎn)移到這一區(qū)間芹关。

  • Perm 永久區(qū)

    Perm代主要保存class,method,filed對(duì)象续挟,這部份的空間一般不會(huì)溢出,除非一次性加載了很多的類侥衬,不過在涉及到熱部署的應(yīng)用服務(wù)器的時(shí)候诗祸,有時(shí)候會(huì)遇到j(luò)ava.lang.OutOfMemoryError : PermGen space 的錯(cuò)誤,造成這個(gè)錯(cuò)誤的很大原因就有可能是每次都重新部署轴总,但是重新部署后直颅,類的class沒有被卸載掉,這樣就造成了大量的class對(duì)象保存在了perm中怀樟,這種情況下功偿,一般重新啟動(dòng)應(yīng)用服務(wù)器可以解決問題。

  • Virtual區(qū):

    • 最大內(nèi)存和初始內(nèi)存的差值往堡,就是Virtual區(qū)械荷。

3.2、jdk1.8的堆內(nèi)存模型

1536501652150.png

由上圖可以看出投蝉,jdk1.8的內(nèi)存模型是由2部分組成养葵,年輕代 + 年老代征堪。

年輕代:Eden + 2*Survivor

年老代:OldGen

在jdk1.8中變化最大的Perm區(qū)瘩缆,用Metaspace(元數(shù)據(jù)空間)進(jìn)行了替換。

需要特別說明的是:Metaspace所占用的內(nèi)存空間不是在虛擬機(jī)內(nèi)部佃蚜,而是在本地內(nèi)存空間中庸娱,這也是與1.7的永久代最大的區(qū)別所在。

1536507420882.png

3.3谐算、為什么要廢棄1.7中的永久區(qū)?

官網(wǎng)給出了解釋:http://openjdk.java.net/jeps/122

This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.

移除永久代是為融合HotSpot JVM與 JRockit VM而做出的努力,因?yàn)镴Rockit沒有永久代芍锦,不需要配置永久代励饵。

現(xiàn)實(shí)使用中剧包,由于永久代內(nèi)存經(jīng)常不夠用或發(fā)生內(nèi)存泄露,爆出異常java.lang.OutOfMemoryError: PermGen往果。

基于此疆液,將永久區(qū)廢棄,而改用元空間陕贮,改為了使用本地內(nèi)存空間堕油。

3.4、通過jstat命令進(jìn)行查看堆內(nèi)存使用情況

jstat命令可以查看堆內(nèi)存各部分的使用量肮之,以及加載類的數(shù)量掉缺。命令的格式如下:

jstat [-命令選項(xiàng)] [vmid] [間隔時(shí)間/毫秒] [查詢次數(shù)]

3.4.1、查看class加載統(tǒng)計(jì)

[root@node01 ~]# jps
7080 Jps
6219 Bootstrap

[root@node01 ~]# jstat -class 6219
Loaded  Bytes  Unloaded  Bytes     Time   
  3273  7122.3        0     0.0       3.98

說明:

  • Loaded:加載class的數(shù)量
  • Bytes:所占用空間大小
  • Unloaded:未加載數(shù)量
  • Bytes:未加載占用空間
  • Time:時(shí)間

3.4.2戈擒、查看編譯統(tǒng)計(jì)

[root@node01 ~]# jstat -compiler 6219
Compiled Failed Invalid   Time   FailedType FailedMethod
    2376      1       0     8.04          1 org/apache/tomcat/util/IntrospectionUtils setProperty

說明:

  • Compiled:編譯數(shù)量眶明。
  • Failed:失敗數(shù)量
  • Invalid:不可用數(shù)量
  • Time:時(shí)間
  • FailedType:失敗類型
  • FailedMethod:失敗的方法

3.4.3、垃圾回收統(tǒng)計(jì)

[root@node01 ~]# jstat -gc 6219
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
9216.0 8704.0  0.0   6127.3 62976.0   3560.4   33792.0    20434.9   23808.0 23196.1 2560.0 2361.6      7    1.078   1      0.244    1.323

#也可以指定打印的間隔和次數(shù)筐高,每1秒中打印一次赘来,共打印5次
[root@node01 ~]# jstat -gc 6219 1000 5
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
9216.0 8704.0  0.0   6127.3 62976.0   3917.3   33792.0    20434.9   23808.0 23196.1 2560.0 2361.6      7    1.078   1      0.244    1.323
9216.0 8704.0  0.0   6127.3 62976.0   3917.3   33792.0    20434.9   23808.0 23196.1 2560.0 2361.6      7    1.078   1      0.244    1.323
9216.0 8704.0  0.0   6127.3 62976.0   3917.3   33792.0    20434.9   23808.0 23196.1 2560.0 2361.6      7    1.078   1      0.244    1.323
9216.0 8704.0  0.0   6127.3 62976.0   3917.3   33792.0    20434.9   23808.0 23196.1 2560.0 2361.6      7    1.078   1      0.244    1.323
9216.0 8704.0  0.0   6127.3 62976.0   3917.3   33792.0    20434.9   23808.0 23196.1 2560.0 2361.6      7    1.078   1      0.244    1.323

說明:

  • S0C:第一個(gè)Survivor區(qū)的大小(KB)
  • S1C:第二個(gè)Survivor區(qū)的大锌痢(KB)
  • S0U:第一個(gè)Survivor區(qū)的使用大腥健(KB)
  • S1U:第二個(gè)Survivor區(qū)的使用大小(KB)
  • EC:Eden區(qū)的大斜ァ(KB)
  • EU:Eden區(qū)的使用大谢戏臁(KB)
  • OC:Old區(qū)大小(KB)
  • OU:Old使用大薪肭贰(KB)
  • MC:方法區(qū)大泻选(KB)
  • MU:方法區(qū)使用大小(KB)
  • CCSC:壓縮類空間大谢牡稹(KB)
  • CCSU:壓縮類空間使用大薪钨恕(KB)
  • YGC:年輕代垃圾回收次數(shù)
  • YGCT:年輕代垃圾回收消耗時(shí)間
  • FGC:老年代垃圾回收次數(shù)
  • FGCT:老年代垃圾回收消耗時(shí)間
  • GCT:垃圾回收消耗總時(shí)間

4、jmap的使用以及內(nèi)存溢出分析

前面通過jstat可以對(duì)jvm堆的內(nèi)存進(jìn)行統(tǒng)計(jì)分析被廓,而jmap可以獲取到更加詳細(xì)的內(nèi)容坏晦,如:內(nèi)存使用情況的匯總、對(duì)內(nèi)存溢出的定位與分析嫁乘。

4.1昆婿、查看內(nèi)存使用情況

[root@node01 ~]# jmap -heap 6219
Attaching to process ID 6219, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15

using thread-local object allocation.
Parallel GC with 2 thread(s)

Heap Configuration: #堆內(nèi)存配置信息
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 488636416 (466.0MB)
   NewSize                  = 10485760 (10.0MB)
   MaxNewSize               = 162529280 (155.0MB)
   OldSize                  = 20971520 (20.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage: # 堆內(nèi)存的使用情況
PS Young Generation #年輕代
Eden Space:
   capacity = 123731968 (118.0MB)
   used     = 1384736 (1.320587158203125MB)
   free     = 122347232 (116.67941284179688MB)
   1.1191416594941737% used
From Space:
   capacity = 9437184 (9.0MB)
   used     = 0 (0.0MB)
   free     = 9437184 (9.0MB)
   0.0% used
To Space:
   capacity = 9437184 (9.0MB)
   used     = 0 (0.0MB)
   free     = 9437184 (9.0MB)
   0.0% used
PS Old Generation #年老代
   capacity = 28311552 (27.0MB)
   used     = 13698672 (13.064071655273438MB)
   free     = 14612880 (13.935928344726562MB)
   48.38545057508681% used

13648 interned Strings occupying 1866368 bytes.

4.2、查看內(nèi)存中對(duì)象數(shù)量及大小

#查看所有對(duì)象蜓斧,包括活躍以及非活躍的
jmap -histo <pid> | more

#查看活躍對(duì)象
jmap -histo:live <pid> | more

[root@node01 ~]# jmap -histo:live 6219 | more

 num     #instances         #bytes  class name
----------------------------------------------
   1:         37437        7914608  [C
   2:         34916         837984  java.lang.String
   3:           884         654848  [B
   4:         17188         550016  java.util.HashMap$Node
   5:          3674         424968  java.lang.Class
   6:          6322         395512  [Ljava.lang.Object;
   7:          3738         328944  java.lang.reflect.Method
   8:          1028         208048  [Ljava.util.HashMap$Node;
   9:          2247         144264  [I
  10:          4305         137760  java.util.concurrent.ConcurrentHashMap$Node
  11:          1270         109080  [Ljava.lang.String;
  12:            64          84128  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  13:          1714          82272  java.util.HashMap
  14:          3285          70072  [Ljava.lang.Class;
  15:          2888          69312  java.util.ArrayList
  16:          3983          63728  java.lang.Object
  17:          1271          61008  org.apache.tomcat.util.digester.CallMethodRule
  18:          1518          60720  java.util.LinkedHashMap$Entry
  19:          1671          53472  com.sun.org.apache.xerces.internal.xni.QName
  20:            88          50880  [Ljava.util.WeakHashMap$Entry;
  21:           618          49440  java.lang.reflect.Constructor
  22:          1545          49440  java.util.Hashtable$Entry
  23:          1027          41080  java.util.TreeMap$Entry
  24:           846          40608  org.apache.tomcat.util.modeler.AttributeInfo
  25:           142          38032  [S
  26:           946          37840  java.lang.ref.SoftReference
  27:           226          36816  [[C
  仓蛆。。挎春。看疙。豆拨。。能庆。辽装。。相味。拾积。。丰涉。拓巧。。一死。肛度。。投慈。承耿。。伪煤。加袋。。抱既。职烧。。防泵。蚀之。。捷泞。
  
#對(duì)象說明
B  byte
C  char
D  double
F  float
I  int
J  long
Z  boolean
[  數(shù)組足删,如[I表示int[]
[L+類名 其他對(duì)象

4.3、將內(nèi)存使用情況dump到文件中

有些時(shí)候我們需要將jvm當(dāng)前內(nèi)存中的情況dump到文件中锁右,然后對(duì)它進(jìn)行分析失受,jmap也是支持dump到文件中的。

#用法:
jmap -dump:format=b,file=dumpFileName <pid>

#示例
jmap -dump:format=b,file=/tmp/dump.dat 6219

1536512769514.png

可以看到已經(jīng)在/tmp下生成了dump.dat的文件骡湖。

4.4贱纠、通過jhat對(duì)dump文件進(jìn)行分析

在上一小節(jié)中峻厚,我們將jvm的內(nèi)存dump到文件中响蕴,這個(gè)文件是一個(gè)二進(jìn)制的文件,不方便查看惠桃,這時(shí)我們可以借助于jhat工具進(jìn)行查看浦夷。

#用法:
jhat -port <port> <file>

#示例:
[root@node01 tmp]# jhat -port 9999 /tmp/dump.dat 
Reading from /tmp/dump.dat...
Dump file created Mon Sep 10 01:04:21 CST 2018
Snapshot read, resolving...
Resolving 204094 objects...
Chasing references, expect 40 dots........................................
Eliminating duplicate references........................................
Snapshot resolved.
Started HTTP server on port 9999
Server is ready.

打開瀏覽器進(jìn)行訪問:http://192.168.40.133:9999/

1536513416186.png

在最后面有OQL查詢功能辖试。

1536513474958.png
1536513591638.png

4.5、通過MAT工具對(duì)dump文件進(jìn)行分析

4.5.1劈狐、MAT工具介紹

MAT(Memory Analyzer Tool)罐孝,一個(gè)基于Eclipse的內(nèi)存分析工具,是一個(gè)快速肥缔、功能豐富的JAVA heap分析工具莲兢,它可以幫助我們查找內(nèi)存泄漏和減少內(nèi)存消耗。使用內(nèi)存分析工具從眾多的對(duì)象中進(jìn)行分析续膳,快速的計(jì)算出在內(nèi)存中對(duì)象的占用大小改艇,看看是誰阻止了垃圾收集器的回收工作,并可以通過報(bào)表直觀的查看到可能造成這種結(jié)果的對(duì)象坟岔。

官網(wǎng)地址:https://www.eclipse.org/mat/

1536515331814.png

4.5.2谒兄、下載安裝

下載地址:https://www.eclipse.org/mat/downloads.php

1536515395914.png

將下載得到的MemoryAnalyzer-1.8.0.20180604-win32.win32.x86_64.zip進(jìn)行解壓:

1536515524838.png

4.5.3、使用

1536515700264.png
1536515764096.png
1536515922938.png
1536516001249.png

查看對(duì)象以及它的依賴:


1536516171924.png

查看可能存在內(nèi)存泄露的分析:


1536516259369.png

5社付、實(shí)戰(zhàn):內(nèi)存溢出的定位與分析

內(nèi)存溢出在實(shí)際的生產(chǎn)環(huán)境中經(jīng)常會(huì)遇到承疲,比如,不斷的將數(shù)據(jù)寫入到一個(gè)集合中鸥咖,出現(xiàn)了死循環(huán)燕鸽,讀取超大的文件等等,都可能會(huì)造成內(nèi)存溢出啼辣。

如果出現(xiàn)了內(nèi)存溢出绵咱,首先我們需要定位到發(fā)生內(nèi)存溢出的環(huán)節(jié),并且進(jìn)行分析熙兔,是正常還是非正常情況悲伶,如果是正常的需求,就應(yīng)該考慮加大內(nèi)存的設(shè)置住涉,如果是非正常需求麸锉,那么就要對(duì)代碼進(jìn)行修改,修復(fù)這個(gè)bug舆声。

首先花沉,我們得先學(xué)會(huì)如何定位問題,然后再進(jìn)行分析媳握。如何定位問題呢碱屁,我們需要借助于jmap與MAT工具進(jìn)行定位分析。

接下來蛾找,我們模擬內(nèi)存溢出的場景娩脾。

5.1、模擬內(nèi)存溢出

編寫代碼打毛,向List集合中添加100萬個(gè)字符串柿赊,每個(gè)字符串由1000個(gè)UUID組成俩功。如果程序能夠正常執(zhí)行,最后打印ok碰声。

package cn.test.jvm;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class TestJvmOutOfMemory {

    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            String str = "";
            for (int j = 0; j < 1000; j++) {
                str += UUID.randomUUID().toString();
            }
            list.add(str);
        }
        System.out.println("ok");
    }
}

為了演示效果诡蜓,我們將設(shè)置執(zhí)行的參數(shù),這里使用的是Idea編輯器胰挑。

1536591244437.png
#參數(shù)如下:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

5.2蔓罚、運(yùn)行測試

測試結(jié)果如下:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid5348.hprof ...
Heap dump file created [8137186 bytes in 0.032 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at cn.test.jvm.TestJvmOutOfMemory.main(TestJvmOutOfMemory.java:14)

Process finished with exit code 1

可以看到,當(dāng)發(fā)生內(nèi)存溢出時(shí)瞻颂,會(huì)dump文件到j(luò)ava_pid5348.hprof脚粟。


1536591747657.png

5.3、導(dǎo)入到MAT工具中進(jìn)行分析

1536592135948.png

可以看到蘸朋,有91.03%的內(nèi)存由Object[]數(shù)組占有核无,所以比較可疑。

分析:這個(gè)可疑是正確的藕坯,因?yàn)橐呀?jīng)有超過90%的內(nèi)存都被它占有团南,這是非常有可能出現(xiàn)內(nèi)存溢出的。

查看詳情:


1536594010544.png

可以看到集合中存儲(chǔ)了大量的uuid字符串炼彪。

6吐根、jstack的使用

有些時(shí)候我們需要查看下jvm中的線程執(zhí)行情況,比如辐马,發(fā)現(xiàn)服務(wù)器的CPU的負(fù)載突然增高了拷橘、出現(xiàn)了死鎖、死循環(huán)等喜爷,我們該如何分析呢冗疮?

由于程序是正常運(yùn)行的,沒有任何的輸出檩帐,從日志方面也看不出什么問題术幔,所以就需要看下jvm的內(nèi)部線程的執(zhí)行情況,然后再進(jìn)行分析查找出原因湃密。

這個(gè)時(shí)候诅挑,就需要借助于jstack命令了,jstack的作用是將正在運(yùn)行的jvm的線程情況進(jìn)行快照泛源,并且打印出來:

#用法:jstack <pid>

[root@node01 bin]# jstack 2203
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.141-b15 mixed mode):

"Attach Listener" #24 daemon prio=9 os_prio=0 tid=0x00007fabb4001000 nid=0x906 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"http-bio-8080-exec-5" #23 daemon prio=5 os_prio=0 tid=0x00007fabb057c000 nid=0x8e1 waiting on condition [0x00007fabd05b8000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

"http-bio-8080-exec-4" #22 daemon prio=5 os_prio=0 tid=0x00007fab9c113800 nid=0x8e0 waiting on condition [0x00007fabd06b9000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

"http-bio-8080-exec-3" #21 daemon prio=5 os_prio=0 tid=0x0000000001aeb800 nid=0x8df waiting on condition [0x00007fabd09ba000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

"http-bio-8080-exec-2" #20 daemon prio=5 os_prio=0 tid=0x0000000001aea000 nid=0x8de waiting on condition [0x00007fabd0abb000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

"http-bio-8080-exec-1" #19 daemon prio=5 os_prio=0 tid=0x0000000001ae8800 nid=0x8dd waiting on condition [0x00007fabd0bbc000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000000f8508360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
    at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

"ajp-bio-8009-AsyncTimeout" #17 daemon prio=5 os_prio=0 tid=0x00007fabe8128000 nid=0x8d0 waiting on condition [0x00007fabd0ece000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:152)
    at java.lang.Thread.run(Thread.java:748)

"ajp-bio-8009-Acceptor-0" #16 daemon prio=5 os_prio=0 tid=0x00007fabe82d4000 nid=0x8cf runnable [0x00007fabd0fcf000]
   java.lang.Thread.State: RUNNABLE
    at java.net.PlainSocketImpl.socketAccept(Native Method)
    at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
    at java.net.ServerSocket.implAccept(ServerSocket.java:545)
    at java.net.ServerSocket.accept(ServerSocket.java:513)
    at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
    at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)
    at java.lang.Thread.run(Thread.java:748)

"http-bio-8080-AsyncTimeout" #15 daemon prio=5 os_prio=0 tid=0x00007fabe82d1800 nid=0x8ce waiting on condition [0x00007fabd10d0000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:152)
    at java.lang.Thread.run(Thread.java:748)

"http-bio-8080-Acceptor-0" #14 daemon prio=5 os_prio=0 tid=0x00007fabe82d0000 nid=0x8cd runnable [0x00007fabd11d1000]
   java.lang.Thread.State: RUNNABLE
    at java.net.PlainSocketImpl.socketAccept(Native Method)
    at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
    at java.net.ServerSocket.implAccept(ServerSocket.java:545)
    at java.net.ServerSocket.accept(ServerSocket.java:513)
    at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
    at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)
    at java.lang.Thread.run(Thread.java:748)

"ContainerBackgroundProcessor[StandardEngine[Catalina]]" #13 daemon prio=5 os_prio=0 tid=0x00007fabe82ce000 nid=0x8cc waiting on condition [0x00007fabd12d2000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1513)
    at java.lang.Thread.run(Thread.java:748)

"GC Daemon" #10 daemon prio=2 os_prio=0 tid=0x00007fabe83b4000 nid=0x8b3 in Object.wait() [0x00007fabd1c2f000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000e315c2d0> (a sun.misc.GC$LatencyLock)
    at sun.misc.GC$Daemon.run(GC.java:117)
    - locked <0x00000000e315c2d0> (a sun.misc.GC$LatencyLock)

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007fabe80c3800 nid=0x8a5 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fabe80b6800 nid=0x8a4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fabe80b3800 nid=0x8a3 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fabe80b2000 nid=0x8a2 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fabe807f000 nid=0x8a1 in Object.wait() [0x00007fabd2a67000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000e3162918> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
    - locked <0x00000000e3162918> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fabe807a800 nid=0x8a0 in Object.wait() [0x00007fabd2b68000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000e3162958> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    - locked <0x00000000e3162958> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"main" #1 prio=5 os_prio=0 tid=0x00007fabe8009000 nid=0x89c runnable [0x00007fabed210000]
   java.lang.Thread.State: RUNNABLE
    at java.net.PlainSocketImpl.socketAccept(Native Method)
    at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
    at java.net.ServerSocket.implAccept(ServerSocket.java:545)
    at java.net.ServerSocket.accept(ServerSocket.java:513)
    at org.apache.catalina.core.StandardServer.await(StandardServer.java:453)
    at org.apache.catalina.startup.Catalina.await(Catalina.java:777)
    at org.apache.catalina.startup.Catalina.start(Catalina.java:723)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:321)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:455)

"VM Thread" os_prio=0 tid=0x00007fabe8073000 nid=0x89f runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fabe801e000 nid=0x89d runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fabe8020000 nid=0x89e runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007fabe80d6800 nid=0x8a6 waiting on condition 

JNI global references: 43

6.1拔妥、線程的狀態(tài)

1536601656694.png

在Java中線程的狀態(tài)一共被分成6種:

  • 初始態(tài)(NEW)

    • 創(chuàng)建一個(gè)Thread對(duì)象,但還未調(diào)用start()啟動(dòng)線程時(shí)达箍,線程處于初始態(tài)没龙。
  • 運(yùn)行態(tài)(RUNNABLE),在Java中,運(yùn)行態(tài)包括 就緒態(tài) 和 運(yùn)行態(tài)兜畸。

    • 就緒態(tài)

      • 該狀態(tài)下的線程已經(jīng)獲得執(zhí)行所需的所有資源努释,只要CPU分配執(zhí)行權(quán)就能運(yùn)行碘梢。
      • 所有就緒態(tài)的線程存放在就緒隊(duì)列中咬摇。
    • 運(yùn)行態(tài)

      • 獲得CPU執(zhí)行權(quán),正在執(zhí)行的線程煞躬。
      • 由于一個(gè)CPU同一時(shí)刻只能執(zhí)行一條線程肛鹏,因此每個(gè)CPU每個(gè)時(shí)刻只有一條運(yùn)行態(tài)的線程。
  • 阻塞態(tài)(BLOCKED)

    • 當(dāng)一條正在執(zhí)行的線程請(qǐng)求某一資源失敗時(shí)恩沛,就會(huì)進(jìn)入阻塞態(tài)在扰。
    • 而在Java中,阻塞態(tài)專指請(qǐng)求鎖失敗時(shí)進(jìn)入的狀態(tài)雷客。
    • 由一個(gè)阻塞隊(duì)列存放所有阻塞態(tài)的線程芒珠。
    • 處于阻塞態(tài)的線程會(huì)不斷請(qǐng)求資源,一旦請(qǐng)求成功搅裙,就會(huì)進(jìn)入就緒隊(duì)列皱卓,等待執(zhí)行。
  • 等待態(tài)(WAITING)

    • 當(dāng)前線程中調(diào)用wait部逮、join娜汁、park函數(shù)時(shí),當(dāng)前線程就會(huì)進(jìn)入等待態(tài)兄朋。
    • 也有一個(gè)等待隊(duì)列存放所有等待態(tài)的線程掐禁。
    • 線程處于等待態(tài)表示它需要等待其他線程的指示才能繼續(xù)運(yùn)行。
    • 進(jìn)入等待態(tài)的線程會(huì)釋放CPU執(zhí)行權(quán)颅和,并釋放資源(如:鎖)
  • 超時(shí)等待態(tài)(TIMED_WAITING)

    • 當(dāng)運(yùn)行中的線程調(diào)用sleep(time)傅事、wait、join峡扩、parkNanos享完、parkUntil時(shí),就會(huì)進(jìn)入該狀態(tài)有额;
    • 它和等待態(tài)一樣般又,并不是因?yàn)檎?qǐng)求不到資源,而是主動(dòng)進(jìn)入巍佑,并且進(jìn)入后需要其他線程喚醒茴迁;
    • 進(jìn)入該狀態(tài)后釋放CPU執(zhí)行權(quán) 和 占有的資源。
    • 與等待態(tài)的區(qū)別:到了超時(shí)時(shí)間后自動(dòng)進(jìn)入阻塞隊(duì)列萤衰,開始競爭鎖堕义。
  • 終止態(tài)(TERMINATED)

    • 線程執(zhí)行結(jié)束后的狀態(tài)。

6.2、實(shí)戰(zhàn):死鎖問題

如果在生產(chǎn)環(huán)境發(fā)生了死鎖倦卖,我們將看到的是部署的程序沒有任何反應(yīng)了洒擦,這個(gè)時(shí)候我們可以借助jstack進(jìn)行分析,下面我們實(shí)戰(zhàn)下查找死鎖的原因怕膛。

6.2.1熟嫩、構(gòu)造死鎖

編寫代碼,啟動(dòng)2個(gè)線程褐捻,Thread1拿到了obj1鎖掸茅,準(zhǔn)備去拿obj2鎖時(shí),obj2已經(jīng)被Thread2鎖定柠逞,所以發(fā)送了死鎖昧狮。

public class TestDeadLock {

    private static Object obj1 = new Object();

    private static Object obj2 = new Object();


    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }

    private static class Thread1 implements Runnable{
        @Override
        public void run() {
            synchronized (obj1){
                System.out.println("Thread1 拿到了 obj1 的鎖!");

                try {
                    // 停頓2秒的意義在于板壮,讓Thread2線程拿到obj2的鎖
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj2){
                    System.out.println("Thread1 拿到了 obj2 的鎖逗鸣!");
                }
            }
        }
    }

    private static class Thread2 implements Runnable{
        @Override
        public void run() {
            synchronized (obj2){
                System.out.println("Thread2 拿到了 obj2 的鎖!");

                try {
                    // 停頓2秒的意義在于绰精,讓Thread1線程拿到obj1的鎖
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj1){
                    System.out.println("Thread2 拿到了 obj1 的鎖撒璧!");
                }
            }
        }
    }

}

6.2.2、在linux上運(yùn)行

[root@node01 test]# javac TestDeadLock.java 
[root@node01 test]# ll
總用量 28
-rw-r--r--. 1 root root  184 9月  11 10:39 TestDeadLock$1.class
-rw-r--r--. 1 root root  843 9月  11 10:39 TestDeadLock.class
-rw-r--r--. 1 root root 1567 9月  11 10:39 TestDeadLock.java
-rw-r--r--. 1 root root 1078 9月  11 10:39 TestDeadLock$Thread1.class
-rw-r--r--. 1 root root 1078 9月  11 10:39 TestDeadLock$Thread2.class
-rw-r--r--. 1 root root  573 9月   9 10:21 TestJVM.class
-rw-r--r--. 1 root root  261 9月   9 10:21 TestJVM.java

[root@node01 test]# java TestDeadLock
Thread1 拿到了 obj1 的鎖茬底!
Thread2 拿到了 obj2 的鎖沪悲!
#這里發(fā)生了死鎖,程序一直將等待下去

6.2.3阱表、使用jstack進(jìn)行分析

[root@node01 ~]# jstack 3256
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.141-b15 mixed mode):

"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007f5bfc001000 nid=0xcff waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007f5c2c008800 nid=0xcb9 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #9 prio=5 os_prio=0 tid=0x00007f5c2c0e9000 nid=0xcc5 waiting for monitor entry [0x00007f5c1c7f6000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at TestDeadLock$Thread2.run(TestDeadLock.java:47)
    - waiting to lock <0x00000000f655dc40> (a java.lang.Object)
    - locked <0x00000000f655dc50> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:748)

"Thread-0" #8 prio=5 os_prio=0 tid=0x00007f5c2c0e7000 nid=0xcc4 waiting for monitor entry [0x00007f5c1c8f7000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at TestDeadLock$Thread1.run(TestDeadLock.java:27)
    - waiting to lock <0x00000000f655dc50> (a java.lang.Object)
    - locked <0x00000000f655dc40> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:748)

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f5c2c0d3000 nid=0xcc2 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f5c2c0b6000 nid=0xcc1 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f5c2c0b3000 nid=0xcc0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f5c2c0b1800 nid=0xcbf runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f5c2c07e800 nid=0xcbe in Object.wait() [0x00007f5c1cdfc000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000f6508ec8> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
    - locked <0x00000000f6508ec8> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f5c2c07a000 nid=0xcbd in Object.wait() [0x00007f5c1cefd000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000f6506b68> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    - locked <0x00000000f6506b68> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=0 tid=0x00007f5c2c072800 nid=0xcbc runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f5c2c01d800 nid=0xcba runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f5c2c01f800 nid=0xcbb runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f5c2c0d6800 nid=0xcc3 waiting on condition 

JNI global references: 6


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f5c080062c8 (object 0x00000000f655dc40, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f5c08004e28 (object 0x00000000f655dc50, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at TestDeadLock$Thread2.run(TestDeadLock.java:47)
    - waiting to lock <0x00000000f655dc40> (a java.lang.Object)
    - locked <0x00000000f655dc50> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:748)
"Thread-0":
    at TestDeadLock$Thread1.run(TestDeadLock.java:27)
    - waiting to lock <0x00000000f655dc50> (a java.lang.Object)
    - locked <0x00000000f655dc40> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

在輸出的信息中殿如,已經(jīng)看到,發(fā)現(xiàn)了1個(gè)死鎖最爬,關(guān)鍵看這個(gè):

"Thread-1":
    at TestDeadLock$Thread2.run(TestDeadLock.java:47)
    - waiting to lock <0x00000000f655dc40> (a java.lang.Object)
    - locked <0x00000000f655dc50> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:748)
"Thread-0":
    at TestDeadLock$Thread1.run(TestDeadLock.java:27)
    - waiting to lock <0x00000000f655dc50> (a java.lang.Object)
    - locked <0x00000000f655dc40> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:748)

可以清晰的看到:

  • Thread2獲取了 <0x00000000f655dc50> 的鎖涉馁,等待獲取 <0x00000000f655dc40> 這個(gè)鎖
  • Thread1獲取了 <0x00000000f655dc40> 的鎖,等待獲取 <0x00000000f655dc50> 這個(gè)鎖
  • 由此可見爱致,發(fā)生了死鎖烤送。

7、VisualVM工具的使用

VisualVM糠悯,能夠監(jiān)控線程帮坚,內(nèi)存情況,查看方法的CPU時(shí)間和內(nèi)存中的對(duì) 象互艾,已被GC的對(duì)象试和,反向查看分配的堆棧(如100個(gè)String對(duì)象分別由哪幾個(gè)對(duì)象分配出來的)。

VisualVM使用簡單纫普,幾乎0配置阅悍,功能還是比較豐富的,幾乎囊括了其它JDK自帶命令的所有功能。

  • 內(nèi)存信息
  • 線程信息
  • Dump堆(本地進(jìn)程)
  • Dump線程(本地進(jìn)程)
  • 打開堆Dump节视。堆Dump可以用jmap來生成拳锚。
  • 打開線程Dump
  • 生成應(yīng)用快照(包含內(nèi)存信息、線程信息等等)
  • 性能分析寻行。CPU分析(各個(gè)方法調(diào)用時(shí)間霍掺,檢查哪些方法耗時(shí)多),內(nèi)存分析(各類對(duì)象占用的內(nèi)存寡痰,檢查哪些類占用內(nèi)存多)
  • ……

7.1抗楔、啟動(dòng)

在jdk的安裝目錄的bin目錄下棋凳,找到j(luò)visualvm.exe拦坠,雙擊打開即可。

1536654243155.png
1536654347046.png

7.2剩岳、查看本地進(jìn)程

1536654611922.png

7.3贞滨、查看CPU、內(nèi)存拍棕、類晓铆、線程運(yùn)行信息

1536654708421.png

7.4、查看線程詳情

1536654784904.png

也可以點(diǎn)擊右上角Dump按鈕绰播,將線程的信息導(dǎo)出骄噪,其實(shí)就是執(zhí)行的jstack命令。

1536656468818.png

發(fā)現(xiàn)蠢箩,顯示的內(nèi)容是一樣的链蕊。

7.5、抽樣器

抽樣器可以對(duì)CPU谬泌、內(nèi)存在一段時(shí)間內(nèi)進(jìn)行抽樣滔韵,以供分析。


1536678677028.png

7.6掌实、監(jiān)控遠(yuǎn)程的jvm

VisualJVM不僅是可以監(jiān)控本地jvm進(jìn)程陪蜻,還可以監(jiān)控遠(yuǎn)程的jvm進(jìn)程,需要借助于JMX技術(shù)實(shí)現(xiàn)贱鼻。

7.6.1宴卖、什么是JMX?

JMX(Java Management Extensions邻悬,即Java管理擴(kuò)展)是一個(gè)為應(yīng)用程序症昏、設(shè)備、系統(tǒng)等植入管理功能的框架拘悦。JMX可以跨越一系列異構(gòu)操作系統(tǒng)平臺(tái)齿兔、系統(tǒng)體系結(jié)構(gòu)和網(wǎng)絡(luò)傳輸協(xié)議,靈活的開發(fā)無縫集成的系統(tǒng)、網(wǎng)絡(luò)和服務(wù)管理應(yīng)用分苇。

7.6.2添诉、監(jiān)控遠(yuǎn)程的tomcat

想要監(jiān)控遠(yuǎn)程的tomcat,就需要在遠(yuǎn)程的tomcat進(jìn)行對(duì)JMX配置医寿,方法如下:

#在tomcat的bin目錄下栏赴,修改catalina.sh,添加如下的參數(shù)

JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"

#這幾個(gè)參數(shù)的意思是:
#-Dcom.sun.management.jmxremote :允許使用JMX遠(yuǎn)程管理
#-Dcom.sun.management.jmxremote.port=9999 :JMX遠(yuǎn)程連接端口
#-Dcom.sun.management.jmxremote.authenticate=false :不進(jìn)行身份認(rèn)證靖秩,任何用戶都可以連接
#-Dcom.sun.management.jmxremote.ssl=false :不使用ssl

保存退出须眷,重啟tomcat。

7.6.3沟突、使用VisualJVM連接遠(yuǎn)程tomcat

添加遠(yuǎn)程主機(jī):

1536681377182.png

在一個(gè)主機(jī)下可能會(huì)有很多的jvm需要監(jiān)控花颗,所以接下來要在該主機(jī)上添加需要監(jiān)控的jvm:

1536681543452.png
1536681575620.png

連接成功。使用方法和前面就一樣了惠拭,就可以和監(jiān)控本地jvm進(jìn)程一樣扩劝,監(jiān)控遠(yuǎn)程的tomcat進(jìn)程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末职辅,一起剝皮案震驚了整個(gè)濱河市棒呛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌域携,老刑警劉巖簇秒,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秀鞭,居然都是意外死亡趋观,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門气筋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拆内,“玉大人,你說我怎么就攤上這事宠默◆锘校” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵搀矫,是天一觀的道長抹沪。 經(jīng)常有香客問我,道長瓤球,這世上最難降的妖魔是什么融欧? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮卦羡,結(jié)果婚禮上噪馏,老公的妹妹穿的比我還像新娘麦到。我一直安慰自己,他們只是感情好欠肾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布瓶颠。 她就那樣靜靜地躺著,像睡著了一般刺桃。 火紅的嫁衣襯著肌膚如雪粹淋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天瑟慈,我揣著相機(jī)與錄音桃移,去河邊找鬼。 笑死葛碧,一個(gè)胖子當(dāng)著我的面吹牛借杰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吹埠,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼第步,長吁一口氣:“原來是場噩夢啊……” “哼疮装!你這毒婦竟也來了缘琅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤廓推,失蹤者是張志新(化名)和其女友劉穎刷袍,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體樊展,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呻纹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了专缠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雷酪。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涝婉,靈堂內(nèi)的尸體忽然破棺而出哥力,到底是詐尸還是另有隱情,我是刑警寧澤墩弯,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布吩跋,位于F島的核電站,受9級(jí)特大地震影響渔工,放射性物質(zhì)發(fā)生泄漏锌钮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一引矩、第九天 我趴在偏房一處隱蔽的房頂上張望梁丘。 院中可真熱鬧侵浸,春花似錦、人聲如沸氛谜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽混蔼。三九已至履腋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惭嚣,已是汗流浹背遵湖。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晚吞,地道東北人延旧。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像槽地,于是被迫代替她去往敵國和親迁沫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355