jstack命令以及线程转储堆栈分析

一、命令介绍

jstack是jdk自带的jvm分析工具,用于打印指定 java进程core文件 或者远程 调试服务 的java线程栈信息,从而分析java程序性能不佳或者崩溃的问题。另外该命令是实验性的,不被支持。

jstack命令非常简单,使用自描述的帮助文档,可以快速掌握其使用方法:

jstack -help
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

如上,jstack命令作用的对象有三种,不同对象命令格式如下:

  1. 进程: 使用jstack [-l] <pid>来连接到正在运行的进程,使用 jstack -F [-m] [-l] <pid>来连接到挂起的进程。
  2. core/executable: core文件是Linux下程序不正常退出产生的文件;executable文件是可产生core dump文件的java可执行程序;使用jstack [-m] [-l] <executable> <core>命令。
  3. 远程服务: jstack [-m] [-l] [server_id@]<remote server IP or hostname>连接远程服务。

命令详情参考: java 8关于jstack命令的介绍

snapshoot 快照

一个活跃的java程序,其虚拟机内的线程也是活跃的,不断新建销毁,在各个线程状态之间转移。jstack命令,其实是对命令执行时刻的虚拟机线程集合做一个快照,包含了当前时刻虚拟机内所有线程方法栈。便于通过线程方法栈的行为,定位程序性能不佳或者崩溃问题。这些行为主要是线程之间的同步,如死锁,死循环,等待外部资源竞争锁的行为。通过分析离线文件或者附着到正在运行的java进程,定位问题。

一窥方法栈

一段线程方法栈信息如下:

"localhost-startStop-1-SendThread(10.0.24.14:2181)" daemon prio=10 tid=0x00002b0ee8b4e000 nid=0x4b37 waiting for monitor entry [0x00002b0ed5162000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at org.apache.log4j.Category.callAppenders(Category.java:204)
    - waiting to lock <0x00000000db301138> (a org.apache.log4j.spi.RootLogger)
    at org.apache.log4j.Category.forcedLog(Category.java:391)
    at org.apache.log4j.Category.log(Category.java:856)
    at org.slf4j.impl.Log4jLoggerAdapter.info(Log4jLoggerAdapter.java:305)
    at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1156)

跟普通的java方法调用栈很相似,但是又多了些额外的信息,下面重点介绍这些额外的信息。

二、线程状态

jstack打印出的线程状态与java线程状态很相似,但并不是严格意义上相同。

java线程的状态图大概如下:

java线程状态
  1. NEW: 实例化Thread类后的线程对象,不可执行。
  2. RUNNABLE: 调用线程的start()方法后;等待CPU时间片。
  3. RUNNING: 获得CPU时间片,正在执行。
  4. WAITING: 线程等待其他线程执行特定操作,等待时间不确定。
  5. TIMED_WAITING: 线程等待其他线程执行特定操作,等待时间确定。
  6. BLOCKED: 进入同步方法或者同步代码块,没有获取到锁,进入该状态。
  7. TERMINATED: 线程执行完毕,或者抛出未处理的异常,线程结束。

实际上,Thread的内部枚举类 java.lang.Thread.State 也对java线程状态做了介绍,其中没有 RUNNING 状态,以上是考虑CPU调度引入的这个状态。

thread states

jstack导出的线程方法栈状态也是以java线程状态为准,不过,jvm中的实际线程状态不包括NEW以及TERMINATED,只包括:

  1. RUNNABLE
  2. WAITING
  3. TIMED_WAITING
  4. BLOCKED

Monitor机制

操作系统为支持进程/线程间的同步,提供了一些基本的同步原语,其中semaphore信号量 和 mutex互斥量是其中最重要的同步原语。但是使用基础同步原语控制并发时,程序员必须维护繁琐的细节,何时应该加锁,何时应该唤醒进程/线程;为便于开发并发程序,一些高级语言支持了Monitor机制,类似语法糖,操作系统本身不支持Monitor,其实现依赖基础同步原语。

具体到高级语言Java,synchronized ,Object(wait, notify/notifyAll)等元素提供了对Monitor机制的支持。直观的代码体现是 同步方法 或者 同步代码块(虽然java的锁机制也提供线程同步,但锁机制与Monitor机制是不同的)。

下图描述了线程状态转移与Monitor的关系:

thread&monitor

整个monitor分为三个区域,处于不同区域的线程有不同的状态:

  1. Entry Set(进入区):线程欲获取锁,获取锁成功,进入拥有者区域,否者在进入区等待锁;锁释放后,重新参与竞争锁。
  2. The Owner(拥有者):线程成功获得锁。
  3. Wait Set(等待区):由于必要条件不满足,线程通过调用对象的wait方法,释放锁,并进入等待区等待被notify/notifyAll唤醒。

