JVM垃圾回收与性能调优

版权声明:本文为博主原创文章,未经允许不得转载。 https://blog.csdn.net/qq_31142553/article/details/81295331

一、JVM内存结构

图片来源于网络

1、方法区

 方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,描述为堆的一个逻辑部分,称为非堆(HotSpot中也称为永久代)。该区域包含运行时常量池Java8之后改为元空间(MetaSpace),直接分配物理内存。对这块内存的GC条件很苛刻,基本认为不会进行。

2、堆

堆是被所有线程共享的一块内存区域,是JVM管理的内存中最大的一块,在虚拟机启动时创建。几乎所有的对象实例以及数组都要在堆分配内存。垃圾收集器管理的主要区域。

3、虚拟机栈

也称为线程本地栈,每个线程拥有自己独立的一份,每调用一个方法都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。

4、本地方法栈

为虚拟机使用到的Native方法服务。与虚拟机栈发挥的作用相似。有的虚拟机(如HotSpot)把本地方法栈和虚拟机栈合二为一了。

5、程序计数器

是当前线程所执行的字节码的行号指示器

二、什么是垃圾

一般是指堆内存(方法区很少)中不会再使用到的对象,通俗讲就是没有引用指向它的对象。

引用的概念分类

  • 强引用:普遍存在。只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
  • 软引用:还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
  • 弱引用:非必需对象,比软引用略弱。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  • 虚引用:也称幽灵引用或者幻影引用,最弱的一种引用关系。

1、引用计数算法

算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器值为0的对象就是不可能再被使用的。

优点:实现简单,效率也高。

缺点:很难解决对象之间相互循环引用的问题

2、可达性分析算法 

思路:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GCRoots到这个对象不可达)时,则证明此对象是不可用的。

图片来源于网络

如图,对象object4、object5、object6虽然互相有关联,但是它们到GCRoots是不可达的,所以它们将会被判定为可回收的对象。

在Java语言中可作为GC Roots的对象包括以下几种: 

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

三、垃圾收集算法

1、Mark-Sweep标记清除

如果所示:首先标记出所有需要回收的对象,在标记完成后统一回收被标记的对象。 

图片来源于网络

缺点:

  • 效率问题,标记和清除两个过程的效率都不高。
  • 空间问题,标记清除之后会产生大量不连续的内存碎片。当有大对象进来并且没有足够空间时,需要压缩空间。

 2、Copying复制 

如图所示:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存使用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂问题,只要移动堆顶指针按顺序分配内存即可。 

图片来源于网络

优点:

  • 实现简单
  • 运行高效

缺点:

  • 内存浪费

3、Mark-Compact标记整理

如图所示: 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

图片来源于网络

四、垃圾收集器

1、CMS(Concurrent Mark Sweep)收集器

以获取最短回收停顿时间为目标。

优点:并发收集、低停顿

缺点:对CPU资源敏感、无法处理浮动垃圾、产生大量空间碎片

2、G1(Garbag-First)收集器

优点:

  • 并发与并行
  • 分代收集
  • 空间整合
  • 可预测的停顿

更多请查阅https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/

3、Java平台,标准版HotSpot虚拟机垃圾收集调优指南

<<<<<<<<<<摘自Oracle官网>>>>>>>>>>

Java HotSpot VM包括三种不同类型的收集器,每种收集器具有不同的性能特征。

  • 串行收集器使用单个线程来执行所有垃圾收集工作,这使得它相对有效,因为线程之间没有通信开销。它最适合单处理器机器,因为它无法利用多处理器硬件,尽管它对于具有小数据集(最大约100 MB)的应用程序的多处理器非常有用。默认情况下,在某些硬件和操作系统配置上选择串行收集器,或者可以使用该选项显式启用串行收集器-XX:+UseSerialGC。
  • 并行收集器(也称为吞吐量收集器)并行执行次要收集,这可以显着减少垃圾收集开销。它适用于在多处理器或多线程硬件上运行的具有中型到大型数据集的应用程序。默认情况下,在某些硬件和操作系统配置上选择并行收集器,或者可以使用该选项显式启用并行收集器-XX:+UseParallelGC。

