Getting Started with the G1 Garbage Collector

原文
施工中

Hotspot Architecture

Hotspot虚拟机架构

JVM主要组件包括类加载器,运行时数据区和执行引擎。

Key Hotspot Components

关键组件

在进行性能调优时,主要关注虚拟机的三个组件。java堆保存着对象实例,由JVM启动时选定的GC进行维护。大部分调优参数都涉及调整java堆及内部各区域的大小、选择最适合的GC。JIT编译器对性能也有巨大影响,但新近的虚拟机很少需要针对它进行调优。

Performance Basics

性能基础

java应用调优一般关注两个指标:响应时间和吞吐量。

Responsiveness

响应时间

响应时间指的是应用对请求的响应时间,如:

  • 一个桌面应用对时间的响应时间
  • 网站返回一个页面的时间
  • 数据库查询结果返回时间

对于专注于最小响应时间的应用,长时间停顿是无法接受的。

Throughput

吞吐量

吞吐量专注于应用在一段时间的的最大工作量。如:

  • 给定时间内完成的事物数
  • 每小时批处理程序能够完成的任务
  • 每小时数据库可以完成的查询操作数

较长停顿时间在此情况下是可以接受的。比起低响应时间,吞吐量优先应用更看重一段时间内的表现。

The G1 Garbage Collector

G1垃圾收集器

Garbage-First(G1)收集器时一款服务端垃圾收集器,主要针对多处理器、大内存环境设计。在尽可能满足GC停顿时间目标的同时获取更高的吞吐量。

G1的设计目标是满足:

  • 像CMS那样做到并发GC,提高GC并行和并发表现
  • 没有内存碎片问题,内存整理过程不需要延长GC时间,不需要虚拟机停顿STW
  • 可预测的GC停顿时间
  • 更高的吞吐量
  • 在不增加堆内存大小下更好地利用堆内存

G1的远期目标时替代CMS+ParNew(自适应的、分代的、停顿-复制、标记-清理并发垃圾回收器)组合。与CMS相比,G1优点包括:G1是内存整理虚拟机,G1通过对堆内存划分区域(region),分区管理,避免使用细粒度空闲列表来实现高效内存整理;G1提供比CMS更加可预测的GC停顿时间,并允许用户设定停顿时间目标。

G1 Operational Overview

G1实现概览

老式垃圾回收器(Serial,Parallel,CMS)统一将java堆分成大小固定的三部分:新生代、老年代和永久代。(开启GC Ergonomics自适应调整策略,如Parallel Scavange,可以动态调整新生代大小、eden与survivor比例)所有内存对象最终都保存在这三个区域内。

G1收集器将java堆均分成大小相同的区域(region,1M-32M,最多2000个,最大支持堆内存64G)。一个或多个不连续的区域共同组成eden、survivor或old区,但大小不再固定,这为内存应用提供了极大地弹性。G1垃圾收集过程与CMS类似。G1在堆内存中并发地对所有对象进行标记,决定对象的可达性。经过全局标记,G1了解哪些区域几乎是空的,然后优先收集这些区域,这就是GarbageFirst的命名由来。G1将垃圾收集和内存整理活动专注于那些几乎全是垃圾的区域,并建立停顿预测模型来决定每次GC时回收哪些区域,以满足用户设定的停顿时间。

对于区域的回收通过复制算法实现。在完成标记清理后,G1将这几个区域的存活对象复制到一个单独区域中,实现内存整理和空间释放。这一过程通过多线程并行进行来降低停顿时间,提高吞吐量。通过这样的方式,G1在每次GC过程中持续清理碎片,控制停顿时间满足用户要求。这时过去虚拟机无法做到的。CMS不清理内存碎片(除非通过虚拟机参数设置,在每次或多次FullGC后进行整理),ParallelOld进行全堆整理,会导致较长的停顿时间。