Monitor机制中,有且仅有一个线程可以成为Monitor的拥有者,这是这个线程是 An Active Thread;处于 Entry Set以及Wait Set的线程都是 Waiting Thread

三、线程转储堆栈分析

一条典型的jstack线程栈格式如下:

"线程名" [daemon] prio= os_prio= tid= nid= 线程动作 [线程栈的起始地址]
    java.lang.Thread.State:线程状态 [(进入该状态的原因)]
     方法调用栈
     [-调用修饰]

    Locked ownable synchronizers:
        - <地址> (可持有同步器对象)

第一行说明线程相关信息,包括:

  1. 线程名。
  2. 是否守护线程,daemon标识,非守护线程没有。
  3. 线程优先级。
  4. 线程操作系统优先级。
  5. 线程id。
  6. 操作系统映射的线程id,十六进制字符串,使用这个id与实际操作系统线程id关联。
  7. 线程动作。
  8. 线程栈的起始地址。

线程动作

需要特别说明的是,线程动作,它提供的额外信息利于定位问题;线程动作包括:

  1. runnable: 线程可执行,对应的线程状态一般为RUNNABLE, 也有例外对应TIMED_WAITING。
  2. waiting on condition: 调用park阻塞、等待区等待。
  3. waiting for monitor entry: 处于Monitor Entry Set,对应的线程状态一般是BLOCKED。
  4. in Object.wait(): 处于Monitor Wait Set,状态为WAITING或TIMED_WAITING。
  5. sleeping: 调用了sleep,休眠。

第二行是线程的状态,是java.lang.Thread.State中的一种;后面的括号里是进入该状态的原因(可选)。

方法调用栈紧随其后,重要的同步信息也会被输出。

调用修饰

调用修饰是线程方法调用过程中,重要的同步信息;调用修饰包括:

  1. locked <地址> (目标): 使用synchronized成功获得对象锁,即Monitor的拥有者。
  2. waiting to lock <地址> (目标): 使用synchronized获取对象锁失败,进入Entry Set等待。
  3. waiting on <地址> (目标): 使用synchronized获取对象锁成功后,必要条件不满足,调用object.wait()进入Wait Set等待。
  4. parking to wait for <地址> (目标): 调用park。

同步原语park比较特殊,不属于Monitor机制,他是锁机制的基础支持。由Unsafe类的native方法park实现。

最后一行是指定了 -l 选项才会输出的,额外的锁信息。表示当前线程获得的可持有同步器。由此可见Monitor机制(synchronized系列)的得天独厚,线程方法栈对Monitor机制的同步信息进行了详尽的说明。Monitor同步机制下,Locked ownable synchronizers为None。由于锁机制的Lock只是普通的java类,jvm无从得知其详尽的线程同步情况,因此使用锁机制实现的线程同步,出现问题时,不如monitor机制的同步实现,不利于辨识。

三、线程动作&调用修饰实践

分析jstack的线程方法栈,主要就是分析线程之间的同步信息,以及其方法调用栈。以上详细介绍了分析jstack的理论知识,下面从实际代码角度,分析jstack线程方法栈。

首先准备好几个同步方法,用于多线程环境下调用:

public class JStack {
    // 锁机制
    static Lock lock = new ReentrantLock();
    // 锁条件对象
    static Condition condition = lock.newCondition();

    static boolean first = true;

    /**
     * Monitor机制下的同步
     */
    static void monitorSync() {
        synchronized (JStack.class) {
            while (true) ;
        }
    }

    /**
     * Monitor机制下 条件等待
     *
     * @throws InterruptedException
     */
    static void monitorSyncWait() throws InterruptedException {
        synchronized (JStack.class) {
            if (first) {
                first = false;
                while (!first) {
                    JStack.class.wait();
                }
            }
            while (true) ;
        }
    }

    /**
     * lock机制下的同步
     */
    static void lockSync() {
        lock.lock();
        try {
            while (true) ;
        } finally {
            lock.unlock();
        }
    }

    /**
     * lock机制下条件等待
     *
     * @throws InterruptedException
     */
    static void lockSyncAwait() throws InterruptedException {
        lock.lock();
        try {
            if (first) {
                first = false;
                while (!first) {
                    condition.await();
                }
            }
            while (true) ;
        } finally {
            lock.unlock();
        }
    }
}

1) Monitor机制,synchronized竞争锁

多线程synchronized竞争锁:

