JNI和字节码方法调用

Java对象创建的本质就是按照对象的大小分配一块内存,然后完成属性的初始化。对象创建完了,接着干啥了?调用Java方法完成特定功能。这就是我们接下来探讨的主题,Java方法调用是怎么实现的。

一、Main方法
main方法是Java应用启动执行的入口方法,这个方法是怎么执行的了?,关键代码在OpenJDK jdk/src/share/bin/java.c中的int JNICALL JavaMain(void * _args)方法,如下图:

image.png

即main方法是通过JNI的CallStaticVoidMethod方法执行的。

二、JNI 方法调用
1、API定义
JNI的方法调用的API,分为三种,总结如下:

  • NativeType Call<type>Method:执行非静态方法调用,如果子类覆写父类方法,则调用子类覆写后的方法。
  • NativeType CallNonvirtual<type>Method:Call<type>Method的扩展版,如果子类覆写父类方法,可以根据jmethedId调用子类覆写后的方法或者调用父类原来的方法,前者jmethodId从子类jclass中获取,后者jmethodId从父类jclass中获取。
  • NativeType CallStatic<type>Method:执行静态方法调用。

三者根据传递参数的方式的不同,又有三个“重载”方法,以Call<type>Method为例:

  • Call<type>Method:按照Java方法中定义的参数类型和顺序依次传递参数即可,注意JNI中不支持基本类型的自动装箱拆箱,如果方法参数是包装类,则需要调用包装类的构造方法构造基本包装类实例。
  • Call<type>MethodA:按照Java方法中定义的参数类型和顺序将所有参数放入一个jvalue数组中,然后传入该数组的指针即可,jvalue是一个union类型,可以支持所有的参数类型。
    Call<type>MethodV:按照Java方法中定义的参数类型和顺序将所有参数放入一个va_list类中,该类表示一个参数列表。
    通过宏的方式实现不同NativeType和type下的方法定义,以Call<type>Method为例,如下图:
image.png

所有方法调用的API最终调用的都是jni.cpp中jni_invoke_static和jni_invoke_nonstatic方法,这两个方法的调用如下图:

image.png

其中jni_NewObject三个方法是通过jni_invoke_nonstatic调用类构造方法

image.png

2、 jni_invoke_static

static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
  //resolve_jmethod_id将jmethodID转换成Method*
  methodHandle method(THREAD, Method::resolve_jmethod_id(method_id));
 
  // Create object to hold arguments for the JavaCall, and associate it with
  // the jni parser
  ResourceMark rm(THREAD);
  //获取方法参数个数
  int number_of_parameters = method->size_of_parameters();
  //初始化java_args
  JavaCallArguments java_args(number_of_parameters);
  args->set_java_argument_object(&java_args);
  //校验目标方法为静态方法
  assert(method->is_static(), "method should be static");
 
  //fingerprint返回目标方法的fingerprint,是一长串数字,包含方法的参数类型,返回值类型等信息
  /解析方法参数到JavaCallArguments中
  args->iterate( Fingerprinter(method).fingerprint() );
  // 设置返回结果的结果类型
  result->set_type(args->get_ret_type());
 
  //执行方法调用
  JavaCalls::call(result, method, &java_args, CHECK);
 
  // 如果结果类型是对象或者数组类型,则需要将其转换成本地引用
  if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
    result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
  }
}
 
enum JNICallType {
  JNI_STATIC, //静态方法
  JNI_VIRTUAL, //虚方法
  JNI_NONVIRTUAL //非虚方法
};
 
 
 inline static Method* resolve_jmethod_id(jmethodID mid) {
    assert(mid != NULL, "JNI method id should not be null");
    //据此可知,jmethodID实际是Method的指针的指针
    return *((Method**)mid);
  }
