聊聊jstack的工作原理

實(shí)現(xiàn)一個(gè)jstack

在聊Jstack得工作原理前呢展蒂,不如讓我們先寫一個(gè)簡(jiǎn)單的jstack玩玩呢簸。不用怕,很簡(jiǎn)單的,就幾行代碼的事漓概,看:

public class MyJstack {

    public static void main(String[] args)throws Exception {
        VirtualMachine virtualMachine = VirtualMachine.attach("6361");
        HotSpotVirtualMachine hotSpotVirtualMachine = (HotSpotVirtualMachine)virtualMachine;
        InputStream inputStream = hotSpotVirtualMachine.remoteDataDump(new String[]{});

        byte[] buff = new byte[256];
        int len;
        do {
            len = inputStream.read(buff);
            if (len > 0) {
                String respone = new String(buff, 0, len, "UTF-8");
                System.out.print(respone);
            }
        } while(len > 0);

        inputStream.close();
        virtualMachine.detach();
    }
}

很簡(jiǎn)單吧漾月,貼到你的開發(fā)環(huán)境里,運(yùn)行就好了胃珍,別忘了把6361這個(gè)進(jìn)程號(hào)換成你自己的Java進(jìn)程號(hào)哦梁肿。

實(shí)現(xiàn)原理

jstack有兩種實(shí)現(xiàn)方式,一種是基于attach api,其實(shí)現(xiàn)可以在tools.jar里找到觅彰;另一種是基于SA的實(shí)現(xiàn)吩蔑,它被放在了sa-jdi.jar里。如果你通過(guò)idea搜索Jstack類缔莲,你會(huì)看到tools.jar和sa-jdi.jar各有一個(gè)Jstack類哥纫。

本文呢峰伙,就通過(guò)分析attch api的源碼划址,來(lái)了解jstack的工作原理。

jstack本地源碼實(shí)現(xiàn)

我們來(lái)看一下HotSpotVirtualMachine的remoteDataDump方法:

public InputStream remoteDataDump(Object... var1) throws IOException {
        return this.executeCommand("threaddump", var1);
}

他是在執(zhí)行一個(gè)叫threaddump的命令荧呐。沿著這個(gè)executeCommand方法繼續(xù)往里追读拆,會(huì)發(fā)現(xiàn)他是調(diào)用了如下方法:

InputStream execute(String var1, Object... var2) throws AgentLoadException, IOException {
        assert var2.length <= 3;

        String var3;
        synchronized(this) {
            if (this.path == null) {
                throw new IOException("Detached from target VM");
            }

            var3 = this.path;
        }

        int var4 = socket();

        try {
            connect(var4, var3);
        } catch (IOException var9) {
            close(var4);
            throw var9;
        }

        IOException var5 = null;

        try {
            this.writeString(var4, "1");
            this.writeString(var4, var1);

var1參數(shù)就是我們的threaddump指令擅憔,不難看出,這個(gè)方法是建立了一個(gè)socket連接檐晕,然后將threaddump指令發(fā)送給另一端暑诸,即我們要檢查的jvm進(jìn)程蚌讼。

注意:限于篇幅我并沒有貼整個(gè)方法代碼。execute是HotSpotVirtualMachine的抽象方法个榕,不同平臺(tái)的jdk有不同的execute方法的實(shí)現(xiàn)篡石,我這里的代碼是mac下的execute實(shí)現(xiàn),位于BsdVirtualMachine類中西采。

通過(guò)jtack本地源代碼凰萨,我們大致可以粗略的認(rèn)為:jstack就是通過(guò)與指定的jvm進(jìn)程建立socket連接,然后發(fā)送指令械馆,最后將jvm進(jìn)程返回的內(nèi)容打印出來(lái)胖眷。

JVM的源碼實(shí)現(xiàn)

了解了jstack的本地源碼,我們?cè)诳纯磈vm進(jìn)程是如何處理的霹崎。

當(dāng)我們使用Java命令啟動(dòng)jvm進(jìn)程時(shí)珊搀,Java命令會(huì)加載虛擬機(jī)共享庫(kù),然后執(zhí)行共享庫(kù)里的JNI_CreateJavaVM方法完成虛擬機(jī)的創(chuàng)建尾菇,在JNI_CreateJavaVM方法里會(huì)調(diào)用如下代碼境析,完成具體的一個(gè)創(chuàng)建過(guò)程:

result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);

