JVM内存模型和垃圾回收策略

内存模型结构:
JVM内存模型和垃圾回收策略

  • JVM内存模型主要有图中的5块组成:方法区、堆(这两块是线程共享的)、栈、本地方法栈、程序计数器(这三块是线程私有的)
    • 方法区:是线程共享的区域,用于存放类的模版信息、常量、静态变量、字段、方法等。具体实现:jdk1.7和之前都是永久代(Perm区),当永久代满了就会触发FullGC,影响整个系统。JDK1.8时取掉了永久代,改为了元空间(Metaspace),元空间数据不再存放在JVM中而是存放到本地内存中。GC效率提高了。空间不足抛出OutOfMemoryError。
    • 堆:也是线程共享的区域,存放java类的实例。具体可分为新生代和老年代,新生代又包括Eden区、survivor0、survivor1区,是GC主要管理的区域。空间不足抛出OutOfMemoryError。
      • 为什么有两个survivor区?

        个人认为是复制算法决定的,需要一个空的内存区来存放存活的对象,这样才能清理数据碎片。

    • 栈:是线程私有区域。包括了局部变量区、操作数栈、动态链接、方法出口信息。栈中可以包含多个栈帧,即一个线程内的调用多个方法开辟多个栈空间。空间不足抛出*Error。
    • 本地方法栈:存放本地的C或C++方法。比如public static native void sleep(long millis) throws InterruptedException;这就是调用C编写的方法。
    • 程序计数器:因为CPU运行时会切换,需要记录每个线程执行的指令地址。
  • 垃圾回收策略:*
    JVM内存模型和垃圾回收策略
  • 垃圾对象判定标准:
    • 引用计数法:记录对象被引用的次数,引用一次加一,引用失效就减一,引用计数为0即为失效对象。目前新版本JDK不再使用,因为无法判断循环引用的对象是否过期。
    • 根搜索法:构建一个GCRoots为根的树,从根开始搜索,不在树中的即为失效对象。
  • 垃圾回收算法:
    • 标记清除。对活跃对象标注,清除没有标注的对象。清除后空间内对象不再连续,会造成数据内存碎片的问题。
    • 复制。对活跃对象标注,复制到新的空间,再清空老空间的数据。虽然解决了数据碎片,但是清理时会消耗内存空间。
    • 标记整理。是对标记清除的优化,标记清除完数据,在对空间内的存活对象整理向前移动,使之连续。解决内存碎片的问题。
    • 分代回收。将JVM堆分为新生代和老年代,不同的代使用不同的回收算法。新生代因为失效的比较多,所以使用复制算法。老年代因为存活率比较高,而且占用空间大也没有额外的空间分配,所以采用标记清除和标记整理算法。

JVM垃圾回收器具体实现:
JVM内存模型和垃圾回收策略
在生产环境一般在不同的代使用不同的垃圾回收器,图上显示不同回收器针对的内存代

  • 并行垃圾回收器(Parallel Garbage Collector)

    • 年轻代垃圾回收器。使用复制算法。
  • 并发标记扫描垃圾回收器(CMS Garbage Collector)

    • 老年代收集器。使用标记清除算法,GC可以和用户线程并发执行,牺牲了部分性能,减少停顿时间。缺点:GC时加重CPU压力影响性能,造成严重的内存碎片化。
  • G1垃圾回收器(Garbage First Collector)

    • 使用标记整理+复制。JDK7正式发布,在JDK9中成为默认的垃圾回收器。手动设置:-XX:+UseG1GC。最主要就会有碎片整理的功能。其他的有时间深入研究下2333…

FullGC

FullGC发生的条件:

  • 年老代(Tenured)被写满;

  • 持久代(Perm)被写满;

  • System.gc()被显示调用;

  • 上一次GC之后Heap的各域分配策略动态变化;