3、jni_invoke_nonstatic
static void jni_invoke_nonstatic(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
  //校验调用实例方法关联的对象receiver不能为空
  oop recv = JNIHandles::resolve(receiver);
  if (recv == NULL) {
    THROW(vmSymbols::java_lang_NullPointerException());
  }
  Handle h_recv(THREAD, recv);
 
  int number_of_parameters;
  Method* selected_method;
  {
    //将jmethodID转换成Method*
    Method* m = Method::resolve_jmethod_id(method_id);
    //获取方法个数
    number_of_parameters = m->size_of_parameters();
    //获取此方法所属的类Klass
    Klass* holder = m->method_holder();
    if (call_type != JNI_VIRTUAL) {
        //如果是非虚方法调用,即CallNonvirtual<type>Method,则使用指定的方法
        //此时jmethodID从父类Klass获取的则使用父类的实现,如果使用子类Klass的实现则使用子类的实现
        selected_method = m;
    } 
    //虚方法调用,即Call<type>Method,itable即接口方法表
    else if (!m->has_itable_index()) {
      // non-interface call -- for that little speed boost, don't handlize
      // 非接口方法调用
      debug_only(No_Safepoint_Verifier nosafepoint;)
      //校验该方法在虚方法表的索引是否有效,如果目标类已经完成链接和初始化则valid_vtable_index()方法返回true
      assert(m->valid_vtable_index(), "no valid vtable index");
      //获取虚方法表中的索引,注意同一个方法,无论从子类Klass获取还是从父类Klass获取,其vtbl_index都是一样的
      int vtbl_index = m->vtable_index();
 
      //如果vtbl_index不等于nonvirtual_vtable_index,nonvirtual_vtable_index表示该方法不需要通过vtable分发,即父类定义的final方法
      if (vtbl_index != Method::nonvirtual_vtable_index) {
        //获取receiver对应的Klass
        Klass* k = h_recv->klass();
        InstanceKlass *ik = (InstanceKlass*)k;
        //获取目标Klass在指定虚方法表索引处的虚方法实现,
        //如receiver实际是子类实例,jmethodID无论从父类Klass还是子类Klass获取的,实际调用的都是子类的实现
        selected_method = ik->method_at_vtable(vtbl_index);
      } else {
        //final方法
        selected_method = m;
      }
    } else {
      //接口方法
      KlassHandle h_holder(THREAD, holder);
      //获取接口方法表中的索引,无论jmethodID从接口类Klass还是实现类Klass获取的,其itbl_index都是一样的
      int itbl_index = m->itable_index();
      Klass* k = h_recv->klass();
      //获取接口方法,使用receiver实例实际的类的接口实现
      selected_method = InstanceKlass::cast(k)->method_at_itable(h_holder(), itbl_index, CHECK);
    }
  }
 
  methodHandle method(THREAD, selected_method);
 
  /
  ResourceMark rm(THREAD);
  //初始化JavaCallArguments
  JavaCallArguments java_args(number_of_parameters);
  args->set_java_argument_object(&java_args);
 
  //校验方法不是静态方法
  assert(!method->is_static(), "method should not be static");
  //设置接受方法调用的对象实例
  args->push_receiver(h_recv); // Push jobject handle
 
  //解析方法参数
  args->iterate( Fingerprinter(method).fingerprint() );
  //设置方法返回类型
  result->set_type(args->get_ret_type());
 
  //调用方法
  JavaCalls::call(result, method, &java_args, CHECK);
 
  //处理结果返回值
  if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
    result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
  }
}

从上述实现可知,jni_invoke_nonstatic比jni_invoke_static就是多了确认实际调用方法的逻辑。CallNonvirtual<type>Method时和CallStatic<type>Method实际逻辑是一样的,都是执行直接执行传入的jmethodID对应的Java方法,而Call<type>Method需要根据传入的jmethodID确认目标方法的虚方法表索引或者接口方法表索引,然后根据索引获取目标实例对象所属的类的实现方法。itable和vtable可参考《Hotspot Klass模型——Java类内存表示机制》。

