Java 性能优化系列之3.1[JVM调优]

Java 虚拟机内存模型


JVM 虚拟机将其内存数据分为程序计数器、虚拟机栈、本地方法栈、Java 堆和方法区等部分。
程序计数器用于存放下一条运行的指令;虚拟机栈和本地方法栈用于存放函数调用栈信息; Java堆用于存放Java 程序运行时所需的对象等数据;方法区用于存放程序的类元数据信息。

  1. 程序计数器- Program Counter Register
    是一块很小内存空间。 由于Java 是支持线程的语言, 当线程数量超过CPU 数量时,线程之间根据时间片轮询抢夺CPU 资源。对于单核CPU 而言, 每一时刻,只能有一个线程在运行,而其他线程必须被切换出去。为此,每一个线程都必须用一个独立的程序计数器, 用于记录下一条要运行的指令。各个线程之间的计数器互不影响,独立工作;是一块线程私有的内存空间。
    如果当前线程正在执行一个Java 方法,则程序计数器记录正在执行的Java 字节码地址,如果当前线程正在执行一个Native 方法, 则程序计数器为空。

  2. Java 虚拟机栈
    Java 虚拟机栈也是线程私有的内存空间, 它和Java 线程在同一时间创建, 它保存方法的局部变量、部分结果,并参与方法的调用和返回。
    Java虚拟机规范允许Java 栈的大小是动态的或是固定的。在Java 虚拟机规范中,定义了两种异常与栈空间有关: StackOverflowError 和OutOfMemoryError.
    如果线程在计算过程中, 请求的栈深度大于最大可用的栈深度,则抛出StackOverflowError;
    如果Java 栈可以动态扩展,而在扩展栈的过程中, 没有足够的内存空间来支持栈的扩展,则抛出OutOfMemoryError.
    在HotSpot 虚拟机中, 可以使用-Xss参数来设置栈的大小。
    看一段代码:
    TestStack.java

package com.oscar999.performance.JVMTune;  
  
import org.junit.Test;  
  
public class TestStack {  
  
    private int count = 0;  
    public void recursion(){  
        count ++;  
        recursion();  
    }  
      
    @Test  
    public void testStatck(){  
        try{  
            recursion();  
        }catch(Throwable e){  
            System.out.println("deep of stack is "+count);  
            e.printStackTrace();  
        }  
    }  
  
}  

运行结果


如果调整stack space 的大小, 在eclipse中, 如下设置

运行结果:

深度增加了不少。
虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据。在栈帧中,存放了方法的局部变量表、操作数栈、动态连接方法和返回地址等信息。
每一个方法的调用都伴随着栈帧的入栈操作, 相应地,方法的返回则表示栈帧的出栈操作。如果方法调用时,方法的参数和局部变量相对较多,那么栈帧中的局部变量就会比较大,栈帧会膨胀以满足方法调用所需传递的信息, 因此, 单个方法调用所需的栈空间大小也会比较多。
栈帧的结果如下:

使用jclasslib 工具可以查看class文件中每个方法所分配的最大局部变量表的容量。
可以到 http://sourceforge.NET/projects/jclasslib/files/jclasslib 中下载
可以使用jclasslib 查看一下以下类的class文件所需要的字

package com.oscar999.performance.JVMTune;  
  
public class TestWordReuse {  
    public void test1() {  
        {  
            long a = 0;  
        }  
        long b = 0;  
    }  
      
    public void test2(){  
        long a = 0;  
        long b = 0;  
    }  
  
}  

结果一个是 3, 一个是5.
基于此, 看一下系统GC 回收的例子:

package com.oscar999.performance.JVMTune;  
  
public class SystemGC {  
    /** 
     * GC 无法回收b, 因为b 还在局部变量中 
     */  
    public static void test1() {  
        {  
            byte[] b = new byte[6 * 1024 * 1024];  
        }  
        System.gc();  
        System.out.println("first explict gc over");  
    }  
    /** 
     * GC 无法回收, 因为 赋值为null将销毁局部变量表中的数据 
     */  
    public static void test2() {  
        {  
            byte[] b = new byte[6 * 1024 * 1024];  
            b=null;  
        }  
        System.gc();  
        System.out.println("first explict gc over");  
    }  
    /** 
     * GC 可以回收, 因为变量a 复用了变量b 的字,GC根无法找到b 
     */  
    public static void test3() {  
        {  
            byte[] b = new byte[6 * 1024 * 1024];  
              
        }  
        int a=0;  
        System.gc();  
        System.out.println("first explict gc over");  
    }  
    /** 
     * GC 无法回收, 因为变量a 复用了变量c 的字,b 仍然存在 
     */  
    public static void test4() {  
        {  
            int c = 0;  
            byte[] b = new byte[6 * 1024 * 1024];             
        }  
        int a=0;  
        System.gc();  
        System.out.println("first explict gc over");  
    }  
    /** 
     * GC 可以回收, 因为变量a 复用了变量c 的字,变量d 复用了变量b 的字 
     */  
    public static void test5() {  
        {  
            int c = 0;  
            byte[] b = new byte[6 * 1024 * 1024];             
        }  
        int a=0;  
        int d=0;  
        System.gc();  
        System.out.println("first explict gc over");  
    }  
      