G1不是实时垃圾收集器,它会尽量让停顿时间低于用户设置的停顿时间目标但不能保证一定如此。G1根据历史垃圾收集监测数据来 预测每个区域的回收时间,然后根据用户设定的目标停顿时间决定每次GC时可以回收哪些区域。G1通过这种方式建立比较精确的区域回收时间预测模型。

注意:G1有并发阶段(与应用线程并行,无停顿时间)和并行阶段(多线程,应用工作线程停顿)。FullGC仍是单线程,但如果调优合适,FullGC是可以避免的。

G1 Footprint

内存占用

如果从ParallelOldGC或CMS迁移至G1,你会发现JVM线程占用内存增大。增大的部分主要与'accouting'数据结构有关,如Remembered Sets(RSets)和Collection Sets(CSets)。

  • Remembered Sets RSets跟踪指向某个区域的对象引用。每个区域对应一个RSet。RSets对整体内存占用的影响少于5%。

  • Collection Sets CSets 在一次GC中将被回收的区域集合。所有CSet区域中的存活对象都会被移动到新的区域中,这些区域可以是Eden区、survivor区或老年代。CSets对JVM内存占用影响少于1%。

Recommended Use Cases for G1

G1的推荐使用场景

G1的设计初衷是为用户提供大内存、低GC停顿时间的应用解决方案。这意味着堆内存6G或更大,停顿时间稳定且少于0.5秒。

如果应用正在使用CMS或ParallelOld且面临以下问题,推荐将应用迁移至G1

  • FullGC发生频繁或总时间过长
  • 对象分配率或对象升级至老年代的比例波动较大
  • 较长的垃圾收集或内存整理停顿(大于0.5至1秒)

注意:如果应用没有上述问题,不需要迁移虚拟机。G1并不是最新JDK要求的虚拟机。

Reviewing Generational GC and CMS

回顾分代GC和CMS

CMS(并发低停顿收集器)收集老年代。通过将大部分垃圾回收工作与应用线程并发进行,尽可能降低停顿时间。通常CMS不回去复制和整理内存。GC过程不会移动对象。如果内存碎片问题严重,扩充堆内存。

CMS Collection Phases

CMS回收步骤

CMS在老年代上回收步骤如下:

阶段 描述
初始标记(STW) 标记GCRoot直接引用的的老年代对象,包括从新生代引用的对象,停顿时间相比minorGC非常短
并发标记 遍历老年代对象,此时应用线程并发执行。从已经标记的对象和GCRoot开始标记所有可达对象。所有并发阶段新分配的对象或从新生代升级到老年代的对象都标记为可达对象
重新标记(STW) 暂停应用现成,修正并发标记阶段由于应用线程运行导致没有标记的对象
并发清除 收集标记为不可达的对象。对象内存空间被添加到空闲列表中等待分配,这些回收对象的空间可以被合并。存活对象在这一阶段不会移动
重置 重置GC数据结构,为下次GC做准备

Heap Structure for CMS Collector

CMS收集器堆内存结构

堆内存被分配为三部分。

新生代分为Eden区和两个survivor区。老年代是一块连续区域。只有FullGC时才可能发生内存整理。

How Young GC works in CMS

CMS中的新生代GC

新生代淡绿色,老年代蓝色。系统运行一段时间后CMS堆内存可能如下图所示,对象分散在老年代各处。

老年代对象空间原地回收,CMS不会移动它们。内存整理只会发生在FullGC时。

Young Generation Collection

新生代回收

新生代存活对象从Eden区和survivor区复制到另一个空闲的survivor区。任何minorGC年龄达到阈值的老对象被升级至老年代。

After Young GC

新生代GC后

youngGC(minorGC)后,Eden区和一个survivor区被清空。

新近升级至老年代的对象以深蓝色表示。绿色对象是新生代仍存活的对象。

Old Generation Collection with CMS

CMS老年代垃圾回收