三、方法调用字节码指令
1、指令定义
main方法通过JNI的方法调用开始执行后,如果调用其他的Java方法就必须通过方法调用的字节码指令,JNI的方法调用仅限于本地方法实现使用。执行方法调用的字节码指令总共有5个,概述如下,详情可以参考《Java虚拟机规范》:

  • invokedynamic:调用动态方法,动态方法是指最终被调用的方法可以在运行期由程序动态指定,从JDK7引入,但是JDK7的编译器无法直接使用该指令,只能通过ASM字节码编辑工具调用。目前主要用于JDK8中java.lang.invoke包和Lambda表达式。
    -invokeinteface:调用接口方法,即由接口类中定义的方法,此时调用对象声明的类型是接口类,实际的类型是该接口的某个实现类。假如调用对象实例是C,查找具体执行方法时先在C中查找名称和描述符都和接口方法一致的方法,如果没有找到则继续在C的父类,父类的父类,不断往上递归查找,直到找到名称和描述符都和接口方法一致的方法,在编译正常的情形下通过这两步查找就可以确认实际被执行的方法。
    -invokespecial:调用实例方法,包括父类方法,私有方法和实例初始化方法三种,实际被执行的方法的查找逻辑同invokeinteface基本一样,都是现在当前类查找,再往上递归查找当前类的父类,在编译期即可确认。
    -invokestatic:调用静态方法,在方法解析成功后,如果方法所在的类或者接口没有被初始化则指令执行时会触发其初始化。因为静态方法不能被继承,因此只需在调用类中查找是否存在目标方法,也是在编译期即可确认实际被执行的方法。
    -invokevirtual:调用实例方法,依据调用对象实例的类型进行分派,如果目标方法是签名多态性方法(通常是java.lang.invoke.MethodHanlde的invoke和invokeExact方法),则需要做特殊处理,以保证方法句柄能够正常调用。注意invokevirtual并不是其字面描述的一样的调用虚方法,调用某个子类独有的方法(按C++的虚方法定义这种方法就是非虚方法)也是通过invokevirtual完成,因为JVM无法确认方法调用实例是该子类的实例,还是该子类的子类实例,后者可能覆写了该方法。

测试用例如下:

package jni;
 
 
interface InterfaceA{
    void say();
 
    //接口中的default方法只是接口方法的默认实现,接口实现类可以改写默认实现
    default void print(){
        System.out.println("InterfaceA default methoed");
    }
 
    static void staticDo(){
        System.out.println("InterfaceA staticDo");
    }
}
 
 
interface InterfaceB{
 
    void interfaceDo();
 
}
 
 
class superB implements InterfaceA{
 
    @Override
    public void say() {
        System.out.println("superB say");
    }
 
    @Override
    public void print() {
        System.out.println("superB print");
    }
 
    public void superDo(){
        System.out.println("superB superDo ");
    }
}
 
public class InvokeTest extends superB implements InterfaceB{
 
    @Override
    public void say() {
        super.say();
        System.out.println("InvokeTest say");
    }
 
    private void privateDo(){
        System.out.println("InvokeTest privateDo");
    }
 
    public void subDo(){
        privateDo();
        System.out.println("InvokeTest subDo");
    }
 
    @Override
    public void interfaceDo() {
        System.out.println("InvokeTest interfaceDo");
    }
 
    public static void main(String[] args) {
        InterfaceA.staticDo();
        InvokeTest a=new InvokeTest();
        a.say();
        a.subDo();
        a.print();
        a.superDo();
        a.interfaceDo();
 
        superB b=a;
        b.say();
        b.print();
        b.superDo();
 
        InterfaceA c=a;
        c.say();
        c.print();
 
        InterfaceB d=a;
        d.interfaceDo();
    }
}

编译过后执行javap -v可以查看具体的字节码指令,截取部分如下:

 public void say();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #2                  // Method jni/superB.say:()V  调用父类方法
         4: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #4                  // String InvokeTest say
         9: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: return
 
public void subDo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #7                  // Method privateDo:()V  调用私有方法
         4: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #8                  // String InvokeTest subDo
         9: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: return
 
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=5, args_size=1
         0: invokestatic  #10                 // InterfaceMethod jni/InterfaceA.staticDo:()V   调用静态方法
         3: new           #11                 // class jni/InvokeTest
         6: dup
         7: invokespecial #12                 // Method "<init>":()V  调用实例初始化方法
        10: astore_1
        11: aload_1
        12: invokevirtual #13                 // Method say:()V  调用实例的类型都是类,所以这里都是invokevirtual 
        15: aload_1
        16: invokevirtual #14                 // Method subDo:()V  subDo是子类特有的,为了兼容调用实例有可能是子类的子类的情形,所以依然使用invokevirtual指令
        19: aload_1
        20: invokevirtual #15                 // Method print:()V
        23: aload_1
        24: invokevirtual #16                 // Method superDo:()V
        27: aload_1
        28: invokevirtual #17                 // Method interfaceDo:()V
        31: aload_1
        32: astore_2
        33: aload_2
        34: invokevirtual #2                  // Method jni/superB.say:()V
        37: aload_2
        38: invokevirtual #18                 // Method jni/superB.print:()V
        41: aload_2
        42: invokevirtual #19                 // Method jni/superB.superDo:()V
        45: aload_1
        46: astore_3
        47: aload_3
        48: invokeinterface #20,  1           // InterfaceMethod jni/InterfaceA.say:()V  调用实例的类型都是接口类,即使该接口方法已经提供了默认实现,依然使用invokeinterface 
        53: aload_3
        54: invokeinterface #21,  1           // InterfaceMethod jni/InterfaceA.print:()V
        59: aload_1
        60: astore        4
        62: aload         4
        64: invokeinterface #22,  1           // InterfaceMethod jni/InterfaceB.interfaceDo:()V
        69: return