    /** 
     *  
     * 总是可以回收b , 因为上层函数的栈帧已经销毁 
     */  
    public static void main(String args[]){  
        test1();  
        System.gc();  
        System.out.println("second explict gc over");  
    }  
}  
  1. 本地方法栈
    本地方法栈和Java 虚拟机栈的功能很相似, Java 虚拟机栈用于管理Java函数的调用,而本地方法栈用于管理本地方法的调用。 本地方法并不是用Java实现的,而是用C实现的。 在SUN 的Hot Spot虚拟机中,不区分本地方法栈和虚拟机栈。因此,和虚拟机栈一样,它也会抛出StackOverflowError 和OutOfMemoryError.

  2. Java 堆
    Java运行时内存中最为重要的部分, 几乎所有的对象和数组都是在堆中分配空间的。Java堆分为新生代和老年代两个部分。新生代用于存放刚刚产生的对象和年轻的对象,如果对象一直没有被回收,生存得足够长,老年对象就会被移入老年代。
    新生代又分为:eden(伊甸园)、survivor space0(from space), survivor space1(to space)



    看一下以下代码的执行情况:

package com.oscar999.performance.JVMTune;  
  
public class TestHeapGC {  
  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        byte[] b1 = new byte[1024*1204/2];  
        byte[] b2 = new byte[1024*1204*8];  
        b2 = null;  
        b2 = new byte[1204*1204*8];  
        //System.gc();  
    }  
  
}  

使用命令行运行:
java -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -Xms40M -Xmx40M -Xmn20M com.oscar999.performance.JVMTune.TestHeapGC
运行结果


把上面mark 的 System.GC 打开,再运行一下


可以看到,在Full GC之后, 新生代空间被清空,未被回收的对象全部被移入老生代。

  1. 方法区与堆空间类似,它也是被JVM中所有的线程共享的。方法去主要保存的信息是类的元数据。
    方法区中作为重要的是类的类型信息、常量池、域信息、方法信息。
    在Hot Spot 虚拟机中, 方法区也被称为永久区,是一块独立于Java堆的内存空间。虽然叫做永久区,但是在永久区的对象,同样也是可以被 GC回收的。
    对永久区GC的回收,通常从两个方面进行分析: 1. GC 对永久区常量池的回收 2. 永久区对类元数据的回收。
    看如下代码:
package com.oscar999.performance.JVMTune;  
  
  
public class TestPermGenGC {  
  
    public void permGenGC(){  
        for(int i=0;i<Integer.MAX_VALUE;i++)  
        {  
            String t = String.valueOf(i).intern();  
        }  
    }  
      
    public static void main(String[] args){  
        TestPermGenGC test = new TestPermGenGC();  
        test.permGenGC();  
    }  
}  

使用如下命令行运行:
java -XX:PermSize=2M -XX:MaxPermSize=4M -XX:+PrintGCDetails com.oscar999.performance.JVMTune.TestPermGenGC
会发现一直打印如下日志:


也就是说, 每当常量池饱和时,Full GC总能顺利回收常量池数据,确保程序稳定持续运行。
再来看看类元数据的回收情况,
这里要动态生成类的实例,要用到 javassist
可以到如下地址下载:
http://www.java2s.com/Code/Jar/j/Downloadjavassistjar.htm
JavaBeanObject.java

package com.oscar999.performance.JVMTune;  
  
public class JavaBeanObject {  
          
    private String name = "java";  
      
    public String getName() {  
        return name;  
    }  
      
    public void setName(String name) {  
        this.name = name;  
    }  
}  

TestOneClassLoad.java

package com.oscar999.performance.JVMTune;  
  
import javassist.CannotCompileException;  
import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.NotFoundException;  
  
public class TestOneClassLoad {  
  