在初始标记和重新标记阶段发生STW。当老年代剩余空间达到阈值时(发生Concurrent Mode Failure),使用SerialOld替代CMS进行老年代GC。

(1)初始标记停顿时间很短,简单的标记GCRoot引用的对象。(2)并发标记在标记存活对象时,应用继续执行。(3)重新标记阶段,标记并发阶段遗漏的存活对象。

Old Generation Collection - Concurrent Sweep

老年代垃圾收集-并发清除

未被标记的对象直接被回收,不会发生内存整理。

Old Generation Collection - After Sweeping

老年代垃圾收集-清扫后

完成并发清扫后,许多老年代空间被释放出来。同时内存整理仍没有发生,老年代存在大量内存碎片。

最终,CMS经过重置阶段,等待下一次GC。

The G1 Garbage Collector Step by Step

G1垃圾收集步骤

G1 Heap Structure

G1堆内存结构

G1的堆内存被分成许多固定大小的区域。

区域大小相同,在JVM启动时确定。JVM通常管理2000个区域,区域大小介于1M到32M之间。

G1 Heap Allocation

G1堆内存分配

这些区域被映射为逻辑上的Eden、survivor和老年代区域,相同类型区域地址可以不连续。

区域分为五种,Eden、survivor和老年代,另外还有一种大对象区,这些区域用来存放占据50%以上区域空间的对象,这些对象被存储在一组连续区域内,最后一种区域就是未被使用区域。


G1不要求区域连续。

A Young GC in G1

G1的新生代GC

存活对象移动到一个或多个survivor区域。如果对象达到晋升年龄,将被移动到老年代区域。

这一阶段包括STW。eden区和survivor区大小重新计算为下一次youngGC做准备。这一过程使得动态调整各区大小非常简单。

End of a Young GC with G1

G1的youngGC结束

存活对象被移动到survivor区或老年代区域。

G1新生代有如下总结:

  • 堆内存是一个单独的内存区域,被分为多个大小相同的区域
  • 新生代内存由一系列不连续的区域组成。按需调整内存大小很简单。
  • 新生代垃圾回收(youngGC)需要STW,所有应用线程需要停顿。
  • youngGC多线程并行执行。
  • 存活对象复制到新的survivor区或老年代区。

Old Generation Collection with G1

G1老年代垃圾回收

G1 Collection Phases - Concurrent Marking Cycle Phases

G1回收阶段-并发标记周期阶段

G1回收器在对内存的老年代区域执行以下阶段。注意这些阶段也包括新生代的回收。

阶段 描述
初始标记STW 捎带一次youngGC。标记可能引用了老年代区域对象的survivor区域(根区域)
根区域扫描 扫描根区域中指向老年代的引用。与应用现成并发。此阶段完成后才可以进行youngGC
并发标记 全堆扫描存活对象。与应用现成并发。这一阶段可以被youngGC打断
重新标记STW 完成堆中所有存活对象的扫描,使用snapshot-at-the-beginningSATB算法
清除 统计存活对象和空区域(STW),更新RSets(STW),重置空区域,加入空白列表(并发)
复制STW 将存活对象移动至未使用区域

G1 Old Generation Collection Step by Step

G1老年代回收步骤

Initial Marking Phase

初始标记阶段

新生代垃圾收集捎带着一次存活对象的初始标记。在GC日志中打印为GCpause(young)(inital-mark)

Concurrent Marking Phase

并发标记阶段

此阶段如果发现空区域,将会在再次标记阶段立即回收。同时,计算各区域活跃度(回收优先级)的所需的信息在这个阶段统计。

Remark Phase

再次标记阶段

这一阶段空区域被回收,计算出所有区域活跃度。

Copying/Cleanup Phase

复制/清理阶段

G1选择那些活跃度最低,回收速度最快的区域进行回收。这些区域回收的同时进行youngGC。GC日志中记录为[GC pause (mixed)]。因此新生代和老年代垃圾回收同时发生。

