<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    瘋狂

    STANDING ON THE SHOULDERS OF GIANTS
    posts - 481, comments - 486, trackbacks - 0, articles - 1
      BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

    JAVA Thread Dump 分析綜述

    Posted on 2015-12-14 18:04 瘋狂 閱讀(4620) 評(píng)論(0)  編輯  收藏 所屬分類: java 、linux 、方法論架構(gòu)
    轉(zhuǎn)載自:http://blog.csdn.net/rachel_luo/article/details/8920596

    最近在做性能測(cè)試,需要對(duì)線程堆棧進(jìn)行分析,在網(wǎng)上收集了一些資料,學(xué)習(xí)完后,將相關(guān)知識(shí)整理在一起,輸出文章如下。


    一、Thread Dump介紹

    1.1什么是Thread Dump?

    Thread Dump是非常有用的診斷Java應(yīng)用問(wèn)題的工具。每一個(gè)Java虛擬機(jī)都有及時(shí)生成所有線程在某一點(diǎn)狀態(tài)的thread-dump的能力,雖然各個(gè) Java虛擬機(jī)打印的thread dump略有不同,但是大多都提供了當(dāng)前活動(dòng)線程的快照,及JVM中所有Java線程的堆棧跟蹤信息,堆棧信息一般包含完整的類名及所執(zhí)行的方法,如果可能的話還有源代碼的行數(shù)。

     

    1.2 Thread Dump特點(diǎn)

    1. 能在各種操作系統(tǒng)下使用

    2. 能在各種Java應(yīng)用服務(wù)器下使用

    3. 可以在生產(chǎn)環(huán)境下使用而不影響系統(tǒng)的性能

    4. 可以將問(wèn)題直接定位到應(yīng)用程序的代碼行上

     

    1.3 Thread Dump 能診斷的問(wèn)題

    1. 查找內(nèi)存泄露,常見(jiàn)的是程序里load大量的數(shù)據(jù)到緩存;

    2. 發(fā)現(xiàn)死鎖線程;

     

    1.4如何抓取Thread Dump

    一般當(dāng)服務(wù)器掛起,崩潰或者性能底下時(shí),就需要抓取服務(wù)器的線程堆棧(Thread Dump)用于后續(xù)的分析. 在實(shí)際運(yùn)行中,往往一次 dump的信息,還不足以確認(rèn)問(wèn)題。為了反映線程狀態(tài)的動(dòng)態(tài)變化,需要接連多次做threaddump,每次間隔10-20s,建議至少產(chǎn)生三次 dump信息,如果每次 dump都指向同一個(gè)問(wèn)題,我們才確定問(wèn)題的典型性。

     

    有很多方式可用于獲取ThreadDump, 下面列出一部分獲取方式:

    操作系統(tǒng)命令獲取ThreadDump:

    Windows:

    1.轉(zhuǎn)向服務(wù)器的標(biāo)準(zhǔn)輸出窗口并按下Control + Break組合鍵, 之后需要將線程堆棧復(fù)制到文件中;

    UNIX/ Linux:

    首先查找到服務(wù)器的進(jìn)程號(hào)(process id), 然后獲取線程堆棧.

    1. ps –ef  | grep java

    2. kill -3 <pid>

    注意:一定要謹(jǐn)慎, 一步不慎就可能讓服務(wù)器進(jìn)程被殺死。kill -9 命令會(huì)殺死進(jìn)程。

     

    JVM 自帶的工具獲取線程堆棧:

    JDK自帶命令行工具獲取PID,再獲取ThreadDump:

    1. jps 或 ps –ef|grepjava (獲取PID)

    2. jstack [-l ]<pid> | tee -a jstack.log  (獲取ThreadDump)

     

    二、java線程的狀態(tài)轉(zhuǎn)換介紹(為后續(xù)分析做準(zhǔn)備)


    2.1 新建狀態(tài)(New)

    用new語(yǔ)句創(chuàng)建的線程處于新建狀態(tài),此時(shí)它和其他Java對(duì)象一樣,僅僅在堆區(qū)中被分配了內(nèi)存。

    2.2 就緒狀態(tài)(Runnable)

    當(dāng)一個(gè)線程對(duì)象創(chuàng)建后,其他線程調(diào)用它的start()方法,該線程就進(jìn)入就緒狀態(tài),Java虛擬機(jī)會(huì)為它創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器。處于這個(gè)狀態(tài)的線程位于可運(yùn)行池中,等待獲得CPU的使用權(quán)。

    2.3 運(yùn)行狀態(tài)(Running)

    處于這個(gè)狀態(tài)的線程占用CPU,執(zhí)行程序代碼。只有處于就緒狀態(tài)的線程才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。

    2.4 阻塞狀態(tài)(Blocked)

    阻塞狀態(tài)是指線程因?yàn)槟承┰蚍艞塁PU,暫時(shí)停止運(yùn)行。當(dāng)線程處于阻塞狀態(tài)時(shí),Java虛擬機(jī)不會(huì)給線程分配CPU。直到線程重新進(jìn)入就緒狀態(tài),它才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。

    阻塞狀態(tài)可分為以下3種:

     1)位于對(duì)象等待池中的阻塞狀態(tài)(Blocked in object’s wait pool):當(dāng)線程處于運(yùn)行狀態(tài)時(shí),如果執(zhí)行了某個(gè)對(duì)象的wait()方法,Java虛擬機(jī)就會(huì)把線程放到這個(gè)對(duì)象的等待池中,這涉及到“線程通信”的內(nèi)容。

     2)位于對(duì)象鎖池中的阻塞狀態(tài)(Blocked in object’s lock pool):當(dāng)線程處于運(yùn)行狀態(tài)時(shí),試圖獲得某個(gè)對(duì)象的同步鎖時(shí),如果該對(duì)象的同步鎖已經(jīng)被其他線程占用,Java虛擬機(jī)就會(huì)把這個(gè)線程放到這個(gè)對(duì)象的鎖池中,這涉及到“線程同步”的內(nèi)容。

     3)其他阻塞狀態(tài)(Otherwise Blocked):當(dāng)前線程執(zhí)行了sleep()方法,或者調(diào)用了其他線程的join()方法,或者發(fā)出了I/O請(qǐng)求時(shí),就會(huì)進(jìn)入這個(gè)狀態(tài)。

    2.5 死亡狀態(tài)(Dead)

    當(dāng)線程退出run()方法時(shí),就進(jìn)入死亡狀態(tài),該線程結(jié)束生命周期。

     

    三、Thread Dump分析

    通過(guò)前面1.4部分的方法,獲取Thread Dump信息后,對(duì)其進(jìn)行分析;

    3.1 首先介紹一下Thread Dump信息的各個(gè)部分

    頭部信息:

    時(shí)間,jvm信息

    2011-11-02 19:05:06  

    Full thread dump Java HotSpot(TM) Server VM (16.3-b01 mixed mode):  

     

    線程info信息塊:

    1. "Timer-0" daemon prio=10tid=0xac190c00 nid=0xaef in Object.wait() [0xae77d000]

    2.  java.lang.Thread.State: TIMED_WAITING (on object monitor)

    3.  atjava.lang.Object.wait(Native Method)

    4.  -waiting on <0xb3885f60> (a java.util.TaskQueue)     ###繼續(xù)wait 

    5.  atjava.util.TimerThread.mainLoop(Timer.java:509)

    6.  -locked <0xb3885f60> (a java.util.TaskQueue)         ###已經(jīng)locked

    7.  atjava.util.TimerThread.run(Timer.java:462)

    線程名稱:Timer-0
    線程類型:daemon
    * 優(yōu)先級(jí): 10,默認(rèn)是5
    * jvm線程id:tid=0xac190c00,jvm內(nèi)部線程的唯一標(biāo)識(shí)(通過(guò)java.lang.Thread.getId()獲取,通常用自增方式實(shí)現(xiàn)。)

    對(duì)應(yīng)系統(tǒng)線程id(NativeThread ID):nid=0xaef,和top命令查看的線程pid對(duì)應(yīng),不過(guò)一個(gè)是10進(jìn)制,一個(gè)是16進(jìn)制。(通過(guò)命令:top -H -p pid,可以查看該進(jìn)程的所有線程信息)
    線程狀態(tài):in Object.wait().
    * 起始棧地址:[0xae77d000]

    Java thread statck trace:是上面2-7行的信息。到目前為止這是最重要的數(shù)據(jù),Java stack trace提供了大部分信息來(lái)精確定位問(wèn)題根源。

     

    對(duì)于thread dump信息,主要關(guān)注的是線程的狀態(tài)和其執(zhí)行堆?!,F(xiàn)在針對(duì)這兩個(gè)重點(diǎn)部分進(jìn)行講解:

    1)Java thread statck trace詳解:

    堆棧信息應(yīng)該逆向解讀:程序先執(zhí)行的是第7行,然后是第6行,依次類推。

    - locked <0xb3885f60> (a java.util.ArrayList)
    - waiting on <0xb3885f60> (a java.util.ArrayList) 

    也就是說(shuō)對(duì)象先上鎖,鎖住對(duì)象0xb3885f60,然后釋放該對(duì)象鎖,進(jìn)入waiting狀態(tài)。

    為啥會(huì)出現(xiàn)這樣的情況呢?看看下面的java代碼示例,就會(huì)明白:

    synchronized(obj) {  

           .........  

           obj.wait();  

           .........  

    }  

    在堆棧的第一行信息中,進(jìn)一步標(biāo)明了線程在代碼級(jí)的狀態(tài),例如:

    java.lang.Thread.State: TIMED_WAITING (parking)

    解釋如下:

    |blocked|

    This thread tried to enter asynchronized block, but the lock was taken by another thread. This thread isblocked until the lock gets released.

    |blocked (on thin lock)|

    This is the same state asblocked, but the lock in question is a thin lock.

    |waiting|

    This thread calledObject.wait() on an object. The thread will remain there until some otherthread sends a notification to that object.

    |sleeping|

    This thread calledjava.lang.Thread.sleep().

    |parked|

    This thread calledjava.util.concurrent.locks.LockSupport.park().

    |suspended|

    The thread's execution wassuspended by java.lang.Thread.suspend() or a JVMTI agent call.

    2) 線程狀態(tài)詳解:

    Runnable
    _The thread is either running or ready to run when it gets its CPU turn._

    Wait on condition
    _The thread is either sleeping or waiting to be notified by another thread._
        該狀態(tài)出現(xiàn)在線程等待某個(gè)條件的發(fā)生或者sleep。具體是什么原因,可以結(jié)合 stacktrace來(lái)分析。最常見(jiàn)的情況是線程在等待網(wǎng)絡(luò)的讀寫,比如當(dāng)網(wǎng)絡(luò)數(shù)據(jù)沒(méi)有準(zhǔn)備好讀時(shí),線程處于這種等待狀態(tài),而一旦有數(shù)據(jù)準(zhǔn)備好讀之后,線程會(huì)重新激活,讀取并處理數(shù)據(jù)。在Java引入 New IO之前,對(duì)于每個(gè)網(wǎng)絡(luò)連接,都有一個(gè)對(duì)應(yīng)的線程來(lái)處理網(wǎng)絡(luò)的讀寫操作,即使沒(méi)有可讀寫的數(shù)據(jù),線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費(fèi),而且給操作系統(tǒng)的線程調(diào)度也帶來(lái)壓力。在 New IO里采用了新的機(jī)制,編寫的服務(wù)器程序的性能和可擴(kuò)展性都得到提高。
        如果發(fā)現(xiàn)有大量的線程都處在 Wait on condition,從線程 stack看, 正等待網(wǎng)絡(luò)讀寫,這可能是一個(gè)網(wǎng)絡(luò)瓶頸的征兆。因?yàn)榫W(wǎng)絡(luò)阻塞導(dǎo)致線程無(wú)法執(zhí)行。一種情況是網(wǎng)絡(luò)非常忙,幾乎消耗了所有的帶寬,仍然有大量數(shù)據(jù)等待網(wǎng)絡(luò)讀寫;另一種情況也可能是網(wǎng)絡(luò)空閑,但由于路由等問(wèn)題,導(dǎo)致包無(wú)法正常的到達(dá)。所以要結(jié)合系統(tǒng)的一些性能觀察工具來(lái)綜合分析,比如 netstat統(tǒng)計(jì)單位時(shí)間的發(fā)送包的數(shù)目,看是否很明顯超過(guò)了所在網(wǎng)絡(luò)帶寬的限制;觀察cpu的利用率,看系統(tǒng)態(tài)的CPU時(shí)間是否明顯大于用戶態(tài)的CPU時(shí)間;如果程序運(yùn)行在 Solaris 10平臺(tái)上,可以用dtrace工具看系統(tǒng)調(diào)用的情況,如果觀察到 read/write的系統(tǒng)調(diào)用的次數(shù)或者運(yùn)行時(shí)間遙遙領(lǐng)先;這些都指向由于網(wǎng)絡(luò)帶寬所限導(dǎo)致的網(wǎng)絡(luò)瓶頸。另外一種出現(xiàn) Wait on condition的常見(jiàn)情況是該線程在 sleep,等待 sleep的時(shí)間到了,將被喚醒。


    Waiting for Monitor Entry and in Object.wait()

    _The thread is waiting to getthe lock for an object (some other thread may be holding the lock). Thishappens if two or more threads try to execute synchronized code. Note that thelock is always for an object and not for individual methods._

        在多線程的 JAVA程序中,實(shí)現(xiàn)線程之間的同步,就要說(shuō)說(shuō) Monitor。 Monitor是Java中用以實(shí)現(xiàn)線程之間的互斥與協(xié)作的主要手段,它可以看成是對(duì)象或者 Class的鎖。每一個(gè)對(duì)象都有,也僅有一個(gè) monitor。每個(gè) Monitor在某個(gè)時(shí)刻,只能被一個(gè)線程擁有,該線程就是 “ActiveThread”,而其它線程都是 “Waiting Thread”,分別在兩個(gè)隊(duì)列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的線程狀態(tài)是 “Waiting for monitorentry”,而在 “Wait Set”中等待的線程狀態(tài)是“in Object.wait()”。
       先看 “Entry Set”里面的線程。我們稱被 synchronized保護(hù)起來(lái)的代碼段為臨界區(qū)。當(dāng)一個(gè)線程申請(qǐng)進(jìn)入臨界區(qū)時(shí),它就進(jìn)入了 “Entry Set”隊(duì)列。對(duì)應(yīng)的 code就像:
    synchronized(obj) {
        .........
    }
    這時(shí)有兩種可能性:
        該 monitor不被其它線程擁有, Entry Set里面也沒(méi)有其它等待線程。本線程即成為相應(yīng)類或者對(duì)象的 Monitor的 Owner,執(zhí)行臨界區(qū)的代碼。
        該 monitor被其它線程擁有,本線程在 Entry Set隊(duì)列中等待。 
        在第一種情況下,線程將處于 “Runnable”的狀態(tài),而第二種情況下,線程 DUMP會(huì)顯示處于 “waiting for monitor entry”。

    臨界區(qū)的設(shè)置,是為了保證其內(nèi)部的代碼執(zhí)行的原子性和完整性。但是因?yàn)榕R界區(qū)在任何時(shí)間只允許線程串行通過(guò),這和我們多線程的程序的初衷是相反的。如果在多線程的程序中,大量使用 synchronized,或者不適當(dāng)?shù)氖褂昧怂瑫?huì)造成大量線程在臨界區(qū)的入口等待,造成系統(tǒng)的性能大幅下降。如果在線程 DUMP中發(fā)現(xiàn)了這個(gè)情況,應(yīng)該審查源碼,改進(jìn)程序。

        再看“Wait Set”里面的線程。當(dāng)線程獲得了 Monitor,進(jìn)入了臨界區(qū)之后,如果發(fā)現(xiàn)線程繼續(xù)運(yùn)行的條件沒(méi)有滿足,它則調(diào)用對(duì)象(一般就是被 synchronized 的對(duì)象)的 wait() 方法,放棄 Monitor,進(jìn)入 “Wait Set”隊(duì)列。只有當(dāng)別的線程在該對(duì)象上調(diào)用了 notify() 或者 notifyAll(),“Wait Set”隊(duì)列中線程才得到機(jī)會(huì)去競(jìng)爭(zhēng),但是只有一個(gè)線程獲得對(duì)象的Monitor,恢復(fù)到運(yùn)行態(tài)。在 “Wait Set”中的線程, DUMP中表現(xiàn)為: in Object.wait()。

    一般,Cpu很忙時(shí),則關(guān)注runnable的線程,Cpu很閑時(shí),則關(guān)注waiting for monitor entry的線程。

     

    3.2 JVM線程介紹

    在Thread Dump中,有一些 JVM內(nèi)部的后臺(tái)線程,來(lái)執(zhí)行譬如垃圾回收,或者低內(nèi)存的檢測(cè)等等任務(wù),這些線程往往在 JVM初始化的時(shí)候就存在,如下所示:

    HotSpot VM Thread

    被HotSpot VM管理的內(nèi)部線程為了完成內(nèi)部本地操作,一般來(lái)說(shuō)不需要擔(dān)心它們,除非CPU很高。

    "VM Periodic Task Thread" prio=10tid=0xad909400 nid=0xaed waiting on condition

     

    HotSpot GC Thread

    當(dāng)使用HotSpot parallel GC,HotSpot VM默認(rèn)創(chuàng)建一定數(shù)目的GC thread。

    "GC task thread#0 (ParallelGC)"prio=10 tid=0xf690b400 nid=0xade runnable

    "GC task thread#1 (ParallelGC)"prio=10 tid=0xf690cc00 nid=0xadf runnable

    "GC task thread#2 (ParallelGC)"prio=10 tid=0xf690e000 nid=0xae0 runnable

    ……

    當(dāng)面對(duì)過(guò)多GC,內(nèi)存泄露等問(wèn)題時(shí),這些是關(guān)鍵的數(shù)據(jù)。使用native id,可以將從OS/Java進(jìn)程觀測(cè)到的高CPU與這些線程關(guān)聯(lián)起來(lái)。

     

    JNI global references count

    JNI global reference是基本的對(duì)象引用,從本地代碼到被Java GC管理的Java對(duì)象的引用。其角色是阻止仍然被本地代碼使用的對(duì)象集合,但在Java代碼中沒(méi)有引用。在探測(cè)JNI相關(guān)內(nèi)存泄露時(shí),關(guān)注JNI references很重要。如果你的程序直接使用JNI或使用第三方工具,如檢測(cè)工具,檢測(cè)本地內(nèi)存泄露。

    JNI global references: 832

     

    Java Heap utilization view

    從jdk1.6開(kāi)始在thread dump快照底部,可以找到崩潰點(diǎn)的內(nèi)存空間利用情況:YongGen,OldGen和PermGen。目前我測(cè)試的系統(tǒng)導(dǎo)出的thread dump,還未見(jiàn)到這一部分內(nèi)容(sun jdk1.6)。以下例子,摘自他人文章:

    Heap  

     PSYoungGen      total 466944K, used 178734K [0xffffffff45c00000, 0xffffffff70800000, 0xffffffff70800000)  

      eden space 233472K, 76% used [0xffffffff45c00000,0xffffffff50ab7c50,0xffffffff54000000)  

      from space 233472K, 0% used [0xffffffff62400000,0xffffffff62400000,0xffffffff70800000)  

      to   space 233472K, 0% used [0xffffffff54000000,0xffffffff54000000,0xffffffff62400000)  

     PSOldGen        total 1400832K, used 1400831K [0xfffffffef0400000, 0xffffffff45c00000, 0xffffffff45c00000)  

      object space 1400832K, 99% used [0xfffffffef0400000,0xffffffff45bfffb8,0xffffffff45c00000)  

     PSPermGen       total 262144K, used 248475K [0xfffffffed0400000, 0xfffffffee0400000, 0xfffffffef0400000)  

      object space 262144K, 94% used [0xfffffffed0400000,0xfffffffedf6a6f08,0xfffffffee0400000)      

     

     還有一些其他的線程(如下),不一一介紹了,有興趣,可查看文章最后的附件信息。

    "Low Memory Detector" daemon prio=10tid=0xad907400 nid=0xaec runnable [0x00000000]

    "CompilerThread1" daemon prio=10tid=0xad905400 nid=0xaeb waiting on condition [0x00000000]

    "CompilerThread0" daemon prio=10tid=0xad903c00 nid=0xaea waiting on condition [0x00000000]

    "Signal Dispatcher" daemon prio=10tid=0xad902400 nid=0xae9 runnable [0x00000000]

    "Finalizer" daemon prio=10tid=0xf69eec00 nid=0xae8 in Object.wait() [0xaf17d000]

    "Reference Handler" daemon prio=10tid=0xf69ed800 nid=0xae7 in Object.wait() [0xae1e7000]

    "VM Thread" prio=10 tid=0xf69e9800nid=0xae6 runnable

     

    四、案例分析:

    4.1、使用方案

    cpu飆高,load高,響應(yīng)很慢

    方案:
    * 一個(gè)請(qǐng)求過(guò)程中多次dump

    * 對(duì)比多次dump文件的runnable線程,如果執(zhí)行的方法有比較大變化,說(shuō)明比較正常。如果在執(zhí)行同一個(gè)方法,就有一些問(wèn)題了。

    查找占用cpu最多的線程信息

    方案:
    * 使用命令: top -H -p pid(pid為被測(cè)系統(tǒng)的進(jìn)程號(hào)),找到導(dǎo)致cpu高的線程id。

    上述Top命令找到的線程id,對(duì)應(yīng)著dump thread信息中線程的nid,只不過(guò)一個(gè)是十進(jìn)制,一個(gè)是十六進(jìn)制。

    * 在thread dump中,根據(jù)top命令查找的線程id,查找對(duì)應(yīng)的線程堆棧信息。

    cpu使用率不高但是響應(yīng)很慢

    方案:
    * 進(jìn)行dump,查看是否有很多thread struck在了i/o、數(shù)據(jù)庫(kù)等地方,定位瓶頸原因。 

    請(qǐng)求無(wú)法響應(yīng)

    方案:
    * 多次dump,對(duì)比是否所有的runnable線程都一直在執(zhí)行相同的方法,如果是的,恭喜你,鎖住了!

     

    4.2 案例分析:

    1.死鎖:

    死鎖經(jīng)常表現(xiàn)為程序的停頓,或者不再響應(yīng)用戶的請(qǐng)求。從操作系統(tǒng)上觀察,對(duì)應(yīng)進(jìn)程的CPU占用率為零,很快會(huì)從top或prstat的輸出中消失。

        在thread dump中,會(huì)看到類似于這樣的信息:

    (圖 1)


     (圖2)

     

    說(shuō)明:

    (圖1)中有一個(gè)“Waiting formonitor entry”,可以看出,兩個(gè)線程各持有一個(gè)鎖,又在等待另一個(gè)鎖,很明顯這兩個(gè)線程互相持有對(duì)方正在等待的鎖。所以造成了死鎖現(xiàn)象;

    (圖2)中對(duì)死鎖的現(xiàn)象做了說(shuō)明,可以看到,是“DeadLockTest.java”的39行造成的死鎖現(xiàn)象。這樣就能到相應(yīng)的代碼下去查看,定位問(wèn)題。

     

    2.熱鎖

    熱鎖,也往往是導(dǎo)致系統(tǒng)性能瓶頸的主要因素。其表現(xiàn)特征為:由于多個(gè)線程對(duì)臨界區(qū),或者鎖的競(jìng)爭(zhēng),可能出現(xiàn):
        頻繁的線程的上下文切換:從操作系統(tǒng)對(duì)線程的調(diào)度來(lái)看,當(dāng)線程在等待資源而阻塞的時(shí)候,操作系統(tǒng)會(huì)將之切換出來(lái),放到等待的隊(duì)列,當(dāng)線程獲得資源之后,調(diào)度算法會(huì)將這個(gè)線程切換進(jìn)去,放到執(zhí)行隊(duì)列中。
        * 
    大量的系統(tǒng)調(diào)用:因?yàn)榫€程的上下文切換,以及熱鎖的競(jìng)爭(zhēng),或者臨界區(qū)的頻繁的進(jìn)出,都可能導(dǎo)致大量的系統(tǒng)調(diào)用。
        * 大部分CPU開(kāi)銷用在“系統(tǒng)態(tài) ”:線程上下文切換,和系統(tǒng)調(diào)用,都會(huì)導(dǎo)致 CPU在 “系統(tǒng)態(tài) ”運(yùn)行,換而言之,雖然系統(tǒng)很忙碌,但是 CPU用在 “用戶態(tài) ”的比例較小,應(yīng)用程序得不到充分的 CPU資源。 
        * 隨著 CPU數(shù)目的增多,系統(tǒng)的性能反而下降。因?yàn)镃PU數(shù)目多,同時(shí)運(yùn)行的線程就越多,可能就會(huì)造成更頻繁的線程上下文切換和系統(tǒng)態(tài)的CPU開(kāi)銷,從而導(dǎo)致更糟糕的性能。 
        上面的描述,都是一個(gè) scalability(可擴(kuò)展性)很差的系統(tǒng)的表現(xiàn)。從整體的性能指標(biāo)看,由于線程熱鎖的存在,程序的響應(yīng)時(shí)間會(huì)變長(zhǎng),吞吐量會(huì)降低。
        那么,怎么去了解 “熱鎖 ”出現(xiàn)在什么地方呢?一個(gè)重要的方法還是結(jié)合操作系統(tǒng)的各種工具觀察系統(tǒng)資源使用狀況,以及收集Java線程的DUMP信息,看線程都阻塞在什么方法上,了解原因,才能找到對(duì)應(yīng)的解決方法。
        我們?cè)?jīng)遇到過(guò)這樣的例子,程序運(yùn)行時(shí),出現(xiàn)了以上指出的各種現(xiàn)象,通過(guò)觀察操作系統(tǒng)的資源使用統(tǒng)計(jì)信息,以及線程 DUMP信息,確定了程序中熱鎖的存在,并發(fā)現(xiàn)大多數(shù)的線程狀態(tài)都是 Waitingfor monitor entry或者 Wait on monitor,且是阻塞在壓縮和解壓縮的方法上。后來(lái)采用第三方的壓縮包 javalib替代 JDK自帶的壓縮包后,系統(tǒng)的性能提高了幾倍。

     

    五、附件:

    JVM運(yùn)行過(guò)程中產(chǎn)生的一些比較重要的線程羅列如下:

     線程名稱

    所屬

    解釋說(shuō)明

    Attach Listener

    JVM

    Attach Listener 線程是負(fù)責(zé)接收到外部的命令,而對(duì)該命令進(jìn)行執(zhí)行的并且吧結(jié)果返回給發(fā)送者。通常我們會(huì)用一些命令去要求jvm給我們一些反饋信息,如:java -version、jmap、jstack等等。 如果該線程在jvm啟動(dòng)的時(shí)候沒(méi)有初始化,那么,則會(huì)在用戶第一次執(zhí)行jvm命令時(shí),得到啟動(dòng)。

    Signal Dispatcher

    JVM

    前面我們提到第一個(gè)Attach Listener線程的職責(zé)是接收外部jvm命令,當(dāng)命令接收成功后,會(huì)交給signal dispather 線程去進(jìn)行分發(fā)到各個(gè)不同的模塊處理命令,并且返回處理結(jié)果。 signal dispather線程也是在第一次接收外部jvm命令時(shí),進(jìn)行初始化工作。

    CompilerThread0

    JVM

    用來(lái)調(diào)用JITing,實(shí)時(shí)編譯裝卸class 。 通常,jvm會(huì)啟動(dòng)多個(gè)線程來(lái)處理這部分工作,線程名稱后面的數(shù)字也會(huì)累加,例如:CompilerThread1

    Concurrent Mark-Sweep GC Thread

    JVM

    并發(fā)標(biāo)記清除垃圾回收器(就是通常所說(shuō)的CMS GC)線程, 該線程主要針對(duì)于老年代垃圾回收。ps:?jiǎn)⒂迷摾厥掌?,需要在jvm啟動(dòng)參數(shù)中加上: -XX:+UseConcMarkSweepGC 

    DestroyJavaVM

    JVM

    執(zhí)行main()的線程在main執(zhí)行完后調(diào)用JNI中的 jni_DestroyJavaVM() 方法喚起DestroyJavaVM 線程。  

    JVM在 Jboss 服務(wù)器啟動(dòng)之后,就會(huì)喚起DestroyJavaVM線程,處于等待狀態(tài),等待其它線程(java線程和native線程)退出時(shí)通知它卸載JVM。線程退出時(shí),都會(huì)判斷自己當(dāng)前是否是整個(gè)JVM中最后一個(gè)非deamon線程,如果是,則通知DestroyJavaVM 線程卸載JVM。

    ps:

    擴(kuò)展一下:

    1.如果線程退出時(shí)判斷自己不為最后一個(gè)非deamon線程,那么調(diào)用thread->exit(false) ,并在其中拋出thread_end事件,jvm不退出。

    2.如果線程退出時(shí)判斷自己為最后一個(gè)非deamon線程,那么調(diào)用before_exit() 方法,拋出兩個(gè)事件: 

    事件1:thread_end 線程結(jié)束事件;

    事件2:VM的death事件。

    然后調(diào)用thread->exit(true) 方法,接下來(lái)把線程從active list卸下,刪除線程等等一系列工作執(zhí)行完成后,則通知正在等待的DestroyJavaVM 線程執(zhí)行卸載JVM操作。

    ContainerBackgroundProcessor 線程

    JBOSS

    它是一個(gè)守護(hù)線程, 在jboss服務(wù)器在啟動(dòng)的時(shí)候就初始化了,主要工作是定期去檢查有沒(méi)有Session過(guò)期.過(guò)期則清除.

    參考:

    http://liudeh-009.iteye.com/blog/1584876

    Dispatcher-Thread-3  線程

    Log4j

    Log4j具有異步打印日志的功能,需要異步打印日志的Appender都需要注冊(cè)到 AsyncAppender對(duì)象里面去,由AsyncAppender進(jìn)行監(jiān)聽(tīng),決定何時(shí)觸發(fā)日志打印操作。 AsyncAppender如果監(jiān)聽(tīng)到它管轄范圍內(nèi)的Appender有打印日志的操作,則給這個(gè)Appender生成一個(gè)相應(yīng)的event,并將該event保存在一個(gè)buffuer區(qū)域內(nèi)。 

    Dispatcher-Thread-3線程負(fù)責(zé)判斷這個(gè)event緩存區(qū)是否已經(jīng)滿了,如果已經(jīng)滿了,則將緩存區(qū)內(nèi)的所有event分發(fā)到Appender容器里面去,那些注冊(cè)上來(lái)的Appender收到自己的event后,則開(kāi)始處理自己的日志打印工作。 Dispatcher-Thread-3線程是一個(gè)守護(hù)線程。

    Finalizer線程

    JVM

    這個(gè)線程也是在main線程之后創(chuàng)建的,其優(yōu)先級(jí)為10,主要用于在垃圾收集前,調(diào)用對(duì)象的finalize()方法;關(guān)于Finalizer線程的幾點(diǎn):

    1) 只有當(dāng)開(kāi)始一輪垃圾收集時(shí),才會(huì)開(kāi)始調(diào)用finalize()方法;因此并不是所有對(duì)象的finalize()方法都會(huì)被執(zhí)行;

    2) 該線程也是daemon線程,因此如果虛擬機(jī)中沒(méi)有其他非daemon線程,不管該線程有沒(méi)有執(zhí)行完finalize()方法,JVM也會(huì)退出;

    3) JVM在垃圾收集時(shí)會(huì)將失去引用的對(duì)象包裝成Finalizer對(duì)象(Reference的實(shí)現(xiàn)),并放入ReferenceQueue,由Finalizer線程來(lái)處理;最后將該Finalizer對(duì)象的引用置為null,由垃圾收集器來(lái)回收;

    4) JVM為什么要單獨(dú)用一個(gè)線程來(lái)執(zhí)行finalize()方法呢?如果JVM的垃圾收集線程自己來(lái)做,很有可能由于在finalize()方法中誤操作導(dǎo)致GC線程停止或不可控,這對(duì)GC線程來(lái)說(shuō)是一種災(zāi)難;

    Gang worker#0

    JVM

    JVM 用于做新生代垃圾回收(monir gc)的一個(gè)線程。#號(hào)后面是線程編號(hào),例如:Gang worker#1

    GC Daemon

    JVM

    GC Daemon 線程是JVM為RMI提供遠(yuǎn)程分布式GC使用的,GC Daemon線程里面會(huì)主動(dòng)調(diào)用System.gc()方法,對(duì)服務(wù)器進(jìn)行Full GC。 其初衷是當(dāng) RMI 服務(wù)器返回一個(gè)對(duì)象到其客戶機(jī)(遠(yuǎn)程方法的調(diào)用方)時(shí),其跟蹤遠(yuǎn)程對(duì)象在客戶機(jī)中的使用。當(dāng)再?zèng)]有更多的對(duì)客戶機(jī)上遠(yuǎn)程對(duì)象的引用時(shí),或者如果引用的“租借”過(guò)期并且沒(méi)有更新,服務(wù)器將垃圾回收遠(yuǎn)程對(duì)象。

    不過(guò),我們現(xiàn)在jvm啟動(dòng)參數(shù)都加上了-XX:+DisableExplicitGC配置,所以,這個(gè)線程只有打醬油的份了。

    IdleRemover

    JBOSS

    Jboss連接池有一個(gè)最小值, 該線程每過(guò)一段時(shí)間都會(huì)被Jboss喚起,用于檢查和銷毀連接池中空閑和無(wú)效的連接,直到剩余的連接數(shù)小于等于它的最小值。

    Java2D Disposer

    JVM

    這個(gè)線程主要服務(wù)于awt的各個(gè)組件。 說(shuō)起該線程的主要工作職責(zé)前,需要先介紹一下Disposer類是干嘛的。 Disposer提供一個(gè)addRecord方法。 如果你想在一個(gè)對(duì)象被銷毀前再做一些善后工作,那么,你可以調(diào)用Disposer#addRecord方法,將這個(gè)對(duì)象和一個(gè)自定義的DisposerRecord接口實(shí)現(xiàn)類,一起傳入進(jìn)去,進(jìn)行注冊(cè)。  

    Disposer類會(huì)喚起“Java2D Disposer”線程,該線程會(huì)掃描已注冊(cè)的這些對(duì)象是否要被回收了,如果是,則調(diào)用該對(duì)象對(duì)應(yīng)的DisposerRecord實(shí)現(xiàn)類里面的dispose方法。

    Disposer實(shí)際上不限于在awt應(yīng)用場(chǎng)景,只是awt里面的很多組件需要訪問(wèn)很多操作系統(tǒng)資源,所以,這些組件在被回收時(shí),需要先釋放這些資源。

    InsttoolCacheScheduler_QuartzSchedulerThread

    Quartz

    InsttoolCacheScheduler_QuartzSchedulerThread是Quartz的主線程,它主要負(fù)責(zé)實(shí)時(shí)的獲取下一個(gè)時(shí)間點(diǎn)要觸發(fā)的觸發(fā)器,然后執(zhí)行觸發(fā)器相關(guān)聯(lián)的作業(yè) 。 

    原理大致如下:

       Spring和Quartz結(jié)合使用的場(chǎng)景下,Spring IOC容器初始化時(shí)會(huì)創(chuàng)建并初始化Quartz線程池(TreadPool),并啟動(dòng)它。剛啟動(dòng)時(shí)線程池中每個(gè)線程都處于等待狀態(tài),等待外界給他分配Runnable(持有作業(yè)對(duì)象的線程)。

       繼而接著初始化并啟動(dòng)Quartz的主線程

    (InsttoolCacheScheduler_QuartzSchedulerThread),該線程自啟動(dòng)后就會(huì)處于等待狀態(tài)。等待外界給出工作信號(hào)之后,該主線程的run方法才實(shí)質(zhì)上開(kāi)始工作。run中會(huì)獲取JobStore中下一次要觸發(fā)的作業(yè),拿到之后會(huì)一直等待到該作業(yè)的真正觸發(fā)時(shí)間,然后將該作業(yè)包裝成一個(gè)JobRunShell對(duì)象(該對(duì)象實(shí)現(xiàn)了Runnable接口,其實(shí)看是上面TreadPool中等待外界分配給他的Runnable),然后將剛創(chuàng)建的JobRunShell交給線程池,由線程池負(fù)責(zé)執(zhí)行作業(yè)。

    線程池收到Runnable后,從線程池一個(gè)線程啟動(dòng)Runnable,反射調(diào)用JobRunShell中的run方法,run方法執(zhí)行完成之后, TreadPool將該線程回收至空閑線程中。

    InsttoolCacheScheduler_Worker-2

    Quartz

    InsttoolCacheScheduler_Worker-2線程就是ThreadPool線程的一個(gè)簡(jiǎn)單實(shí)現(xiàn),它主要負(fù)責(zé)分配線程資源去執(zhí)行

    InsttoolCacheScheduler_QuartzSchedulerThread線程交給它的調(diào)度任務(wù)(也就是JobRunShell)。

    JBossLifeThread

    Jboss

    Jboss主線程啟動(dòng)成功,應(yīng)用程序部署完畢之后將JBossLifeThread線程實(shí)例化并且start,JBossLifeThread線程啟動(dòng)成功之后就處于等待狀態(tài),以保持Jboss Java進(jìn)程處于存活中。  所得比較通俗一點(diǎn),就是Jboss啟動(dòng)流程執(zhí)行完畢之后,為什么沒(méi)有結(jié)束? 就是因?yàn)橛羞@個(gè)線程hold主了它。

    JBoss System Threads(1)-1

    Jboss

    該線程是一個(gè)socket服務(wù),默認(rèn)端口號(hào)為: 1099。

    主要用于接收外部naming service(Jboss  JNDI)請(qǐng)求。

    JCA PoolFiller

    Jboss

    該線程主要為JBoss內(nèi)部提供連接池的托管。 

    簡(jiǎn)單介紹一下工作原理 :

    Jboss內(nèi)部凡是有遠(yuǎn)程連接需求的類,都需要實(shí)現(xiàn)

    ManagedConnectionFactory接口,例如需要做JDBC連接的

    XAManagedConnectionFactory對(duì)象,就實(shí)現(xiàn)了該接口。

    然后將XAManagedConnectionFactory對(duì)象,

    還有其它信息一起包裝到

    InternalManagedConnectionPool

    對(duì)象里面,接著將

    InternalManagedConnectionPool

    交給PoolFiller對(duì)象里面的列隊(duì)進(jìn)行管理。  

    JCA PoolFiller線程會(huì)定期判斷列隊(duì)內(nèi)是否有需要?jiǎng)?chuàng)建和管理的

    InternalManagedConnectionPool

    對(duì)象,如果有的話,則調(diào)用該對(duì)象的fillToMin方法, 觸發(fā)它去創(chuàng)建相應(yīng)的遠(yuǎn)程連接,并且將這個(gè)連接維護(hù)到它相應(yīng)的連接池里面去。

    JDWP Event Helper Thread

    JVM

    JDWP是通訊交互協(xié)議,它定義了調(diào)試器和被調(diào)試程序之間傳遞信息的格式。它詳細(xì)完整地定義了請(qǐng)求命令、回應(yīng)數(shù)據(jù)和錯(cuò)誤代碼,保證了前端和后端的JVMTI和JDI的通信通暢。  該線程主要負(fù)責(zé)將JDI事件映射成JVMTI信號(hào),以達(dá)到調(diào)試過(guò)程中操作JVM的目的。   

    JDWP Transport Listener:

     dt_socket

    JVM

    該線程是一個(gè)Java Debugger的監(jiān)聽(tīng)器線程,負(fù)責(zé)受理客戶端的debug請(qǐng)求。 通常我們習(xí)慣將它的監(jiān)聽(tīng)端口設(shè)置為8787。

    Low Memory Detector

    JVM

    這個(gè)線程是負(fù)責(zé)對(duì)可使用內(nèi)存進(jìn)行檢測(cè),如果發(fā)現(xiàn)可用內(nèi)存低,分配新的內(nèi)存空間。

    process reaper

    JVM

    該線程負(fù)責(zé)去執(zhí)行一個(gè) OS 命令行的操作。

    Reference Handler

    JVM

    JVM在創(chuàng)建main線程后就創(chuàng)建Reference Handler線程,其優(yōu)先級(jí)最高,為10,它主要用于處理引用對(duì)象本身(軟引用、弱引用、虛引用)的垃圾回收問(wèn)題 。

    Surrogate Locker Thread (CMS)

    JVM

    這個(gè)線程主要用于配合CMS垃圾回收器使用,它是一個(gè)守護(hù)線程,其主要負(fù)責(zé)處理GC過(guò)程中,Java層的Reference(指軟引用、弱引用等等)與jvm 內(nèi)部層面的對(duì)象狀態(tài)同步。 這里對(duì)它們的實(shí)現(xiàn)稍微做一下介紹:這里拿 WeakHashMap做例子,將一些關(guān)鍵點(diǎn)先列出來(lái)(我們后面會(huì)將這些關(guān)鍵點(diǎn)全部串起來(lái)):

    1.我們知道HashMap用Entry[]數(shù)組來(lái)存儲(chǔ)數(shù)據(jù)的,WeakHashMap也不例外, 內(nèi)部有一個(gè)Entry[]數(shù)組。

    2. WeakHashMap的Entry比較特殊,它的繼承體系結(jié)構(gòu)為

    Entry->WeakReference->Reference 。

    3.Reference 里面有一個(gè)全局鎖對(duì)象:Lock,

    它也被稱為pending_lock.注意:它是靜態(tài)對(duì)象。

    4. Reference  里面有一個(gè)靜態(tài)變量:pending。

    5. Reference里面有一個(gè)靜態(tài)內(nèi)部類:ReferenceHandler的線程,它在static塊里面被初始化并且啟動(dòng),啟動(dòng)完成后處于wait狀態(tài),它在一個(gè)Lock同步鎖模塊中等待。

    6.另外,WeakHashMap里面還實(shí)例化了一個(gè)ReferenceQueue列隊(duì),這個(gè)列隊(duì)的作用,后面會(huì)提到。

    7.上面關(guān)鍵點(diǎn)就介紹完畢了,下面我們把他們串起來(lái)。

    假設(shè),WeakHashMap對(duì)象里面已經(jīng)保存了很多對(duì)象的引用。

    JVM 在進(jìn)行CMS GC的時(shí)候,會(huì)創(chuàng)建一個(gè)ConcurrentMarkSweepThread(簡(jiǎn)稱CMST)線程去進(jìn)行GC,ConcurrentMarkSweepThread線程被創(chuàng)建的同時(shí)會(huì)創(chuàng)建一個(gè)SurrogateLockerThread(簡(jiǎn)稱SLT)線程并且啟動(dòng)它,SLT啟動(dòng)之后,處于等待階段。CMST開(kāi)始GC時(shí),會(huì)發(fā)一個(gè)消息給SLT讓它去獲取Java層Reference對(duì)象的全局鎖:Lock。 直到CMS GC完畢之后,JVM 會(huì)將WeakHashMap中所有被回收的對(duì)象所屬的WeakReference容器對(duì)象放入到Reference 的pending屬性當(dāng)中(每次GC完畢之后,pending屬性基本上都不會(huì)為null了),然后通知SLT釋放并且notify全局鎖:Lock。此時(shí)激活了ReferenceHandler線程的run方法,使其脫離wait狀態(tài),開(kāi)始工作了。ReferenceHandler這個(gè)線程會(huì)將pending中的所有WeakReference對(duì)象都移動(dòng)到它們各自的列隊(duì)當(dāng)中,比如當(dāng)前這個(gè)WeakReference屬于某個(gè)WeakHashMap對(duì)象,那么它就會(huì)被放入相應(yīng)的ReferenceQueue列隊(duì)里面(該列隊(duì)是鏈表結(jié)構(gòu))。 當(dāng)我們下次從WeakHashMap對(duì)象里面get、put數(shù)據(jù)或者調(diào)用size方法的時(shí)候,WeakHashMap就會(huì)將ReferenceQueue列隊(duì)中的WeakReference依依poll出來(lái)去和Entry[]數(shù)據(jù)做比較,如果發(fā)現(xiàn)相同的,則說(shuō)明這個(gè)Entry所保存的對(duì)象已經(jīng)被GC掉了,那么將Entry[]內(nèi)的Entry對(duì)象剔除掉。

    taskObjectTimerFactory

    JVM

    顧名思義,該線程就是用來(lái)執(zhí)行任務(wù)的。 當(dāng)我們把一個(gè)認(rèn)為交給Timer對(duì)象,并且告訴它執(zhí)行時(shí)間,周期時(shí)間后,Timer就會(huì)將該任務(wù)放入任務(wù)列隊(duì),并且通知taskObjectTimerFactory線程去處理任務(wù),taskObjectTimerFactory線程會(huì)將狀態(tài)為取消的任務(wù)從任務(wù)列隊(duì)中移除,如果任務(wù)是非重復(fù)執(zhí)行類型的,則在執(zhí)行完該任務(wù)后,將它從任務(wù)列隊(duì)中移除,如果該任務(wù)是需要重復(fù)執(zhí)行的,則計(jì)算出它下一次執(zhí)行的時(shí)間點(diǎn)。

    VM Periodic Task Thread

    JVM

    該線程是JVM周期性任務(wù)調(diào)度的線程,它由WatcherThread創(chuàng)建,是一個(gè)單例對(duì)象。 該線程在JVM內(nèi)使用得比較頻繁,比如:定期的內(nèi)存監(jiān)控、JVM運(yùn)行狀況監(jiān)控,還有我們經(jīng)常需要去執(zhí)行一些jstat 這類命令查看gc的情況,如下:

    jstat -gcutil 23483 250 7   這個(gè)命令告訴jvm在控制臺(tái)打印PID為:23483的gc情況,間隔250毫秒打印一次,一共打印7次。

    VM Thread

    JVM

    這個(gè)線程就比較牛b了,是jvm里面的線程母體,根據(jù)hotspot源碼(vmThread.hpp)里面的注釋,它是一個(gè)單例的對(duì)象(最原始的線程)會(huì)產(chǎn)生或觸發(fā)所有其他的線程,這個(gè)單個(gè)的VM線程是會(huì)被其他線程所使用來(lái)做一些VM操作(如,清掃垃圾等)。

    在 VMThread 的結(jié)構(gòu)體里有一個(gè)VMOperationQueue列隊(duì),所有的VM線程操作(vm_operation)都會(huì)被保存到這個(gè)列隊(duì)當(dāng)中,VMThread 本身就是一個(gè)線程,它的線程負(fù)責(zé)執(zhí)行一個(gè)自輪詢的loop函數(shù)(具體可以參考:

    VMThread.cpp里面的

    void VMThread::loop()) ,該loop函數(shù)從VMOperationQueue列隊(duì)中按照優(yōu)先級(jí)取出當(dāng)前需要執(zhí)行的操作對(duì)象(VM_Operation),

    并且調(diào)用VM_Operation->evaluate函數(shù)去執(zhí)行該操作類型本身的業(yè)務(wù)邏輯。

    ps:VM操作類型被定義在

    vm_operations.hpp文件內(nèi),列舉幾個(gè):ThreadStop、

    ThreadDump、

    PrintThreads、

    GenCollectFull、GenCollectFullConcurrent、CMS_Initial_Mark、CMS_Final_Remark…..

     

    參考文章:

    http://jameswxx.iteye.com/blog/1041173

    http://blog.csdn.net/wanyanxgf/article/details/6944987

    http://blog.csdn.net/fanshadoop/article/details/8509218

    http://www.7dtest.com/site/article-80-1.html

    http://blog.csdn.net/a43350860/article/details/8134234

    https://weblogs.java.net/blog/mandychung/archive/2005/11/thread_dump_and_1.html

    主站蜘蛛池模板: 毛片a级毛片免费播放下载| 亚洲AV无码一区二三区| 亚洲av午夜国产精品无码中文字| 亚洲国模精品一区| 免费国产黄网站在线观看| 91在线亚洲综合在线| 中文字幕日韩亚洲| 色片在线免费观看| 国产成人精品免费视频大全| 亚洲成人午夜电影| 亚洲午夜精品第一区二区8050| 99久久99久久免费精品小说| 少妇亚洲免费精品| 亚洲中文字幕久在线| 中文字幕不卡亚洲| 成人性生活免费视频| 美女视频黄的免费视频网页| 亚洲av色香蕉一区二区三区| 亚洲国产精品va在线播放| 国产午夜免费福利红片| aⅴ免费在线观看| 国产精品免费在线播放| 亚洲国产精品成人综合色在线| 亚洲成在人天堂在线| 亚洲国产香蕉人人爽成AV片久久| 无码av免费毛片一区二区| 伊人免费在线观看| 欧洲精品码一区二区三区免费看| 亚洲成年人免费网站| 亚洲va久久久噜噜噜久久狠狠| 国产不卡免费视频| 国产一卡2卡3卡4卡无卡免费视频 国产一卡二卡3卡四卡免费 | 亚洲1234区乱码| 亚洲AV综合色区无码另类小说| 四虎永久成人免费影院域名| 国产1000部成人免费视频| 美女在线视频观看影院免费天天看| 免费人成再在线观看网站 | 9i9精品国产免费久久| 亚洲国产成人AV网站| 亚洲日韩中文字幕一区|