    public void testOneClassLoad() throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException {  
        for (int i = 0; i < Integer.MAX_VALUE; i++) {  
            CtClass c = ClassPool.getDefault().makeClass("Geym"+i);  
            c.setSuperclass(ClassPool.getDefault().get("com.oscar999.performance.JVMTune.JavaBeanObject"));  
            Class clz = c.toClass();  
            JavaBeanObject v = (JavaBeanObject)clz.newInstance();  
        }  
    }  
  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        TestOneClassLoad test = new TestOneClassLoad();  
        try {  
            test.testOneClassLoad();  
        } catch (Exception e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }   
    }  
  
}  

使用如下命名运行:
java -classpath .;../lib/javassist.jar -XX:PermSize=2M -XX:MaxPermSize=4M -XX:+PrintGCDetails com.oscar999.performance.JVMTune.TestOneClassLoad


持久代溢出, Full GC在这种情况下不能回收类的元数据。
事实上,如果虚拟机确认该类的所有实例已经被回收,并且加载该类的ClassLoader已经被回收, GC就有可能回收该类型。
如果新增Class MyClassLoader

package com.oscar999.performance.JVMTune;  
  
public class MyClassLoader extends ClassLoader {  
  
}  

TestOneClassLoad 修改成:

package com.oscar999.performance.JVMTune;  
  
import javassist.CannotCompileException;  
import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.NotFoundException;  
  
public class TestOneClassLoad {  
  
    static MyClassLoader c1 = new MyClassLoader();  
    public void testOneClassLoad() throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException {  
        for (int i = 0; i < Integer.MAX_VALUE; i++) {  
            CtClass c = ClassPool.getDefault().makeClass("Geym"+i);  
            c.setSuperclass(ClassPool.getDefault().get("com.oscar999.performance.JVMTune.JavaBeanObject"));  
            Class clz = c.toClass(c1,null);  
            JavaBeanObject v = (JavaBeanObject)clz.newInstance();  
            if(i%10==0)  
                c1 = new MyClassLoader();  
              
        }  
    }  
  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        TestOneClassLoad test = new TestOneClassLoad();  
        try {  
            test.testOneClassLoad();  
        } catch (Exception e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }   
    }  
  
}  

就不会出现上面的问题了。

JVM内存分配参数

  1. 设置最大堆内存
    使用 -Xmx 参数指定。 最大堆指的是新生代和老生代的大小之和的最大值, 它是Java 应用程序的堆上限。
    看例子:
package com.oscar999.performance.JVMTune;  
  
import java.util.Vector;  
  
public class TestXmx {  
  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        Vector v = new Vector();  
        for(int i=1;i<=10;i++){  
            byte[] b = new byte[1024*1024];  
            v.add(b);  
            System.out.println(i+"M is allocated");  
        }  
        System.out.println("Max memory:"+Runtime.getRuntime().maxMemory()/1024/1024+"M");  
          
    }  
  
}  

使用如下命令行:
java -Xmx5M com.oscar999.performance.JVMTune.TestXmx


  1. 设置最小堆内存
    使用JVM参数 -Xms 可以用于设置系统的最小堆空间。 也就是JVM启动时,所占据的操作系统内存大小。
    Java 应用程序在运行时,首先会被分配-Xms指定的内存大小, 并尽可能尝试在这个空间内运行程序。当-Xms 指定的内存大小确实无法满足应用程序时,JVM 才会向操作系统申请更多的内存,直到内存大小达到-Xmx指定的最大内存为止。若超过-Xmx的值,则抛出OutOfMemoryError异常。
    如果-Xms的数值较小,那么JVM为了保证系统尽可能地在指定内存范围内运行,就会更加频繁地进行GC操作,以释放失效的内存空间,从而, 会增加Minor GC 和 Full GC的次数, 对系统性能产生一定的影响。
package com.oscar999.performance.JVMTune;  
  
import java.util.Vector;  
  
public class TestXms {  
    public static void main(String args[]) {  
        Vector v = new Vector();  
        for (int i = 1; i <= 10; i++) {  
            byte[] b = new byte[1024 * 1024];  
            v.add(b);  
            if (v.size() == 3)  
                v.clear();  
        }  
    }  
}  

