JVM学习笔记之内存分配与回收策略【六】

内存分配与回收策略

以下例子使用 openjdk8 测试

一、对象优先在 Eden 分配

大多数情况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。

  • 虚拟机参数

    • -Xms20M -Xmx20M : 限制堆的大小为 20M
    • -Xmn10M :分配 10M 给新生代
    • -XX:SurvivorRatio=8 : 新生代中 Eden 区与一个 Survivor 区的空间比例是 8:1:1
    • -XX:+PrintGCDetails : 收集器日志参数,虚拟机在发生垃圾收集行为时打印内存回收日志
    • -XX:+UseSerialGC : 使用 Serial 收集器
  • 代码清单

/**
 * @Description: 优先分配Eden
 * <p>
 * 虚拟机参数: 使用Serial加SerialOld客户端默认收集器组合下的内存分配和回收的策略(-XX:+UseSerialGC)
 * <pre>
 *     -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
 * </pre>
 *
 * @Author hdj
 * @Date 2021/1/10 下午5:01
 */
public class AllocationEden {

    public static final int _1M = 1024 * 1024;
    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3, allocation4;

        allocation1 = new byte[2 * _1M];
        allocation2 = new byte[2 * _1M];
        allocation3 = new byte[2 * _1M];
        //出现一次minor GC
        allocation4 = new byte[4 * _1M];
    }
}
  • 输出的 GC 日志

    [GC (Allocation Failure) [DefNew: 7996K->469K(9216K), 0.0051882 secs] 7996K->6613K(19456K), 0.0052237 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
    Heap
     def new generation   total 9216K, used 4647K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
      eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
      from space 1024K,  45% used [0x00000000ff500000, 0x00000000ff5755d8, 0x00000000ff600000)
      to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
     tenured generation   total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
       the space 10240K,  60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
     Metaspace       used 3169K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 342K, capacity 388K, committed 512K, reserved 1048576K
    

从 GC 的日志可以看出 新生代总内存:9216K,已使用:4647K

二、大对象直接进入老年代

大对象就是指需要大量连续内存空间的 Java 对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组。大对象对虚拟机的内存分配来说就是一个不折不扣的坏消息,比遇到一个大对象更加坏的消息就是遇到一群“朝生夕灭”的“短命大对象”,我们写程序的时候应注意避免。在 Java 虚拟机中要避免大对象的原因是,在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们,而当复制对象时,大对象就意味着高额的内存复制开销。

  • 虚拟机参数

    • -XX:PretenureSizeThreshold: 指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在 Eden 区及两个 Survivor 区之间来回复制,产生大量的内存复制操作。注意:该参数只对 Serial 和 ParNew 两款新生代收集器有效
    • -verbose:gc
    • Xms20M -Xmx20M -Xmn10M : 限制堆的大小为 20M, 新生代 10M
    • -XX:+UseSerialGC
    • -XX:+PrintGCDetails -XX:SurvivorRatio=8
  • 代码

    /**
     * @Description: PretenureSizeThreshold 测试
     * <p>
     * jvm args :
     * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC
     * -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
     * @Author hdj
     * @Date 2021/2/1 下午11:10
     */
    public class PretenureSizeThresholdTest {
       public static final int _1M = 1024 * 1024;
        public static void main(String[] args) {
            byte[] allocate;
            allocate = new byte[4 * _1M];
        }
    }
    
  • 输出的 GC 日志

    Heap
     def new generation   total 9216K, used 2017K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
      eden space 8192K,  24% used [0x00000000fec00000, 0x00000000fedf84d8, 0x00000000ff400000)
      from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
      to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
     tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
       the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00010, 0x00000000ffa00200, 0x0000000100000000)
     Metaspace       used 3167K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 342K, capacity 388K, committed 512K, reserved 1048576K
    

    有 GC 日志可以看出 tenured generation 使用了 4096k,分配的 4M 字节数组直接被分配在老年代。

三、长期存活的对象将进入老年代