public static void main(String[] args) {
    // Monitor机制,synchronized竞争锁
    Thread monitorSync1 = new Thread(JStack::monitorSync);
    monitorSync1.setName("SYNC monitor#1");
    Thread monitorSync2 = new Thread(JStack::monitorSync);
    monitorSync2.setName("SYNC monitor#2");

    monitorSync1.start();
    monitorSync2.start();
}
"SYNC monitor#2" #12 prio=5 os_prio=0 tid=0x000000001f299800 nid=0x3630 waiting for monitor entry [0x000000001fc7f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at jstack.JStack.monitorSync(JStack.java:27)
        - waiting to lock <0x000000076b87f128> (a java.lang.Class for jstack.JStack)
        at jstack.JStack$$Lambda$2/1989780873.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - None

"SYNC monitor#1" #11 prio=5 os_prio=0 tid=0x000000001f299000 nid=0x1854 runnable [0x000000001fb7f000]
   java.lang.Thread.State: RUNNABLE
        at jstack.JStack.monitorSync(JStack.java:27)
        - locked <0x000000076b87f128> (a java.lang.Class for jstack.JStack)
        at jstack.JStack$$Lambda$1/2093631819.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - None

可见,Monitor机制下,使用synchronized竞争锁,

  • Monitor拥有者SYNC monitor#1的调用修饰是 lock,线程动作是runnable,状态是RUNNABLE
  • 竞争锁失败的线程SYNC monitor#2的调用修饰是 waiting to lock,地址和目标与拥有者线程相同,线程动作是waiting for monitor entry(在Entry Set等待),状态是BLOCKED(在对象监视器上阻塞)。

2) 锁机制,竞争锁

多线程竞争lock锁:

public static void main(String[] args) {
    //  锁机制,竞争锁
    Thread lockSync1 = new Thread(JStack::lockSync);
    lockSync1.setName("SYNC lock#1");
    Thread lockSync2 = new Thread(JStack::lockSync);
    lockSync2.setName("SYNC lock#2");

    lockSync1.start();
    lockSync2.start();
}
"SYNC lock#2" #12 prio=5 os_prio=0 tid=0x000000001f1e8800 nid=0x4d8 waiting on condition [0x000000001fbde000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076b8876d0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at jstack.JStack.lockSync(JStack.java:52)
        at jstack.JStack$$Lambda$2/1989780873.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - None

"SYNC lock#1" #11 prio=5 os_prio=0 tid=0x000000001f1e8000 nid=0x172c runnable [0x000000001fadf000]
   java.lang.Thread.State: RUNNABLE
        at jstack.JStack.lockSync(JStack.java:54)
        at jstack.JStack$$Lambda$1/2093631819.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - <0x000000076b8876d0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

可见,锁机制下,锁竞争同步原语都是park:

  • 锁拥有锁线程 SYNC lock#1获取锁并没有调用修饰,而在Locked ownable synchronizers 指明其拥有的可持有同步器(锁),线程动作是runnable,状态时RUNNABLE。
  • 竞争锁失败线程SYNC lock#2的调用修饰是parking to wait for,地址和对象正是拥有者线程持有的锁,线程动作是waiting on condition,状态是WAITING(parking),不同于Monitor未获得锁处于BLOCKED状态。

3) Monitor机制,条件对象上等待

多线程环境下,在monitor机制的条件对象上等待:

public static void main(String[] args) {
    // Monitor机制,条件对象上等待
    Thread monitorSyncCon1 = new Thread(() -> {
        try {
            monitorSyncWait();
        } catch (InterruptedException e) {
            // suppressed
        }
    });
    monitorSyncCon1.setName("SYNC monitor condition#1");
    Thread monitorSyncCon2 = new Thread(() -> {
        try {
            monitorSyncWait();
        } catch (InterruptedException e) {
            // suppressed
        }
    });
    monitorSyncCon2.setName("SYNC monitor condition#2");
    monitorSyncCon1.start();
    monitorSyncCon2.start();
}
"SYNC monitor condition#2" #12 prio=5 os_prio=0 tid=0x000000001ed0b000 nid=0x31f8 runnable [0x000000001f6ff000]
   java.lang.Thread.State: RUNNABLE
        at jstack.JStack.monitorSyncWait(JStack.java:44)
        - locked <0x000000076b87f348> (a java.lang.Class for jstack.JStack)
        at jstack.JStack.lambda$main$1(JStack.java:110)
        at jstack.JStack$$Lambda$2/1989780873.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - None

