jvm之内存模型和垃圾收集

1. 五大内存区域

1.1 程序计数器

程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。在发生线程切换的时候用于保存当前线程的虚拟机字节码指令的地址,如果为native方法,那么计数器为空。这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。

1.2 Java栈(虚拟机栈)

每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。栈帧详解

Java虚拟机栈可能出现两种类型的异常:

  • 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
  • 虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。

1.3 本地方法栈

本地方法栈是虚拟机使用到的native方法,可能底层调用的c或者c++。

1.4 堆

堆是java虚拟机管理内存最大的一块内存区域,因为堆存放的对象是线程共享的,所以多线程的时候也需要同步机制。逃逸分析和JIT编译器的发展使得对象的分配并不是全都在堆上,可能会在栈上直接分配。堆可以细分为新生代和老年代。堆除了存放对象以外,在jdk1.8之后还会存放常量池。

1.5 方法区

所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。永久代是对jvm规范中的方法区的一种实现,并且只有 HotSpot 才有 “PermGen space”。在jdk1.8之后完全的移除了永久代,永久代保存的部分数据移到了堆中,取而代之的是Metaspace。
Metaspace(元空间)的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

2. gc

2.1 gc区域

gc区域主要指的是堆空间,额外的还有方法区,metaspace,直接内存等。

2.3 判断对象存活算法:

  • 引用计数算法:优点:实现简单效率高,被广泛使用与如python何游戏脚本语言上。缺点:难以解决循环引用的问题,同时计数器的增加和减少带来额外的开销。
  • 可达性分析算法:从GC Roots出发,能够检索到某个对象时,说明这个对象是可用的。

GC Roots的对象有四种:

  • 虚拟机栈中的java对象
  • 静态对象
  • 方法区中的常量引用对象
  • native方法引用的对象

真正回收一个对象需要两个步骤:1.可达性分析后没有发现引用链。2.查看对象是否有finalize方法。

2.4 垃圾收集算法

  • 标记/清除算法:分为两个阶段标记阶段和清除阶段,标记阶段是从gc root出发标记所有能到达的对象,清除阶段就是会清除标记阶段没有标记到的对象。
    缺点:1.效率问题:两个阶段效率均不高。2.空间问题:清除对象是产生大量的空间碎片,造成需要分配一大块内存是发生内存不足而提前触发新一轮的垃圾回收。

  • 复制算法:将内存分为两块,平时只是用一块内存,当开始垃圾回收时,将使用的一块内存中存活的对象复制到另一块内存上,然后在一次性清除使用过的内存块。
    优点:效率高,没有内存碎片
    缺点:1.浪费一半的内存。2.当存活对象过多时,复制的过程效率会变得很低。

  • 标记/整理算法:在发生垃圾回收时,先标记对象,然后把存活的对象向一端移动,同时清除边界外的对象。同样可以解决空间碎片问题。
    缺点:1.效率问题,同标记/清除算法。

新生代采用复制算法

新生代分为一块Eden空间和From Survivor、To Survivor【保留空间】,HotSpot虚拟机三者默认比例为8:1:1(可配置),优先使用Eden区,若Eden区满(有些情况下会提前触发Minor GC,如System.gc()),则将对象复制到第二块内存区上,如果Eden和第二块内存区都满了就复制他们的存活对象到第三块内存,始终保持一块Survivor内存为空。如果一块Survivor内存不足以放下所有的存活对象则直接提前进入老年代。当某个对象存活过了一定的gc次数(默认15次)也会进入老年代。或者是想要直接分配一个大对象时也是直接进入老年代。

2.5 垃圾收集器

年轻代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:Serial Old、Parallel Old、CMS收集器
特殊收集器:G1收集器(同时收集年轻代和老年代)

2.5.1 Serial 和 Serial Old

单线程收集器,gc时整个过程都会stop the world,优点是在单或双cpu情况下性能优异。
Serial Old也是单线程,采用"标记-整理算法",作为CMS收集器的后备。

2.5.2 Parallel Scavenge和Parallel Old

Parallel Scavenge和ParNew基本一致,不一样的是它关注的是吞吐量,吞吐量 = 运行用户代码时间 / (运行用户代码时间+垃圾收集时间),虚拟机总共运行了100min,其中垃圾收集花费了1min,那吞吐量就是99%。

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效地利用CPU时间,主要适合在后台运算而不需要太多交互的任务。

Parallel Old是Parallel Scavenge的老年版本。它们都是并行收集器,在gc时会暂停用户线程。通过自适应调节策略达到高吞吐量的目的。

2.5.3 ParNew和CMS

ParNew可以认为是serial的升级版本,它支持多线程gc,收集算法、Stop The World、回收策略和Serial一样。默认开启的线程数和cpu数量一致。

cms是一种以获取最短停顿时间为目标的收集器。cms包含4个过程:

  • 初始标记:标记一下GC Roots能直接关联到的对象,速度很快

  • 并发标记:GC Roots Tarcing过程,即可达性分析

  • 重新标记:为了修正因并发标记期间用户程序运作而产生变动的那一部分对象的标记记录,会有些许停顿,时间上一般 初始标记 < 重新标记 < 并发标记

  • 并发清除

    初始标记和重新标记需要stw。并发标记和并发清除耗时最长,但是可以和用户线程一起运行,所以它是并发垃圾回收器。

缺点:

  • CMS收集器对cpu敏感

  • cms无法处理浮动垃圾,可能导致Concurrent Mode Failure(并发模式故障)而触发full GC

  • 由于cms是采用"标记-清除“算法,因此就会存在垃圾碎片的问题

2.5.4 G1

G1(garbage first:尽可能多收垃圾,避免full gc)收集器是当前最为前沿的收集器之一(1.7以后才开始有),同cms一样也是关注降低延迟,是用于替代cms功能更为强大的新型收集器,因为它解决了cms产生空间碎片等一系列缺陷。

G1的特点:

  • g1将整个内存区域分为多个region(区域),从而实现了新生代和老年代的物理内存不隔离,但概念上的区分还是存在的。

  • g1提供三种垃圾回收模式 young gc、mixed gc 和 full gc,不像其它的收集器,根据region(区域)而不是分代,新生代老年代的对象它都能回收。

  • 一组或多组区域中存活对象以增量并行的方式复制到不同区域进行压缩,从而减少堆碎片,目标是尽可能多回收堆空间【垃圾优先】,且尽可能不超出暂停目标以达到低延迟的目的。

    G1的GC过程与CMS不一样的地方在第四步,G1中叫筛选回收。首先对每个region的回收价值和回收成本排序,根据用户希望的停顿时间来制定GC计划。这个阶段可以做到和用户线程并发执行,但是因为只回收部分region,时间是用户控制的,而且停顿用户线程将大大提高回收效率。

你可能感兴趣的:(jvm之内存模型和垃圾收集)