HotSpot 虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存活对象应当放在新生代,哪些存活对象放在老年代中。为做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器,存储在对象头中。对象通常在 Eden 区里诞生,如果经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,该对象会被移动到 Survivor 空间中,并且将其对象年龄设为 1 岁。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过以下虚拟机参数设置。

  • 虚拟机参数

    • -XX:MaxTenuringThreshold : 设置对象晋升老年代的年龄阈值
  • 代码

    /**
     * @Description: 设置对象晋升老年代的年龄阈值
     *
     * 虚拟机参数:
    * <p>
     * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
    * @Author hdj
     * @Date 2021/2/1 下午11:29
     */
    public class TenuringThresholdTest {
    
        public static final int _1M = 1024 * 1024;
        public static void main(String[] args) {
            byte[] allocate, allocate2, allocate3;
            allocate = new byte[_1M / 4];
            //什么时候进入老年代取决于XX:MaxTenuringThreshold设置
            allocate2 = new byte[_1M * 4 ];
            allocate3 = new byte[_1M * 4 ];
            allocate3 = null;
            allocate3 = new byte[_1M * 4];
    
        }
    }
    
  • 设置-XX:MaxTenuringThreshold=15

    [GC (Allocation Failure) [DefNew
    Desired survivor size 524288 bytes, new threshold 1 (max 15)
    - age   1:     742936 bytes,     742936 total
    : 6204K->725K(9216K), 0.0043667 secs] 6204K->4821K(19456K), 0.0043995 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
    [GC (Allocation Failure) [DefNew
    Desired survivor size 524288 bytes, new threshold 15 (max 15)
    : 4821K->0K(9216K), 0.0014240 secs] 8917K->4812K(19456K), 0.0014512 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    Heap
     def new generation   total 9216K, used 4178K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
      eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
      from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
      to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
     tenured generation   total 10240K, used 4812K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
       the space 10240K,  47% used [0x00000000ff600000, 0x00000000ffab3390, 0x00000000ffab3400, 0x0000000100000000)
     Metaspace       used 3167K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 342K, capacity 388K, committed 512K, reserved 1048576K
    
  • 设置-XX:MaxTenuringThreshold=1

    [GC (Allocation Failure) [DefNew
    Desired survivor size 524288 bytes, new threshold 1 (max 1)
    - age   1:     742936 bytes,     742936 total
    : 6204K->725K(9216K), 0.0046998 secs] 6204K->4821K(19456K), 0.0047442 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
    [GC (Allocation Failure) [DefNew
    Desired survivor size 524288 bytes, new threshold 1 (max 1)
    : 4821K->0K(9216K), 0.0015807 secs] 8917K->4812K(19456K), 0.0016155 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    Heap
     def new generation   total 9216K, used 4178K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
      eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
      from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
      to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
     tenured generation   total 10240K, used 4812K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
       the space 10240K,  47% used [0x00000000ff600000, 0x00000000ffab3390, 0x00000000ffab3400, 0x0000000100000000)
     Metaspace       used 3166K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 342K, capacity 388K, committed 512K, reserved 1048576K
    

对于上面的代码,不管设置-XX:MaxTenuringThreshold=1 或者 15,都在第二次 Minor GC 后,都进入了老年代。

这里就有个疑问了,不是设置-XX:MaxTenuringThreshold 进入老年代的阀值吗,为什么不起作用呢?

  • 这里就涉及一个新的概念: 动态对象年龄判定——如果在 Survivor 空间中【低或等于某个年龄的】所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold 中要求的年龄,就可以直接晋升老年代。

看了动态对象年龄判定的简单解析,是不是感觉还有一个疑问,分配的 allocate 256K 没有超过 Survivor 空间的一半啊? 下面分析以下内存变化

  • -Xms20M -Xmx20M -Xmn10M 堆内存分配了 20M,新生代 10M,老年代 10M
  • -XX:SurvivorRatio=8 新生代比例 8:1:1,即 eden:8M ,survivor from : 1M , survivor to:1M
  • allocate 分配 256K,allocate2 分配 4M,此时 eden 应该占用 4M + 256K ,但是实际还有其它,猜测是 Java 内部创建的对象占用(不知道是哪些? 知道的大佬,请告知小弟)。
  • 这时 allocate3 分配 4M, 由于 eden 内存不够分配,触发一次 MinorGC,allocate2 晋升老年代,allocate 进入 survivor,但此时看 设置-XX:MaxTenuringThreshold=15的 GC 日志 6204K->725K(9216K) ,新生代 还有 725K 占用,所以除了 allocate 256K, 还有其他对象占用。 最后 allocate3 成功分配 4M 到 eden。
  • eden 占用 4M ,设置 allocate3 = null,又准备重新分配 allocate3=4M,eden 内存再次不够分配,又触发一次 MinorGC,由于 allocate3 置空,分配在 eden 的对象因为没有 GC Roots 引用,被垃圾回收掉,survivor 中被占用 725K,满足动态对象年龄判定条件,也晋升老年代 ,从 GC 日志 4821K->0K(9216K)中可以看出,最后 allocate3 成功分配 4M 到 eden。

四、动态对象年龄判定

为了能更好地适应不同程序的内存状况,HotSpot 虚拟机并不是永远要求对象的年龄必须达到-XX:MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 空间中【低或等于某个年龄的】所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold 中要求的年龄。

  • https://github.com/fenixsoft/jvm_book/issues/13

  • -XX:TargetSurvivorRatio : 设定 survivor 区的目标使用率。默认 50,即 survivor 区对象目标使用率为 50%,即设置 desired_survivor_size

    size_t G1Policy::desired_survivor_size(uint max_regions) const {
      size_t const survivor_capacity = HeapRegion::GrainWords * max_regions;
      return (size_t)((((double)survivor_capacity) * TargetSurvivorRatio) / 100);
    }
    
  • jdk 源码, 计算进入老年代年龄阀值

    //编译的jdk13源码
    uint AgeTable::compute_tenuring_threshold(size_t desired_survivor_size) {
      uint result;
    
      if (AlwaysTenure || NeverTenure) {
        assert(MaxTenuringThreshold == 0 || MaxTenuringThreshold == markOopDesc::max_age + 1,
               "MaxTenuringThreshold should be 0 or markOopDesc::max_age + 1, but is " UINTX_FORMAT, MaxTenuringThreshold);
        result = MaxTenuringThreshold;
      } else {
        size_t total = 0;
        uint age = 1;
        assert(sizes[0] == 0, "no objects with age zero should be recorded");
        while (age < table_size) {
          total += sizes[age];
          // check if including objects of age 'age' made us pass the desired
          // size, if so 'age' is the new threshold
          if (total > desired_survivor_size) break;
          age++;
        }
        result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
      }
    
    
      log_debug(gc, age)("Desired survivor size " SIZE_FORMAT " bytes, new threshold " UINTX_FORMAT " (max threshold " UINTX_FORMAT ")",
                         desired_survivor_size * oopSize, (uintx) result, MaxTenuringThreshold);
    
      return result;
    }
    
  • 测试 动态年龄设置

    /**
     * @Description: 动态对象年龄判定
     *
     * <p>
     * 虚拟机参数:
     * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution
     * @Author hdj
     * @Date 2021/2/1 下午11:29
     */
    public class TenuringThresholdTest2 {
    
        public static final int _1M = 1024 * 1024;
    
        public static void main(String[] args) {
            byte[] allocate, allocate2, allocate3, allocate4;
    
            //内部占用 460K左右,注释allocate 和 allocate2的赋值, 在执行一次 MinorGC后,查看GC日志 5781K->461K(9216K)
            //因为这两个对象加起来已经到达了70K + 461K > 512K,并且它们是同年龄的,满足同年龄对象达到Survivor空间一半的规则
            //所以进入了老年代
            allocate = new byte[1024 * 35];
            allocate2 = new byte[1024 * 35];
    
            allocate3 = new byte[_1M * 4];
            allocate4 = new byte[_1M * 4];
            allocate4 = null;
            allocate3 = new byte[_1M * 4];
        }
    }
    
  • 没有注释 allocate2 变量赋值运行

    [GC (Allocation Failure) [DefNew
    Desired survivor size 524288 bytes, new threshold 1 (max 15)
    - age   1:     552488 bytes,     552488 total
    : 5948K->539K(9216K), 0.0034109 secs] 5948K->4635K(19456K), 0.0034457 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
    [GC (Allocation Failure) [DefNew
    Desired survivor size 524288 bytes, new threshold 15 (max 15)
    : 4635K->0K(9216K), 0.0009382 secs] 8731K->4626K(19456K), 0.0009562 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    Heap
     def new generation   total 9216K, used 4178K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
      eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
      from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
      to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
     tenured generation   total 10240K, used 4626K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
       the space 10240K,  45% used [0x00000000ff600000, 0x00000000ffa84ba0, 0x00000000ffa84c00, 0x0000000100000000)
     Metaspace       used 3167K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 342K, capacity 388K, committed 512K, reserved 1048576K
    
  • 注释没有注释 allocate2 变量赋值再运行

    [GC (Allocation Failure) [DefNew
    Desired survivor size 524288 bytes, new threshold 15 (max 15)
    - age   1:     491176 bytes,     491176 total
    : 5948K->479K(9216K), 0.0031022 secs] 5948K->4575K(19456K), 0.0031245 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
    [GC (Allocation Failure) [DefNew
    Desired survivor size 524288 bytes, new threshold 15 (max 15)
    - age   1:        144 bytes,        144 total
    - age   2:     482256 bytes,     482400 total
    : 4659K->471K(9216K), 0.0014258 secs] 8756K->4567K(19456K), 0.0014492 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
    Heap
     def new generation   total 9216K, used 4950K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
      eden space 8192K,  54% used [0x00000000fec00000, 0x00000000ff05fbf0, 0x00000000ff400000)
      from space 1024K,  46% used [0x00000000ff400000, 0x00000000ff475c60, 0x00000000ff500000)
      to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
     tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
       the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00010, 0x00000000ffa00200, 0x0000000100000000)
     Metaspace       used 3158K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 340K, capacity 388K, committed 512K, reserved 1048576K
    

五、空间分配担保

在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间

  • 如果这个条件成立,那这一次 Minor GC 可以确保是安全的。

  • 如果不成立

    • 则虚拟机会先查看-XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure);
    • 如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,
    • 如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;
    • 如果小于,或者-XX:HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次Full GC
  • 虚拟机参数

    • -XX:HandlePromotionFailure: 设置值是否允许担保失败(Handle Promotion Failure); JDK 6Update 24 之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC。
  • hotpot 空间分配检查代码

    bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
    //老年代最大可用连续空间
      size_t available = max_contiguous_available();
      //每次晋升老年代的平均大小
      size_t av_promo  = (size_t)gc_stats()->avg_promoted()->padded_average();
          // 老年代可用空间是否大于平均晋升大小,或者老年代可用空间是否大于当此GC时新生代所有对象容量
      bool   res = (available >= av_promo) || (available >= max_promotion_in_bytes);
    
      log_trace(gc)("Tenured: promo attempt is%s safe: available(" SIZE_FORMAT ") %s av_promo(" SIZE_FORMAT "), max_promo(" SIZE_FORMAT ")",
        res? "":" not", available, res? ">=":"<", av_promo, max_promotion_in_bytes);
    
      return res;
    }
    
  • 空间分配担保实践

    /**
     * @Description: 空间分配担保  请在 JDK 6Update 24 之前运行
     * <p>
     * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
     * @Author hdj
     * @Date 2021/2/28 下午12:38
     */
    public class HandlePromotionFailureTest {
    
        public static final int _1M = 1024 * 1024;
    
        public static void main(String[] args) {
    
            byte[] allocate1;
            byte[] allocate2;
            byte[] allocate3;
            byte[] allocate4;
            byte[] allocate5;
            byte[] allocate6;
            byte[] allocate7;
    
            allocate1 = new byte[_1M * 2];
            allocate2 = new byte[_1M * 2];
            allocate3 = new byte[_1M * 2];
            allocate1 = null;
            allocate4 = new byte[_1M * 2];
            allocate5 = new byte[_1M * 2];
            allocate6 = new byte[_1M * 2];
            allocate4 = null;
            allocate5 = null;
            allocate6 = null;
            allocate7 = new byte[_1M * 2];
    
        }
    }
    
    

参考

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

推荐阅读更多精彩内容