运行命令:
java -Xmx11M -Xms4M -verbose:gc com.oscar999.performance.JVMTune.TestXms
结果:


  1. 设置新生代
    参数-Xmn 用于设置新生代的大小。 设置一个较大的新生代会减小老生代的大小,这个参数对系统性能以及GC行为有很大的影响。新生代的大小一般设置为整个堆空间的1/4 到1/3 左右。
    在Hot Spot 虚拟机中, -XX:NewSize用于设置新生代的初始大小, -XX:MaxNewSize 用于设置新生代的最大值。但通常情况下,只设置-Xmn已经可以满足绝大部分应用的需要。 设置-Xmn 的效果等同与设置了相同的-XX:NewSize 和 -XX:MaxNewSize.

  2. 设置持久代
    持久代(方法区)不属于堆的一部分。在Hot Spot 虚拟机中, 使用-XX:MaxPermSize 可以设置持久代的最大值,使用-XX:PermSize可以设置持久代的初始大小。
    持久代的大小直接决定了系统可以支持多个类定义和多少常量。对于使用CGLIB或者Javassist 等动态字节码生成工具的应用程序而言,设置合理的持久代大小有助于维持系统稳定。

  3. 设置线程栈
    线程栈是线程的一块私有空间。
    在JVM中, 可以使用 -Xss参数设置线程栈的大小。



    栈大小与线程数的关系。

  4. 堆的比例分配
    -XX:SurvivorRatio 是用来设置新生代中, eden 空间和s0空间的比例关系。 s0和s1空间又分别被称为from空间和to空间。

参数总结:


垃圾收集

垃圾收集算法与思想

  1. 引用计数器(Reference Counting)
    对于一个对象A, 只要有任何一个对象引用了A, 则A的引用计数器就加1 , 当引用失效时, 引用计数器就减1. 只要对象A的引用计数器的值为0, 则对象A就不可能再被使用。
    但由于垃圾对象间相互引用,从而使垃圾回收器无法识别,引起内存泄漏



    因此,在Java语言中,单纯的使用引用计数器算法实现垃圾回收是不可行的。

  2. 标记-清除算法(Mark-Sweep)
    将垃圾回收分为两个阶段:标记阶段和清除阶段。
    一个可行的实现是, 在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。标记-清除算法可能产生的最大问题就是空间碎片。


  3. 复制算法(Copying)
    与标记-清除算法相比,复制算法是一种相当高效的回收方法。它的核心思想是:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾收集。
    但是, 复制算法的代价缺点是将系统内存折半,因此,单纯的复制算法也很难让人接受。


  4. 标记-压缩算法(Mark-Compact)
    标记-压缩算法是一种老年代的回收算法,它在标记-清除算法的基础上做一些优化, 和标记-清除算法一样,标记-压缩算法也首先从根节点开始,对所有可达对象做一次标记。



    5.增量算法(Incremental Collecting)
    如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程,如此反复,知道垃圾收集完成。

  5. 分代(Generational Collecting)
    将内存区间根据对象的特点分成几块,根据每块内存区间的特点,使用不同的回收算法,以提高垃圾回收的效率。


垃圾收集器的分类


评价GC策略的指标
吞吐量: 在应用程序的生命周期内, 应用程序所花费的时间和系统总运行时间的比值。
垃圾回收器负载: 垃圾回收器负载指垃圾回收器耗时与系统运行总时间的比值
停顿时间: 垃圾回收器正在运行时,应用程序的暂停时间。
垃圾回收频率: 指垃圾回收器多长时间会运行一次。
反应时间: 指当一个对象称为垃圾后, 多长时间内,它所占据的内存空间会被释放。
堆分配: 不同的垃圾回收器对内存的分配方式可能是不同的。一个良好的垃圾收集器应该有一个合理的堆内存区间划分。

常用调优案例和方法

  1. 将新对象预留在新生代
    Full GC的成本要远远高于Minor GC, 因此尽可能将对象分配在新生代是一项明智的做法。虽然在大部分情况下,JVM会尝试在eden 区分配对象,但是由于空间紧张等问题,很可能不得不将部分年轻对象提前向老年代压缩。因此,在JVM参数调优中,可以为应用程序分配一个合理的新生代空间,以最大限度避免新对象直接进入老年代的情况。
    看以下例子,
package com.oscar999.performance.JVMTune;  
  
public class PutInEden {  
  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        byte[] b1,b2,b3,b4;  
        b1 = new byte[1024*1204];  
        b2 = new byte[1024*1204];  
        b3 = new byte[1024*1204];  
        b4 = new byte[1024*1204];  
    }  
  
}  

当新生代大小设置为 1 M时, 也就是如下命令运行:
java -XX:+PrintGCDetails -Xmx20M -Xms20M -Xmn1M com.oscar999.performance.JVMTune.PutInEden


当新生代设置为 6 M时,
java -XX:+PrintGCDetails -Xmx20M -Xms20M -Xmn6M com.oscar999.performance.JVMTune.PutInEden