2、invokeinteface指令
该指令的实现参考OpenJDK8 hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp 2522行,源代码说明如下:

CASE(_invokeinterface): {
        //pc表示当前字节码指令的地址,get_native_u2返回从指定地址开始的2字节的数据,将其作为short读取
        //根据JVM规范,index表示运行时常量池的索引,指向一个接口方法的符号引用
        u2 index = Bytes::get_native_u2(pc+1);
 
        //获取常量池索引index处的值
        ConstantPoolCacheEntry* cache = cp->entry_at(index);
        if (!cache->is_resolved((Bytecodes::Code)opcode)) {
         //如果接口方法未解析则解析
          CALL_VM(InterpreterRuntime::resolve_invoke(THREAD, (Bytecodes::Code)opcode),
                  handle_exception);
          cache = cp->entry_at(index);
        }
 
        istate->set_msg(call_method);
 
        //特殊场景下java.lang.Object的虚方法调用,
        if (cache->is_forced_virtual()) {
          Method* callee;
          //检查操作数栈的方法参数是否为空
          CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
          //如果是final方法
          if (cache->is_vfinal()) {
            //获取解析后的方法
            callee = cache->f2_as_vfinal_method();
            //final方法调用
            BI_PROFILE_UPDATE_FINALCALL();
          } else {
             //非final方法
            // Get receiver.
            int parms = cache->parameter_size();
            //获取执行方法调用的对象实例
            oop rcvr = STACK_OBJECT(-parms);
            //校验rcvr是否为空
            VERIFY_OOP(rcvr);
            //获取rcvr的实际类型Klass
            InstanceKlass* rcvrKlass = (InstanceKlass*)rcvr->klass();
            //获取虚方法表相同索引下的方法
            callee = (Method*) rcvrKlass->start_of_vtable()[ cache->f2_as_index()];
            //profile统计
            BI_PROFILE_UPDATE_VIRTUALCALL(rcvr->klass());
          }
          //调用方法
          istate->set_callee(callee);
          istate->set_callee_entry_point(callee->from_interpreted_entry());
 
          istate->set_bcp_advance(5);
          //方法调用完成返回
          UPDATE_PC_AND_RETURN(0); // I'll be back...
        }
 
        // this could definitely be cleaned up QQQ
        Method* callee;
        //获取接口方法
        Method *interface_method = cache->f2_as_interface_method();
        //获取接口类
        InstanceKlass* iclass = interface_method->method_holder();
 
        // get receiver
        int parms = cache->parameter_size();
        //获取方法调用对象实例
        oop rcvr = STACK_OBJECT(-parms);
        CHECK_NULL(rcvr);
        //获取方法调用对象实例的真实类型
        InstanceKlass* int2 = (InstanceKlass*) rcvr->klass();
 
        // Receiver subtype check against resolved interface klass (REFC).
        {
          //获取目标接口方法的resolved interface klass
          Klass* refc = cache->f1_as_klass();
          itableOffsetEntry* scan;
          //遍历int2实现的所有接口类,判断是否存在目标接口方法对应的resolved interface klass
          for (scan = (itableOffsetEntry*) int2->start_of_itable();
               scan->interface_klass() != NULL;
               scan++) {
            if (scan->interface_klass() == refc) {
              break;
            }
          }
          // int2没有实现目标resolved interface klass,抛出异常
          if (scan->interface_klass() == NULL) {
            VM_JAVA_ERROR(vmSymbols::java_lang_IncompatibleClassChangeError(), "", note_no_trap);
          }
        }
   
       //遍历int2实现的所有接口类,判断是否存在目标接口方法对应InstanceKlass     
        itableOffsetEntry* ki = (itableOffsetEntry*) int2->start_of_itable();
        int i;
        for ( i = 0 ; i < int2->itable_length() ; i++, ki++ ) {
          if (ki->interface_klass() == iclass) break;
        }
        // int2没有实现目标接口方法的InstanceKlass,抛出异常
        if (i == int2->itable_length()) {
          VM_JAVA_ERROR(vmSymbols::java_lang_IncompatibleClassChangeError(), "", note_no_trap);
        }
 
        //获取接口方法表的索引
        int mindex = interface_method->itable_index();
        
        //获取rcvr的接口方法表第一个元素
        itableMethodEntry* im = ki->first_method_entry(rcvr->klass());
        //获取接口方法表索引mindex处的方法
        callee = im[mindex].method();
        if (callee == NULL) {
          VM_JAVA_ERROR(vmSymbols::java_lang_AbstractMethodError(), "", note_no_trap);
        }
 
        //profile统计
        BI_PROFILE_UPDATE_VIRTUALCALL(rcvr->klass());
 
        //执行方法调用
        istate->set_callee(callee);
        istate->set_callee_entry_point(callee->from_interpreted_entry());
 
        //字节码指针往后移动5个,_invokeinterface的指令长度是5
        istate->set_bcp_advance(5);
        UPDATE_PC_AND_RETURN(0); // I'll be back...
      }