After Copying/Cleanup Phase

复制/清理阶段后

被选中区域的存活对象移动到新的区域中,原区域被回收加入空白列表。

Summary of Old Generation GC

老年代GC总结

  • 并发标记阶段
  • 区域活跃度信息的统计与应用线程并发进行
  • 活跃度信息决定了那个区域最先在清理阶段被回收
  • 没有CMS的清理阶段
  • 再次标记阶段
  • SATB算法比CMS使用的算法更快
  • 空区域在这个阶段被回收
  • 复制/清理阶段
  • 新生代和老年代同时被回收
  • 老年代根据活跃度确定回收优先级

关于Remembered Set概念:G1收集器中,Region之间的对象引用以及其他收集器中的新生代和老年代之间的对象引用是使用Remembered Set来避免扫描全堆。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序对Reference类型数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之间(在分代中例子中就是检查是否老年代中的对象引用了新生代的对象),如果是便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当内存回收时,在GC根节点的枚举范围加入Remembered Set即可保证不对全局堆扫描也不会有遗漏。

Command Line Options and Best Practices

JVM参数最佳实践

Basic Command Line

基本命令

启用G1 -XX:+UserG1GC
以下是使用G1收集器的javademo演示
java -Xmx50m -Xms50m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar

Key Command Line Switches

关键命令

  • -XX:+UseG1GC - 启用G1
  • -XX:MaxGCPauseMillis=200 - GC停顿的最长时间。这是一个软目标,即虚拟机会尽最大可能满足这一时间,但某些情况下仍可能超过。默认值为200ms。
  • -XX:InitiatingHeapOccupancyPercent=45 - 堆内存使用率达到多少时启动一次GC过程。GC过程涉及整个堆内存,不单指某个年龄代。设为0时表示循环进行并发GC。默认值为45,即堆内存使用45%后触发一次GC。

Best Practices

最佳实践

Do not Set Young Generation Size

不要设置新生代大小

设置新生代大小-Xmn会影响G1的回收表现。

  • G1不在遵守目标停顿时间,即设置新生代大小后,目标停顿时间失效。
  • G1不能按需扩展或收缩新生代空间。

Response Time Metrics

响应时间指标

不要根据平均响应时间ART设置MaxGCPauseMillis参数,将这个值设置为90%的用户发起请求时的等待时间不会超过的值。

What is an Evacuation Failure?

转移失败

在GC过程中,堆内存区域耗尽,新生代对象晋升老年代失败。堆内存由于已经达到最大而无法扩张。在GClog中打印to-space overflow日志。这会耗费大量资源。

  • GC仍要继续,所以空间必须被释放。
  • 拷贝失败的对象必须留在原区域。
  • 这一过程中,CSets中区域的RSets如果发生变化,RSets需要重新生成。
  • 这些步骤都非常耗费资源。

How to avoid Evacuation Failure

如何避免转移失败

  • 增加堆内存大小
  • 提高-XX:G1ReservePercent,默认值是10
  • G1为堆内存设置虚拟使用上限,预留一部分空间防止to-space情况出现
  • 提前进行标记阶段
  • 提高标记阶段的线程数,-XX:ConcGCThreads

Complete List of G1 GC Switches

G1 GC参数一览

参数与默认值 说明
-XX:+UserG1GC 使用G1收集器
-XX:MaxGCPauseMillis=200 设置最大停顿时间,软目标,虚拟机不保证总是达到要求
-XX:InitiatingHeapOccupancyPercent=45 当堆内存使用率达到多少时启动一次GC周期。GC周期针对整个堆内存而不是某个年龄代。设为0时表示持续进行GC周期
-XX:NewRatio=2 新生代与老年代大小比例
-XX:SurvivorRatio=8 Eden区与survivor区大小比例
-XX:MaxTenuringThreshold=15 新生代存活过多少次youngGC后晋升老年代
-XX:ConcGCThreads 并发垃圾回收器使用的线程数,默认值根据JVM运行平台不同而变化
-XX:ParallelGCThreads 在并行阶段垃圾回收器线程数
-XX:G1ReservePercent=10 预留多少个区域,防止出现转移失败导致to-space overflow
-XX:G1HeapRegionSize G1将堆内存划分为大小相同的多个区域。区域大小由这个参数确定。默认值与堆内存大小有关。不小于1M,不大于32M。总数2000