如果你有心,或許會(huì)留意到派诬,在你啟動(dòng)一個(gè)jvm進(jìn)程時(shí)簿晓,即便你什么線程也沒創(chuàng)建,你用jstack查看還是有很多的線程千埃,如:Signal Dispatcher,VM Thread忆植,Attach Listener等等放可。當(dāng)過(guò)閱讀本文,你會(huì)了解到這三個(gè)線程的作用朝刊。

01 VM Thread線程

Threads::create_vm這個(gè)方法很長(zhǎng)耀里,接下來(lái)咱們跳出一些重要的段落,來(lái)分析分析拾氓。

// Create the VMThread
  { TraceTime timer("Start VMThread", TraceStartupTime);
    VMThread::create();//創(chuàng)建Thread對(duì)象
    Thread* vmthread = VMThread::vm_thread();

    if (!os::create_thread(vmthread, os::vm_thread))//調(diào)用操作系統(tǒng)api創(chuàng)建線程
      vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");

    // Wait for the VM thread to become ready, and VMThread::run to initialize
    // Monitors can have spurious returns, must always check another state flag
    {
      MutexLocker ml(Notify_lock);
      os::start_thread(vmthread);//啟動(dòng)線程
      while (vmthread->active_handles() == NULL) {
        Notify_lock->wait();
      }
    }
  }

通過(guò)注釋冯挎,你也知道,這一段代碼是從來(lái)創(chuàng)建VM Thread線程的咙鞍。VMThread::create()完成了對(duì)現(xiàn)成的命名工作房官,代碼如下:

void VMThread::create() {
  assert(vm_thread() == NULL, "we can only allocate one VMThread");
  _vm_thread = new VMThread();

  // Create VM operation queue
  _vm_queue = new VMOperationQueue();
  guarantee(_vm_queue != NULL, "just checking");

  _terminate_lock = new Monitor(Mutex::safepoint, "VMThread::_terminate_lock", true);

  if (UsePerfData) {
    // jvmstat performance counters
    Thread* THREAD = Thread::current();
    _perf_accumulated_vm_operation_time =
                 PerfDataManager::create_counter(SUN_THREADS, "vmOperationTime",
                                                 PerfData::U_Ticks, CHECK);
  }
}


VMThread::VMThread() : NamedThread() {
  set_name("VM Thread");
}

通過(guò)new VMThread()創(chuàng)建線程對(duì)象,在VMThread的構(gòu)造方法里將線程命名成VM Thread续滋,這就是我們jstack看到的VM Thread線程,同時(shí)還為這個(gè)線程創(chuàng)建了一個(gè)叫VMOperationQueue的隊(duì)列翰守。

至于VM Thread線程的作用,我們留到最后再說(shuō)疲酌。

02 Signal Dispatcher線程

繼續(xù)沿著 Threads::create_vm方法往下看蜡峰,我們會(huì)看到如下代碼:

// Signal Dispatcher needs to be started before VMInit event is posted
  os::signal_init();

這一句代碼實(shí)現(xiàn)了Signal Dispatcher線程的創(chuàng)建了袁,進(jìn)入到signal_init()方法看看:

void os::signal_init() {
  if (!ReduceSignalUsage) {
    // Setup JavaThread for processing signals
    EXCEPTION_MARK;
    Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
    instanceKlassHandle klass (THREAD, k);
    instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);

    const char thread_name[] = "Signal Dispatcher";
    Handle string = java_lang_String::create_from_str(thread_name, CHECK);

    // Initialize thread_oop to put it into the system threadGroup
    Handle thread_group (THREAD, Universe::system_thread_group());
    JavaValue result(T_VOID);
    JavaCalls::call_special(&result, thread_oop,
                           klass,
                           vmSymbols::object_initializer_name(),
                           vmSymbols::threadgroup_string_void_signature(),
                           thread_group,
                           string,
                           CHECK);

    KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
    JavaCalls::call_special(&result,
                            thread_group,
                            group,
                            vmSymbols::add_method_name(),
                            vmSymbols::thread_void_signature(),
                            thread_oop,         // ARG 1
                            CHECK);

    os::signal_init_pd();

    { MutexLocker mu(Threads_lock);
      JavaThread* signal_thread = new JavaThread(&signal_thread_entry);

      // At this point it may be possible that no osthread was created for the
      // JavaThread due to lack of memory. We would have to throw an exception
      // in that case. However, since this must work and we do not allow
      // exceptions anyway, check and abort if this fails.
      if (signal_thread == NULL || signal_thread->osthread() == NULL) {
        vm_exit_during_initialization("java.lang.OutOfMemoryError",
                                      "unable to create new native thread");
      }

      java_lang_Thread::set_thread(thread_oop(), signal_thread);
      java_lang_Thread::set_priority(thread_oop(), NearMaxPriority);
      java_lang_Thread::set_daemon(thread_oop());

      signal_thread->set_threadObj(thread_oop());
      Threads::add(signal_thread);
      Thread::start(signal_thread);
    }
    // Handle ^BREAK
    os::signal(SIGBREAK, os::user_handler());
  }
}