3、invokevirtual,invokespecial,invokestatic
   invokevirtual,invokespecial,invokestatic三个指令的实现都是一样的,同样也是参考bytecodeInterpreter.cpp 2634行,源代码说明如下:

CASE(_invokevirtual):
      CASE(_invokespecial):
      CASE(_invokestatic): {
        //获取运行时常量池中目标方法的符号引用的索引
        u2 index = Bytes::get_native_u2(pc+1);
 
        //获取运行时常量池指定索引的符号引用解析对象
        ConstantPoolCacheEntry* cache = cp->entry_at(index);
        
        if (!cache->is_resolved((Bytecodes::Code)opcode)) {
          //如果未解析则解析符号引用
          CALL_VM(InterpreterRuntime::resolve_invoke(THREAD, (Bytecodes::Code)opcode),
                  handle_exception);
          cache = cp->entry_at(index);
        }
 
        istate->set_msg(call_method);
        {
          Method* callee;
          //如果是invokevirtual指令
          if ((Bytecodes::Code)opcode == Bytecodes::_invokevirtual) {
            //判断方法调用实例对象是否为空
            CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
            //如果是final方法
            if (cache->is_vfinal()) {
              //final方法不需走虚方法表分派,直接使用符号引用解析的结果
              callee = cache->f2_as_vfinal_method();
              // Profile final方法调用统计
              BI_PROFILE_UPDATE_FINALCALL();
            } else {
              // get receiver
              int parms = cache->parameter_size();
              //获取方法调用的实例对象
              oop rcvr = STACK_OBJECT(-parms);
              VERIFY_OOP(rcvr);
              //获取方法调用实例对象的实际klass
              InstanceKlass* rcvrKlass = (InstanceKlass*)rcvr->klass();
              //获取虚方法表中相同索引处的方法
              callee = (Method*) rcvrKlass->start_of_vtable()[ cache->f2_as_index()];
              //vitual 调用 profile统计
              BI_PROFILE_UPDATE_VIRTUALCALL(rcvr->klass());
            }
          } else {
            //invokespecial指令
            if ((Bytecodes::Code)opcode == Bytecodes::_invokespecial) {
              //判断方法调用实例对象是否为空
              CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
            }
            //invokespecial指令调用的方法不需要分派,直接使用符号引用解析的方法
            callee = cache->f1_as_method();
 
            // Profile统计
            BI_PROFILE_UPDATE_CALL();
          }
          //执行方法调用
          istate->set_callee(callee);
          istate->set_callee_entry_point(callee->from_interpreted_entry());
          //字节码指针往后移动3个,invokevirtual,invokespecial,invokestatic三个指令的指令长度都是3
          istate->set_bcp_advance(3);
          UPDATE_PC_AND_RETURN(0); // I'll be back...
        }
      }

从上述源码分析可知,invokevirtual,invokespecial,invokestatic,invokeinteface四个指令在找到了正确的执行方法后,就直接通过JVM解释器跳转到对应方法的执行了,跟JNI中方法调用有很大的不同

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容