"SYNC monitor condition#1" #11 prio=5 os_prio=0 tid=0x000000001ed0a000 nid=0x30d0 in Object.wait() [0x000000001f5fe000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b87f348> (a java.lang.Class for jstack.JStack)
        at java.lang.Object.wait(Object.java:502)
        at jstack.JStack.monitorSyncWait(JStack.java:41)
        - locked <0x000000076b87f348> (a java.lang.Class for jstack.JStack)
        at jstack.JStack.lambda$main$0(JStack.java:102)
        at jstack.JStack$$Lambda$1/2093631819.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - None

可见,Monitor机制下,在条件对象上等待:

  • 在条件对象上等待的线程SYNC monitor condition#1, 首先 locked <0x000000076b87f348>获得锁成为拥有者,接着主动调用wait方法,waiting on <0x000000076b87f348>后释放锁,在条件对象上等待,进入Wait Set,线程动作是in Object.wait(),状态是WAITING(在对象监视器上等待)。
  • 此时成为拥有者的线程SYNC monitor condition#2,在线程SYNC monitor condition#1主动放弃锁后 locked <0x000000076b87f348>获得同一个锁,线程动作是runnable,线程状态是RUNNABLE。

4) Lock机制,条件对象上等待

多线程环境下,竞争lock对象的条件对象:

public static void main(String[] args) {
    // Lock机制,条件对象上等待
    Thread lockSyncCon1 = new Thread(() -> {
        try {
            lockSyncAwait();
        } catch (InterruptedException e) {
            // suppressed
        }
    });
    lockSyncCon1.setName("SYNC lock condition#1");
    Thread lockSyncCon2 = new Thread(() -> {
        try {
            lockSyncAwait();
        } catch (InterruptedException e) {
            // suppressed
        }
    });
    lockSyncCon2.setName("SYNC monitor condition#2");
    lockSyncCon1.start();
    lockSyncCon2.start();
}

"SYNC monitor condition#2" #12 prio=5 os_prio=0 tid=0x000000001f1aa800 nid=0x135c waiting on condition [0x000000001fb9e000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076b8894f8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at jstack.JStack.lockSyncAwait(JStack.java:71)
        at jstack.JStack.lambda$main$1(JStack.java:131)
        at jstack.JStack$$Lambda$2/1989780873.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - None

"SYNC lock condition#1" #11 prio=5 os_prio=0 tid=0x000000001f1aa000 nid=0x330c runnable [0x000000001fa9f000]
   java.lang.Thread.State: RUNNABLE
        at jstack.JStack.lockSyncAwait(JStack.java:74)
        at jstack.JStack.lambda$main$0(JStack.java:123)
        at jstack.JStack$$Lambda$1/2093631819.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - <0x000000076b887af0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

可见,在lock条件对象上等待:

  • 一开始获得锁的线程SYNC monitor condition#2,后来放弃锁在锁的条件对象上等待,方法调用栈中并没有提现这一个过程,只是在最后说明,该线程parking to wait for <0x000000076b8894f8>,线程动作是 waiting on condition,状态是WAITING (parking)
  • 由于线程SYNC monitor condition#2主动释放锁而获得锁的线程SYNC lock condition#1,Locked ownable synchronizers指示它获得一个锁,处于RUNNABLE状态。

以上,wait或者await方法加上一个超时时间,WAITING状态变为TIMED_WAITING状态

4) sleep

try {
    Thread.sleep(50_000);
} catch (InterruptedException e) {
    // suppressed
}
"main" #1 prio=5 os_prio=0 tid=0x00000000036e2800 nid=0x2aa8 waiting on condition [0x00000000035df000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at jstack.JStack.main(JStack.java:141)

   Locked ownable synchronizers:
        - None

REFER TO

[1] linux:core文件的产生和调试
[2] Java 中的 Monitor 机制

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

推荐阅读更多精彩内容

  • 一、top(Linux命令) 执行top命令: (查看进程15477的详细情况,下文用到) 系统信息(前五行): ...
    java菜阅读 1,096评论 0 1
  • jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机...
    sherlock_6981阅读 828评论 0 0
  • jstack jstack是java虚拟机自带的一种堆栈跟踪工具。 功能 jstack用于生成java虚拟机当前时...
    Kate_Blog阅读 1,202评论 0 3
  • 我有三个舅舅,一个阿姨。三舅自我出生后,看了我一眼后,第二年就说去江苏打拼,在那个连通讯都难以联络的时代,三舅自从...
    黄秋明阅读 249评论 0 0
  • 抱怨,总是能听到各种抱怨,所谓一升米养恩人,一斗米养仇人。 抱怨唯一的好处就是于周围人处于一种看似融洽,却各有打算...
    律人阅读 246评论 0 0