JVM垃圾回收

一、JVM内存区域划分和作用

JVM垃圾回收_第1张图片
JVM内存区域划分和作用

设置JVM各区域的大小:
-Xmx :堆的最大值
-Xms :堆的最小值
-Xmn :堆新生代的大小
-Xss :每个线程的栈大小,决定了函数调用的最大深度
示例:-Xmx512m -Xms512m -Xmn64m -Xss256k 设置堆的最大值和最小值都为512M(可以避免扩容带来的性能损耗),新生代大小为64M,每个栈的大小为256k(过大会造成浪费)

方法区存放内容:

  1. 类的全限定名(类的全路径名)。
  2. 类的直接超类的权全限定名(如果这个类是Object,则它没有超类)。
  3. 类的类型(类或接口)。
  4. 类的访问修饰符,public,abstract,final等。
  5. 类的直接接口全限定名的有序列表。
  6. 常量池(字段,方法信息,静态变量,类型引用(class))等 ,jdk1.7之前字符串常量存在常量池中,之后被移到了堆中
  • 延伸理解字符串的intern方法(面试前必看):字符串的intern方法的理解,常使用String.intern()来优化大量字符串,intern用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中加入该对象,然后返回引用。
  • JVM中的四种引用强度排序:强引用>弱引用>软引用>虚引用
  • JVM在回收对象之前会调用一次对象的finalize()方法,但是仅调用一次,不建议在java中使用finalize方法

二、分代垃圾收集

  1. 新生代,用来存放新出生的对象,新生代又分为 Eden区、ServivorFrom、ServivorTo三个区,由于创建对象频繁,新生代会频繁触发MinorGC(基于复制算法的垃圾收集器)进行垃圾回收。
  • Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存
    不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
  • ServivorTo:保留了一次MinorGC过程中的幸存者。
  • ServivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者
  1. 老年代,主要存放应用程序中生命周期长的内存对象。
  • 老年代的对象比较稳定,所以MajorGC/FullGC(基于标记-整理算法的垃圾收集器)不会频繁执行。在进行MajorGC/FullGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发 。
  • 对象每熬过一次MionorGC,对象的年龄+1,当对象的年龄达到一定年龄(默认是15)的时候就会进入老年代
  • 大对象直接进入老年代,对象的大小边界可通过-XX:PretenureSizeThreshold参数进行配置
JVM垃圾回收_第2张图片
新生代进入老年代的过程
  1. 永久代,JDK7之前分配在方法区中,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域
  • 在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代
  • 元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存,这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制

三、JVM垃圾收集算法

  1. 引用计数法
  • 原理:给对象添加一个引用计数器,每当有一个地方引用它,计数器值就+1,当引用失效时-1,计数器为0时表示对象不再被使用,可被回收。
  • 缺点:无法有效解决对象循环引用的问题,对象A持有对象B的引用,对象B也持有对象A的引用,比如父子关系,引用永远无法归零,对象永远无法回收

因为循环引用的问题,jvm并没有采用这个算法

  1. 可达性分析算法
  • 原理:当一个对象到GCRoot没有引用链相连(从GCRoot到这个对象不可达)时,证明这个对象是不可用的,主流编程语言中都使用该算法来判定对象是否存活。

GCroot对象包括:虚拟机栈中引用的对象、方法区中静态属性和常量引用的对象、本地方法栈中JNI(Native方法)引用的对象

  1. 标记-清除算法
  • 原理:首先标记出所有需要回收的对象,在标记完成后统计回收所有被标记的对象
  • 缺点:标记和清楚的效率都不高;标记清楚后产生大量不连续的内存碎片
  1. 复制算法
  • 原理:将可用内存划分为大小相等的两块,每次只使用其中一块,当一块内存用完了,将还存活的对象复制到另外一块上,然后再把已使用过的内存空间一次清理掉。
  • 优点:实现简单,运行高效,不会产生内存碎片
  • 缺点:将可用内存缩小为原来的一半,代价太高
  • 适用场景:专门用来收集新生代(新生代98%的对象“朝生夕死”),将内存划分为Eden空间和两块较小的Survivor空间,当回收时,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor中,然后清理掉用过的Eden和Survivor空间。HotSpot默认Eden和Survivor空间的比例是8:1,只有10%的内存会被浪费。当Survivir空间不够时,还要依赖老年代的内存进行分配担保
  1. 标记-整理算法
  • 原理:让所有存活的对象向内存空间的一端移动,然后直接清理掉边界意外的内存。
  • 适用场景:仅适用于存活率比较高的老年代的对象收集

四、JVM常用垃圾收集器

JVM垃圾回收_第3张图片
HotSpot中的7种垃圾收集器
  1. Serial和Serial Old收集器

Serial是最古老的垃圾收集器,Serial Old是Serial的老年代版本,二者据用同样的垃圾优缺点