并行压缩是一种使并行收集器能够并行执行主要集合的功能。如果没有并行压缩,主要集合将使用单个线程执行,这可能会显着限制可伸缩性。如果-XX:+UseParallelGC已指定选项,则默认启用并行压缩。关闭它的选项是-XX:-UseParallelOldGC。

  • 大多数并发收集器同时执行大部分工作(例如,在应用程序仍在运行时)以防止垃圾收集暂停。它适用于具有中型到大型数据集的应用程序,其中响应时间比总吞吐量更重要,因为用于最小化暂停的技术会降低应用程序性能。Java HotSpot VM提供两个主要并发收集器之间的选择; 看到最常见的收藏家。使用该选项-XX:+UseConcMarkSweepGC可启用CMS收集器或-XX:+UseG1GC启用G1收集器。

选择收集器

除非您的应用程序具有相当严格的暂停时间要求,否则首先运行您的应用程序并允许VM选择收集器。如有必要,请调整堆大小以提高性能。如果性能仍不符合您的目标,请使用以下指南作为选择收集器的起点。

  • 如果应用程序具有较小的数据集(最大约100 MB),那么使用选项选择串行收集器-XX:+UseSerialGC。
  • 如果应用程序将在单个处理器上运行且没有暂停时间要求,则让VM选择收集器,或选择带有该选项的串行收集器-XX:+UseSerialGC。
  • 如果(a)峰值应用程序性能是第一优先级并且(b)没有暂停时间要求或1秒或更长的暂停是可接受的,则让VM选择收集器,或选择并行收集器-XX:+UseParallelGC。
  • 如果响应时间比总吞吐量更重要,并且垃圾收集暂停必须保持短于大约1秒,则使用-XX:+UseConcMarkSweepGC或选择并发收集器-XX:+UseG1GC。

这些指南仅提供了选择收集器的起点,因为性能取决于堆的大小,应用程序维护的实时数据量以及可用处理器的数量和速度。暂停时间对这些因素特别敏感,因此前面提到的1秒阈值只是近似值:并行收集器在许多数据大小和硬件组合上将经历超过1秒的暂停时间; 相反,并发收集器可能无法在某些组合上保持短于1秒的暂停。

如果推荐的收集器未达到所需性能,请首先尝试调整堆和生成大小以满足所需目标。如果性能仍然不足,那么尝试使用不同的收集器:使用并发收集器来减少暂停时间,并使用并行收集器来提高多处理器硬件的总吞吐量。

4、内存分配与回收策略

当前虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法:根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。

图片来源于网络
  • 对象优先在Eden分配

  • 大对象直接进入老年代(-XX:PretenureSizeThreshold参数)

  • 长期存活的对象将进入老年代(-XX:MaxTenuringThreshold参数)

  • 动态对象年龄判定

  • 空间分配担保

5、Java对象的分配

栈上分配

  • 线程私有小对象
  • 无逃逸(方法逃逸、线程逃逸):逃逸分析-XX:+DoEscapeAnalysis
  • 支持标量替换:-XX:+EliminateAllocations
  • 无需调整

线程本地分配:TLAB(Thread Local Allocation Buffer):-XX:+UseTLAB

  • 占用eden,默认1%
  • 多线程的时候不用竞争eden就可以申请空间,提高效率
  • 小对象
  • 无需调整

老年代

  • 大对象

Eden

五、JVM常用参数设置

-:标准参数,所有JVM都应该支持

-X:非标,每个JVM实现都不同

-XX:不稳定参数,下一个版本可能会取消

图片来源于网络

常用设置套路

1、-Xms和-Xmx设置相近或相等:避免Java堆内存自动扩容时带来的性能消耗 。

2、-Xss:设置小,执行的线程可以多一些。设置大,方法的调用可以深一些。

六、案例分析

1、Java对象的分配

当关闭逃逸分析,关闭标量替换,关闭线程本地程序时

改下JVM参数

 

 

2、用Runtime类“大致”计算内存情况

3、内存溢出分析

 

 

可以看到,byte[]占了大部分内存。

4、线程栈大小测试 

 

七、常用工具

1、JConsole:Java监视与管理控制台

2、VisualVM:多合一故障处理工具

无监控,不调优!!!

猜你喜欢

转载自blog.csdn.net/qq_31142553/article/details/81295331