在這個(gè)方法里,我們可以看到要?jiǎng)?chuàng)建的線程名字:Signal Dispatcher湿颅,以及線程啟動(dòng)后調(diào)用的方法signal_thread_entry载绿。(方法較長(zhǎng),看重點(diǎn)就好油航,沒必要每句話都扣清楚)崭庸。

有了對(duì)上邊代碼的分析,我們只需要看看signal_thread_entry方法劝堪,就知道Signal Dispatcher線程的作用了冀自。

static void signal_thread_entry(JavaThread* thread, TRAPS) {
  os::set_priority(thread, NearMaxPriority);
  while (true) {
    int sig;
    {
      // FIXME : Currently we have not decieded what should be the status
      //         for this java thread blocked here. Once we decide about
      //         that we should fix this.
      sig = os::signal_wait();//等待獲取信號(hào)
    }
    if (sig == os::sigexitnum_pd()) {
       // Terminate the signal thread
       return;
    }

    switch (sig) {
      case SIGBREAK: {
        // Check if the signal is a trigger to start the Attach Listener - in that
        // case don't print stack traces.
        if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {
          continue;
        }
        // Print stack traces
        // Any SIGBREAK operations added here should make sure to flush
        // the output stream (e.g. tty->flush()) after output.  See 4803766.
        // Each module also prints an extra carriage return after its output.
        VM_PrintThreads op;
        VMThread::execute(&op);
        VM_PrintJNI jni_op;
        VMThread::execute(&jni_op);
        VM_FindDeadlocks op1(tty);
        VMThread::execute(&op1);
        Universe::print_heap_at_SIGBREAK();
        if (PrintClassHistogram) {
          VM_GC_HeapInspection op1(gclog_or_tty, true /* force full GC before heap inspection */);
          VMThread::execute(&op1);
        }
        if (JvmtiExport::should_post_data_dump()) {
          JvmtiExport::post_data_dump();
        }
        break;

這個(gè)方法里調(diào)用os::signal_wait()獲取傳給該jvm進(jìn)程的信號(hào),然后對(duì)信號(hào)進(jìn)行處理秒啦。

說(shuō)下case SIGBREAK里的處理邏輯熬粗,當(dāng)接收到SIGBREAK信號(hào)時(shí),會(huì)先判斷是否禁止Attach機(jī)制余境,如果沒有禁止驻呐,會(huì)調(diào)用AttachListener::is_init_trigger()方法觸發(fā)Attach Listener線程的初始化.如果attach機(jī)制被禁用,則會(huì)創(chuàng)建VM_PrintThreads芳来、VM_PrintJNI含末、VM_FindDeadlocks等代表某一個(gè)操作的對(duì)象,通過(guò)VMThread::execute()方法扔到VM Thread線程的VMOperationQueue隊(duì)列即舌。

03 Attach Listener線程

繼續(xù)沿著 Threads::create_vm方法往下看佣盒,在緊挨著啟動(dòng)Signal Dispatcher線程的下邊,就是啟動(dòng)Attach Listener線程的語(yǔ)句:

// Start Attach Listener if +StartAttachListener or it can't be started lazily
  if (!DisableAttachMechanism) {
    AttachListener::vm_start();
    if (StartAttachListener || AttachListener::init_at_startup()) {
      AttachListener::init();
    }
  }

重點(diǎn)就在AttachListener::init()方法里:

// Starts the Attach Listener thread
void AttachListener::init() {
  EXCEPTION_MARK;
  Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
  instanceKlassHandle klass (THREAD, k);
  instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);

  const char thread_name[] = "Attach Listener";
  Handle string = java_lang_String::create_from_str(thread_name, CHECK);

  // Initialize thread_oop to put it into the system threadGroup
  Handle thread_group (THREAD, Universe::system_thread_group());
  JavaValue result(T_VOID);
  JavaCalls::call_special(&result, thread_oop,
                       klass,
                       vmSymbols::object_initializer_name(),
                       vmSymbols::threadgroup_string_void_signature(),
                       thread_group,
                       string,
                       THREAD);

  if (HAS_PENDING_EXCEPTION) {
    tty->print_cr("Exception in VM (AttachListener::init) : ");
    java_lang_Throwable::print(PENDING_EXCEPTION, tty);
    tty->cr();

    CLEAR_PENDING_EXCEPTION;

    return;
  }

  KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
  JavaCalls::call_special(&result,
                        thread_group,
                        group,
                        vmSymbols::add_method_name(),
                        vmSymbols::thread_void_signature(),
                        thread_oop,             // ARG 1
                        THREAD);

  if (HAS_PENDING_EXCEPTION) {
    tty->print_cr("Exception in VM (AttachListener::init) : ");
    java_lang_Throwable::print(PENDING_EXCEPTION, tty);
    tty->cr();

    CLEAR_PENDING_EXCEPTION;

    return;
  }

  { MutexLocker mu(Threads_lock);
    JavaThread* listener_thread = new JavaThread(&attach_listener_thread_entry);

    // Check that thread and osthread were created
    if (listener_thread == NULL || listener_thread->osthread() == NULL) {
      vm_exit_during_initialization("java.lang.OutOfMemoryError",
                                    "unable to create new native thread");
    }

    java_lang_Thread::set_thread(thread_oop(), listener_thread);
    java_lang_Thread::set_daemon(thread_oop());

    listener_thread->set_threadObj(thread_oop());
    Threads::add(listener_thread);
    Thread::start(listener_thread);
  }
}

我們可以通過(guò)代碼看出其創(chuàng)建了一個(gè)叫Attach Listener的線程顽聂,線程執(zhí)行的邏輯封裝在了attach_listener_thread_entry方法里肥惭。

Attach Listener線程的作用,我們看看attach_listener_thread_entry方法便知:

static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
  os::set_priority(thread, NearMaxPriority);

  thread->record_stack_base_and_size();

  if (AttachListener::pd_init() != 0) {
    return;
  }
  AttachListener::set_initialized();

  for (;;) {
    AttachOperation* op = AttachListener::dequeue();//從隊(duì)列里獲取操作對(duì)象
    if (op == NULL) {
      return;   // dequeue failed or shutdown
    }

    ResourceMark rm;
    bufferedStream st;
    jint res = JNI_OK;

    // handle special detachall operation
    if (strcmp(op->name(), AttachOperation::detachall_operation_name()) == 0) {
      AttachListener::detachall();
    } else {
      // find the function to dispatch too
      AttachOperationFunctionInfo* info = NULL;
      for (int i=0; funcs[i].name != NULL; i++) {
        const char* name = funcs[i].name;
        assert(strlen(name) <= AttachOperation::name_length_max, "operation <= name_length_max");
        if (strcmp(op->name(), name) == 0) {
          info = &(funcs[i]);
          break;
        }
      }

      // check for platform dependent attach operation
      if (info == NULL) {
        info = AttachListener::pd_find_operation(op->name());
      }

      if (info != NULL) {
        // dispatch to the function that implements this operation
        res = (info->func)(op, &st);//執(zhí)行操作對(duì)象
      } else {
        st.print("Operation %s not recognized!", op->name());
        res = JNI_ERR;
      }
    }

    // operation complete - send result and output to client
    op->complete(res, &st);
  }
}

方法很長(zhǎng)紊搪,我把重點(diǎn)挑出來(lái)分析蜜葱。

首先我們看看調(diào)用AttachListener::pd_init()完了什么:

int AttachListener::pd_init() {
  JavaThread* thread = JavaThread::current();
  ThreadBlockInVM tbivm(thread);

  thread->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or
  // java_suspend_self() via check_and_wait_while_suspended()

  int ret_code = LinuxAttachListener::init();

  // were we externally suspended while we were waiting?
  thread->check_and_wait_while_suspended();

  return ret_code;
}

int LinuxAttachListener::init() {
  char path[UNIX_PATH_MAX];          // socket file
  char initial_path[UNIX_PATH_MAX];  // socket file during setup
  int listener;                      // listener socket (file descriptor)

  // register function to cleanup
  ::atexit(listener_cleanup);

  int n = snprintf(path, UNIX_PATH_MAX, "%s/.java_pid%d",
                   os::get_temp_directory(), os::current_process_id());
  if (n < (int)UNIX_PATH_MAX) {
    n = snprintf(initial_path, UNIX_PATH_MAX, "%s.tmp", path);
  }
  if (n >= (int)UNIX_PATH_MAX) {
    return -1;
  }

  // create the listener socket
  listener = ::socket(PF_UNIX, SOCK_STREAM, 0);//創(chuàng)建套接字
  if (listener == -1) {
    return -1;
  }

  // bind socket
  struct sockaddr_un addr;
  addr.sun_family = AF_UNIX;
  strcpy(addr.sun_path, initial_path);
  ::unlink(initial_path);
  int res = ::bind(listener, (struct sockaddr*)&addr, sizeof(addr));//綁定地址
  if (res == -1) {
    ::close(listener);
    return -1;
  }

  // put in listen mode, set permissions, and rename into place
  res = ::listen(listener, 5);//發(fā)起監(jiān)聽
  if (res == 0) {
      RESTARTABLE(::chmod(initial_path, S_IREAD|S_IWRITE), res);
      if (res == 0) {
          res = ::rename(initial_path, path);
      }
  }
  if (res == -1) {
    ::close(listener);
    ::unlink(initial_path);
    return -1;
  }
  set_path(path);
  set_listener(listener);

  return 0;
}

不難發(fā)現(xiàn),AttachListener::pd_init()方法又調(diào)用了LinuxAttachListener::init()方法耀石,完成了對(duì)套接字的創(chuàng)建和監(jiān)聽牵囤。這與jstack本地代碼建立socket連接發(fā)送命令,不謀而合滞伟。

再就是有一個(gè)for死循環(huán)揭鳞,不停地調(diào)用AttachOperation* op = AttachListener::dequeue();獲取操作對(duì)象。如果進(jìn)入到AttachListener::dequeue()方法看一看诗良,其實(shí)就是在讀上邊監(jiān)聽的套接字汹桦,我這里就不貼源碼了。

在這個(gè)死循環(huán)里鉴裹,我們重點(diǎn)看看如下代碼:

    // find the function to dispatch too
      AttachOperationFunctionInfo* info = NULL;
      for (int i=0; funcs[i].name != NULL; i++) {
        const char* name = funcs[i].name;
        assert(strlen(name) <= AttachOperation::name_length_max, "operation <= name_length_max");
        if (strcmp(op->name(), name) == 0) {
          info = &(funcs[i]);
          break;
        }
      }

      // check for platform dependent attach operation
      if (info == NULL) {
        info = AttachListener::pd_find_operation(op->name());
      }

      if (info != NULL) {
        // dispatch to the function that implements this operation
        res = (info->func)(op, &st);//調(diào)動(dòng)方法
      } else {
        st.print("Operation %s not recognized!", op->name());
        res = JNI_ERR;
      }
    }

    // operation complete - send result and output to client
    op->complete(res, &st);

這個(gè)for循環(huán)會(huì)遍歷funcs數(shù)組舞骆,然后根據(jù)從隊(duì)列里拿到的AttachOperation對(duì)象的name來(lái)找到一個(gè)匹配的AttachOperationFunctionInfo對(duì)象钥弯,然后調(diào)用其func方法。

看到這里你或許很多疑惑督禽,當(dāng)然看看funcs數(shù)組里的東西脆霎,就開朗了:

static AttachOperationFunctionInfo funcs[] = {
  { "agentProperties",  get_agent_properties },
  { "datadump",         data_dump },
  { "dumpheap",         dump_heap },
  { "load",             JvmtiExport::load_agent_library },
  { "properties",       get_system_properties },
  { "threaddump",       thread_dump },
  { "inspectheap",      heap_inspection },
  { "setflag",          set_flag },
  { "printflag",        print_flag },
  { "jcmd",             jcmd },
  { NULL,               NULL }
};

有沒有看到上文中我們提到的threaddump命令。jstack通過(guò)與jvm進(jìn)程建立socket連接狈惫,然后向jvm進(jìn)程發(fā)送threaddump指令睛蛛。上文說(shuō)道調(diào)用AttachOperationFunctionInfo對(duì)象的func方法處理指令,其實(shí)就是調(diào)用了thread_dump方法胧谈,針對(duì)threaddump命令來(lái)說(shuō)忆肾。

堅(jiān)持,馬上就要說(shuō)完了菱肖。來(lái)看看thread_dump方法干了些啥吧:

// Implementation of "threaddump" command - essentially a remote ctrl-break
// See also: ThreadDumpDCmd class
//
static jint thread_dump(AttachOperation* op, outputStream* out) {
  bool print_concurrent_locks = false;
  if (op->arg(0) != NULL && strcmp(op->arg(0), "-l") == 0) {
    print_concurrent_locks = true;
  }

  // thread stacks
  VM_PrintThreads op1(out, print_concurrent_locks);
  VMThread::execute(&op1);

  // JNI global handles
  VM_PrintJNI op2(out);
  VMThread::execute(&op2);

  // Deadlock detection
  VM_FindDeadlocks op3(out);
  VMThread::execute(&op3);

  return JNI_OK;
}

很簡(jiǎn)單客冈,創(chuàng)建了VM_PrintThreads、VM_PrintJNI稳强、VM_FindDeadlocks三個(gè)對(duì)象场仲,扔給了VM Thread線程的隊(duì)列。

說(shuō)到這里退疫,VM Thread線程的作用渠缕,應(yīng)該真相大白了,就是讀取隊(duì)列褒繁,然后執(zhí)行相應(yīng)的操作亦鳞。有興趣你可以繼續(xù)追進(jìn)去看看源代碼,我這里就不追下去了棒坏。

總結(jié)

看了這么多代碼蚜迅,確實(shí)很頭疼,總結(jié)下吧俊抵。

jstack是通過(guò)與jvm進(jìn)程建立socket連接,然后發(fā)送指令來(lái)實(shí)現(xiàn)相關(guān)操作坐梯。

jvm的Attach Listener線程監(jiān)聽套接字徽诲,讀取jstack發(fā)來(lái)的指令,然后將相關(guān)的操作扔給VM Thread線程來(lái)執(zhí)行吵血,最后返回給jstack谎替。

在jvm啟動(dòng)的時(shí)候,如果沒有指定StartAttachListener蹋辅,Attach Listener線程是不會(huì)啟動(dòng)的钱贯,在Signal Dispatcher線程收到SIGBREAK信號(hào)時(shí),會(huì)調(diào)用 AttachListener::is_init_trigger()通過(guò)調(diào)用用AttachListener::init()啟動(dòng)了Attach Listener 線程侦另。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秩命,一起剝皮案震驚了整個(gè)濱河市尉共,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弃锐,老刑警劉巖袄友,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異霹菊,居然都是意外死亡剧蚣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門旋廷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鸠按,“玉大人,你說(shuō)我怎么就攤上這事饶碘∧考猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵熊镣,是天一觀的道長(zhǎng)卑雁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)绪囱,這世上最難降的妖魔是什么测蹲? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮鬼吵,結(jié)果婚禮上扣甲,老公的妹妹穿的比我還像新娘。我一直安慰自己齿椅,他們只是感情好琉挖,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涣脚,像睡著了一般示辈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上遣蚀,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天矾麻,我揣著相機(jī)與錄音,去河邊找鬼芭梯。 笑死险耀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的玖喘。 我是一名探鬼主播甩牺,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼累奈!你這毒婦竟也來(lái)了贬派?” 一聲冷哼從身側(cè)響起急但,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赠群,沒想到半個(gè)月后羊始,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡查描,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年突委,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冬三。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匀油,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出勾笆,到底是詐尸還是另有隱情敌蚜,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布窝爪,位于F島的核電站弛车,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蒲每。R本人自食惡果不足惜纷跛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望邀杏。 院中可真熱鬧贫奠,春花似錦、人聲如沸望蜡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脖律。三九已至谢肾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間小泉,已是汗流浹背勒叠。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膏孟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓拌汇,卻偏偏與公主長(zhǎng)得像柒桑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子噪舀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容