Logging GC with G1

G1的GC日志

Setting the Log Detail
You can set the detail to three different levels of detail.
(1) -verbosegc (which is equivalent to -XX:+PrintGC) sets the detail level of the log to fine.
Sample Output
[GC pause (G1 Humongous Allocation) (young) (initial-mark) 24M- >21M(64M), 0.2349730 secs][GC pause (G1 Evacuation Pause) (mixed) 66M->21M(236M), 0.1625268 secs]
(2) -XX:+PrintGCDetails sets the detail level to finer. The options shows the following information:
Average, Min, and Max time are displayed for each phase.
Root Scan, RSet Updating (with processed buffers information), RSet Scan, Object Copy, Termination (with number of attempts).
Also shows “other” time such as time spent choosing CSet, reference processing, reference enqueuing and freeing CSet.
Shows the Eden, Survivors and Total Heap occupancies.

Sample Output
[Ext Root Scanning (ms): Avg: 1.7 Min: 0.0 Max: 3.7 Diff: 3.7][Eden: 818M(818M)->0B(714M) Survivors: 0B->104M Heap: 836M(4096M)->409M(4096M)]
(3) -XX:+UnlockExperimentalVMOptions -XX:G1LogLevel=finest sets the detail level to its finest. Like finer but includes individual worker thread information.
[Ext Root Scanning (ms): 2.1 2.4 2.0 0.0 Avg: 1.6 Min: 0.0 Max: 2.4 Diff: 2.3] [Update RS (ms): 0.4 0.2 0.4 0.0 Avg: 0.2 Min: 0.0 Max: 0.4 Diff: 0.4] [Processed Buffers : 5 1 10 0 Sum: 16, Avg: 4, Min: 0, Max: 10, Diff: 10]
Determining Time
A couple of switches determine how time is displayed in the GC log.
(1) -XX:+PrintGCTimeStamps - Shows the elapsed time since the JVM started.
Sample Output
1.729: [GC pause (young) 46M->35M(1332M), 0.0310029 secs]
(2) -XX:+PrintGCDateStamps - Adds a time of day prefix to each entry.
2012-05-02T11:16:32.057+0200: [GC pause (young) 46M->35M(1332M), 0.0317225 secs]
Understanding G1 Log
To understand the log, this section defines a number of terms using actual GC log output. The following examples show output from the log with explanations of the terms and values you will find in it.
Note: For more information check out Poonam Bajaj's Blog post on G1 GC logs.
G1 Logging Terms Index
Clear CT
CSet
External Root Scanning
Free CSet
GC Worker End
GC Worker Other
Object Copy
Other
Parallel Time
Ref Eng
Ref Proc
Scanning Remembered Sets
Termination Time
Update Remembered Set
Worker Start
Parallel Time
414.557: [GC pause (young), 0.03039600 secs] [Parallel Time: 22.9 ms][GC Worker Start (ms): 7096.0 7096.0 7096.1 7096.1 706.1 7096.1 7096.1 7096.1 7096.2 7096.2 7096.2 7096.2 Avg: 7096.1, Min: 7096.0, Max: 7096.2, Diff: 0.2]
Parallel Time – Overall elapsed time of the main parallel part of the pause
Worker Start – Timestamp at which the workers start
Note: The logs are ordered on thread id and are consistent on each entry
External Root Scanning
[Ext Root Scanning (ms): 3.1 3.4 3.4 3.0 4.2 2.0 3.6 3.2 3.4 7.7 3.7 4.4 Avg: 3.8, Min: 2.0, Max: 7.7, Diff: 5.7]
External root scanning - The time taken to scan the external root (e.g., things like system dictionary that point into the heap.)
Update Remembered Set
[Update RS (ms): 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 Avg: 0.0, Min: 0.0, Max: 0.1, Diff: 0.1] [Processed Buffers : 26 0 0 0 0 0 0 0 0 0 0 0 Sum: 26, Avg: 2, Min: 0, Max: 26, Diff: 26]
Update Remembered Set - Any buffers that are completed but have not yet been processed by the concurrent refinement thread before the start of the pause have to be updated. Time depends on density of the cards. The more cards, the longer it will take.
Scanning Remembered Sets
[Scan RS (ms): 0.4 0.2 0.1 0.3 0.0 0.0 0.1 0.2 0.0 0.1 0.0 0.0 Avg: 0.1, Min: 0.0, Max: 0.4, Diff: 0.3]F
Scanning Remembered Sets - Look for pointers that point into the Collection Set.
Object Copy
[Object Copy (ms): 16.7 16.7 16.7 16.9 16.0 18.1 16.5 16.8 16.7 12.3 16.4 15.7 Avg: 16.3, Min: 12.3, Max: 18.1, Diff: 5.8]
Object copy – The time that each individual thread spent copying and evacuating objects.
Termination Time
[Termination (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.00.0 Avg: 0.0, Min: 0.0, Max: 0.0, Diff: 0.0] [Termination Attempts : 1 1 1 1 1 1 1 1 1 1 1 1 Sum: 12, Avg: 1, Min: 1, Max: 1, Diff: 0]
Termination time - When a worker thread is finished with its particular set of objects to copy and scan, it enters the termination protocol. It looks for work to steal and once it's done with that work it again enters the termination protocol. Termination attempt counts all the attempts to steal work.
GC Worker End
[GC Worker End (ms): 7116.4 7116.3 7116.4 7116.3 7116.4 7116.3 7116.4 7116.4 7116.4 7116.4 7116.3 7116.3 Avg: 7116.4, Min: 7116.3, Max: 7116.4, Diff: 0.1][GC Worker (ms): 20.4 20.3 20.3 20.2 20.3 20.2 20.2 20.2 20.3 20.2 20.1 20.1 Avg: 20.2, Min: 20.1, Max: 20.4, Diff: 0.3]
GC worker end time – Timestamp when the individual GC worker stops.
GC worker time – Time taken by individual GC worker thread.
GC Worker Other
[GC Worker Other (ms): 2.6 2.6 2.7 2.7 2.7 2.7 2.7 2.8 2.8 2.8 2.8 2.8 Avg: 2.7, Min: 2.6, Max: 2.8, Diff: 0.2]
GC worker other – The time (for each GC thread) that can't be attributed to the worker phases listed previously. Should be quite low. In the past, we have seen excessively high values and they have been attributed to bottlenecks in other parts of the JVM (e.g., increases in the Code Cache occupancy with Tiered).
Clear CT
[Clear CT: 0.6 ms]
Time taken to clear the card table of RSet scanning meta-data
Other
[Other: 6.8 ms]
Time taken for various other sequential phases of the GC pause.
CSet
[Choose CSet: 0.1 ms]
Time taken finalizing the set of regions to collect. Usually very small; slightly longer when having to select old.
Ref Proc
[Ref Proc: 4.4 ms]
Time spent processing soft, weak, etc. references deferred from the prior phases of the GC.
Ref Enq
[Ref Enq: 0.1 ms]
Time spent placing soft, weak, etc. references on to the pending list.
Free CSet
[Free CSet: 2.0 ms]
Time spent freeing the set of regions that have just been collected, including their remembered sets.

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

推荐阅读更多精彩内容