类似地, 使用-XX:NewRatio等参数也可以指定新生代大小。 通过设置一个较大的新生代预留新对象,设置合理的survivor 区并且提高survivor区的使用率,可以将年轻对象保留在新生代。一般来说,当 survivor 区的空间不够,或者占用量达到 50%时,就会将对象进入老年代。

  1. 大对象进入老年代
    大对象出现在新生代很可能扰乱新生代GC, 并破坏新生代原有的结构对象。因为尝试在新生代分配大对象,很可能导致空间不足,为了有足够的空间容纳大对象, JVM不得不将新生代中的年轻对象挪到老年代。因为大对象占用空间多,所以, 可能需要移动大量小的年轻对象进入老年代,这对于GC老说相当不利。
    可以将大对象直接分配到老年代,保持新生代对象结构的完整性,以提高GC的效率。
    软件开发过程中, 应该尽可能避免使用短命的大对象。
    可以使用参数-XX:PretenureSizeThreshold 设置大对象直接进入老年代的阈值。当对象的大小超过这个值时,将直接在老年代分配。
    注意:这个参数只对串行收集器和新生代并行收集器有效,并行收集器不识别这个参数。
    看例子:
package com.oscar999.performance.JVMTune;  
  
public class BigObj2Old {  
  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        byte[] b1;  
        b1 = new byte[1024*1024];  
    }  
  
}  

使用如下命令:
java -XX:+PrintGCDetails -Xmx20M -Xms20M com.oscar999.performance.JVMTune.BigObj2Old



可以看到, 该对象被分配到了新生代,并几乎占满了整个新生代。
如果使用如下命令:
java -XX:+PrintGCDetails -Xmx20M -Xms20M -XX:PretenureSizeThreshold=1000000 com.oscar999.performance.JVMTune.BigObj2Old



可以看到, 1MB的字节数组已经分配在老年代。
  1. 设置对象进入老年代的年龄
    一般情况下, 年轻对象存放在新生代,年老对象存放在老年代。 为了做到这点,虚拟机为每个对象都维护一个年龄。
    如果对象在eden 区,经过一次GC后还存活,则被移动到survivior区中,对象年龄加1, 以后对象每经过一次GC依然存活的,则年龄再加1. 当对象年龄达到阈值时,就移入老年代,成为老年对象。
    这个阈值的最大值通过参数: -XX:MaxTenuringThreshold来设置,它的默认值是15.

  2. 稳定与震荡的堆大小
    一般来说,稳定的堆大小是对垃圾回收有利的。获得一个稳定的堆大小的方法就是使 -Xms 和-Xmx的大小一致,即最大堆和最小堆一样。如果这样设置,系统在运行时,堆大小是恒定的,稳定的堆空间可以减少GC的次数。因此,很多服务端应用都会将最大堆和最小堆设置为相同的数值。
    稳定的堆大小虽然可以减少GC次数, 但同时也增加了每次GC的时间。让堆大小在一个区间中震荡,在系统不惜要使用大内存时,压缩堆空间,使GC应对一个较小的堆,可以加快单次GC的速度。基于此, JVM还提供了两个参数用于压缩和扩展堆空间:
    -XX:MinHeapFreeRatio: 设置堆空间最小空闲比例。默认是40. 当堆空间的空闲内存小于这个数值时, JVM便会扩展堆空间。
    -XX:MaxheapFreeRatio: 设置堆空间最大空闲比例。 默认是70.当堆空间的空闲内存大于这个数值时,便会压缩堆空间, 得到一个较小的堆。
    当-Xms和-Xmx相等时, -XX:MinHeapFreeRation和-XX:MaxHeapFreeRatio 这两个参数是无效的。

  3. 吞吐量优先案例
    尽可能减少系统的执行垃圾回收的总时间。
    看如下设置:


  4. 使用大页案例
    在Solaris 系统中, JVM 可以支持大页的使用, 使用大的内存分页可以增强CPU的内存寻址能力,从而提升系统的性能

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

推荐阅读更多精彩内容

  • 1.一些概念 1.1.数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始...
    落落落落大大方方阅读 4,502评论 4 86
  • 参数设置 在Java虚拟机的参数中,有3种表示方法用“ps -ef |grep "java"命令,可以得到当前Ja...
    九问阅读 9,080评论 2 52
  • Java 虚拟机有自己完善的硬件架构, 如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 屏蔽了与具体操作系...
    尹小凯阅读 1,669评论 0 10
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,927评论 2 31
  • 一、基础概念 UML(unified modeling language)即统一建模语言或标准建模语言,它是一个支...
    IvanHung阅读 252评论 0 0