JVM垃圾回收_第4张图片
Serial和Serial Old收集器一起使用的运行示意图
  • 优点:简单高效,没有线程交互的开销
  • 缺点:在进行垃圾收集的时候必须暂停用户的所有线程,导致Stop The World时间过长,用户体验很不好
  • 适用场景:适用于Client模式下(桌面Java应用)的JVM垃圾收集工作(桌面应用大小有限,每次不会收集几十到一两百兆的新生代,停顿时间在几十毫秒以内,完全可以接受),不适合作为服务端的垃圾收集器
  1. ParNew收集器——Serial的多线程版本


    JVM垃圾回收_第5张图片
    ParNew收集器的运行示意图
  • 优点:多线程GC,可使用多CPU核心,效率高,Stop The World时间短,可以与CMS收集器配合工作
  • 缺点:同Serial收集器缺点一样,在垃圾收集的时候要暂停所有用户线程(二者共用了很多代码)
  • 适用场景: 最好在多核心CPU的系统环境下运行,与CMS配合工作可以达到很好的收集效果
  1. Parallel Scavenge和Paralle Old收集器——“吞吐量”优先的收集器
  • Paralle Old是Parallel Scavenge的老年代版本,二者具有相同的优缺点,都关注吞吐量
  • 吞吐量=运行用户代码时间 / (运行用户代码时间+垃圾收集时间)
JVM垃圾回收_第6张图片
Paralle Scavenge和Paralle Old配合使用的运行示意图
  • 优点:关注吞吐量,可以根据用户给的参数自行优化最大停顿时间(参数MaxGCPauseMillis)和吞吐量(参数GCTimeRatio),跟ParNew的重要区别就是有自适应调节策略
  • 缺点:无法与CMS垃圾收集器配合工作
  • 适用场景: 在注重吞吐量以及CPU资源的场合,可以优先考虑Paralle Scavenge和Paralle Old的组合。
  1. CMS收集器——以获得最短停顿时间为目标的收集器

CMS收集器是基于“标记-清除”算法的收集器,收集过程分为四步:

  • 初始标记,只标记一下GCRoot能直接关联到的对象,速度很快,耗时短,不能与用户线程并行
  • 并发标记,进行GC Roots Tracing(GCRoot可达性分析)的过程,耗时最长,可与用户进程并行
  • 重新标记,修正并发标记期间因用户程序继续运行而导致标记变动的那一部分的标记记录,耗时比初始标记长,但远比并发标记的时间短,不能与用户线程并行
  • 并发清除,耗时也很长,但可与用户线程并行
JVM垃圾回收_第7张图片
CMS运行示意图
  • 优点:并发收集、低停顿
  • 缺点:对CPU资源非常敏感(使用多核心CPU来解决);无法处理浮动垃圾(并发清理阶段用户线程产产生的垃圾),当运行期间CMS预留的内存无法满足程序需要,就会启动后备预案(使用Serial Old进行收集,停顿时间更长了);因为采用“标记-清除”算法,产生大量内存碎片,当无法找到足够的空间分配对象时,会触发Full GC(Full GC的时候用户线程无法运行)
  • 适用场景:服务端的应用,jdk1.5-1.6中的主流垃圾收集器
  1. G1收集器——面向服务端应用的垃圾收集器

G1收集器是jdk1.7之后才有的垃圾收集器,是收集器技术最前沿的成果, G1的设计思想包括:

  • 化整为零,将整个java堆划分为多个大小相等的独立区域(Region),新生代和老年代不再物理隔离,只是逻辑隔离
  • 跟踪个各个Region的回收价值(回收后所获得的空间大小和所需时间),在后台维护一个优先列表,每次根据允许的收集时间优先收集回收价值最大的Region
  • 使用Remembered Set来避免全堆扫描,G1中每个Region都有一个对应的Remembered Set,记录了该Region中的对象对其他Region中对象的引用信息(希望我理解的没问题),当进行内存回收时,在GC根节点枚举范围加入RememberedSet即可保证不对全堆扫描也不会有遗漏

G1垃圾收集过程分为四步:

  • 初始标记,只是标记一下GCRoot能直接关联到的对象,速度快,耗时短,不能与用户线程并行
  • 并发标记,进行GC Roots Tracing(GCRoot可达性分析)的过程,耗时最长,可与用户进程并行
  • 最终标记,修正在并发期间因用户程序继续运行而导致标记产生变化的那一部分标记记录,不能跟用户线程并行
  • 筛选回收,对各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间制定回收计划。
JVM垃圾回收_第8张图片
G1收集器运行示意图
  • 优点:能充分利用CPU多核优势(可与用户线程并行),降低Stop The World时间;分代收集,不需要跟其他收集器整合就可独自管理真个GC堆;空间整合,整体基于“标记-整理”算法,局部(两个Region之间)使用“复制”算法实现,不会产生内存碎片;可预测的停顿,可以根据用户指定的停顿时间做出收集计划。
  • 缺点:面试不久,还在逐步完善
  • 适用场景:服务端应用

五、JVM垃圾收集器常用参数

参数的使用方法:

  • -XX:+
  • -XX:-
  • -XX:

示例:-XX:+PrintGC -XX:+PrintGCDetails 打开打印GC的信息和详细信息

JVM垃圾回收_第9张图片
垃圾回收相关参数配置

你可能感兴趣的:(JVM垃圾回收)