jvm终极篇

目标

  • 能够说出java体系、知道什么是java虚拟机、运行原理
  • 能够知道字节码的结构,包含哪些信息,怎么分布的
  • 能够掌握jvm运行时数据区都是怎么划分的,各个部分会有什么异常
  • 能够说出类的加载过程,每个阶段都做了什么事情
  • 能够说明类的创建过程
  • 能够说出垃圾回收策略、机制和原理 、垃圾回收三种算法的优劣
  • 能够说出G1垃圾回收ygc和fullgc以及与JDK8垃圾回收器区别和共同点

1. 概述

java体系

jvm: Java虚拟机,它是运行在操作系统之上的,它与硬件没有直接的交互

jre: Java运行环境,包含了JVM和Java的核心类库(Java API)

jdk: jre和一些开发工具如jstat

整体运行过程

image-20220609160652527

源码编译:通过java源码编译器或ant、maven等编译为jvm字节码(.class文件)

类加载:通过ClassLoader及其子类来完成JVM的类加载

类执行:字节码被装入内存,进入JVM虚拟机,被解释器解释执行

jvm模型

理论图:

image-20220609161045868

三部分组成:

类加载子系统、运行时数据区、执行引擎。

类加载子系统

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。

运行时数据区

执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁

执行引擎

执行JVM字节码指令,主要有两种方式,分别是解释执行和编译执行,区别在于,解释执行是在执行时翻译成虚拟机指令执行,而编译执行是在执行之前先进行编译再执行

解释执行启动快,执行效率低。编译执行,启动慢,执行效率高。

垃圾回收器就是自动管理运行数据区的内存,将无用的内存占用进行清除,释放内存资源。

本地方法库、本地库接口

在jdk的底层中,有一些实现是需要调用本地方法完成的(使用c或c++写的方法),就是通过本地库接口调用完成的。比如:System.currentTimeMillis()方法

2. class文件

一切都要从类文件开始

class文件为二进制文件,其实就是一张表,数据项如下

image-20220609164903223

常量池

存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。 字面量比较接近于 Java 语言层面的常量概念,如文本字符串、声明为 final 的常量值等。 而符号引用则属于编译原理方面的概念,包括了下面三类常量: 类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符

image-20220609165604155

其他信息

  • 访问标记:public abstract等
  • 类索引,class类型,最终指向一个utf8,标记当前类的名字
  • 父类,同上
  • 接口,2字节记录数量,后面记录多个接口类型
  • 之后是字段,方法,属性,同接口

image-20220609201555267

组合类型:

类型案例说明
类里的属性、字段、方法等com.test.Demo.name:Ljava.lang.String英文点号隔开
标识什么类型com.test.Demo.getName:()Ljava.lang.String英文冒号隔开
方法(参数类型)返回值类型英文括弧,后面是返回值类型

实例如下:

image-20220609201901815

3. 运行时数据区

字节码文件只是个二进制文件,要跑起来,需要有个可用的内存环境,也就是运行时数据区。类的初始化、对象空间的分配、垃圾的回收都是在这块区域。

线程私有的:Java虚拟机栈、程序计数器、本地方法栈

共享的:方法区、Java堆区

image-20220610162423959

虚拟机栈

image-20220612214209933

线程私有的,生命周期与线程相同。Java虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

本地方法栈

虚拟机规范里对这块所用的语言、数据结构、没有强制规定,hotspot把它和虚拟机栈合并成了1个。

概念

堆是所有线程共享的!所谓的线程安全不安全也是出自这里。

此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。

《Java虚拟机规范》并没有对堆进行细致的划分,所以对于堆的讲解要基于具体的虚拟机,我们以使用最多的HotSpot虚拟机为例。Java堆是垃圾收集器管理的内存区域,因此它也被称作“GC堆”。

jdk1.7

image-20220612220845935

年轻代

Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区。Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用。

Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到下面的Tenured区间。

老年代

主要保存生命周期长的对象,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。

永久代

主要保存类信息,class,method,filed等对象,这部份的空间一般不会溢出,除非一次性加载了很多的类。

热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误。重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。

jdk1.8

image-20220612221628313

永久代被干掉,换成了Metaspace(元数据空间),Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。

image-20220612221709861

方法区

线程共享的,存储类的信息、类里定义的常量、静态变量、编译器编译后的代码缓存。

方法区在虚拟机规范里这是一个逻辑概念,实际上,hotspot1.7放在永久代,1.8一部分放在元空间,定义的类对象放在堆中。

方法区具体存放:

  • 类信息:类相关的版本、字段、方法、接口描述、引用等
  • 运行时常量池:编译阶段生成的常量与符号引用、运行时加入的动态变量

1.6里字符串常量是运行时常量池的一部分,在永久代,一直造字符串,就会报永久代内存溢出;1.8以后,字符串在堆中,只会受到最大堆内存的限制。

案例

假设有个Bootstrap的类,执行main方法。在jvm里,它从class文件到跑起来,大致经过如下步骤:

image-20220612222634225

  1. 首先JVM会先将这个Bootstrap.class 信息加载到内存中的方法区
  2. 接着,主线程开辟一块内存空间,准备好程序计数器pc,虚拟机栈、本地方法栈
  3. 然后,JVM会在Heap堆上为Bootstrap.class 创建一个Bootstrap.class 的类实例
  4. JVM开始执行main方法,这时在虚拟机栈里为main方法创建一个栈帧
  5. main方法在执行的过程之中,调用了greeting方法,则JVM会为greeting方法再创建一个栈帧,推到虚拟机栈顶,在main的上面,每次只有一个栈帧处于活动状态,当前为greeting
  6. 当greeting方法运行完成后,则greeting方法出栈,当前活动帧指向main,方法继续往下运行

4. 类加载

字节码如何进入jvm的内存空间,各自进入哪个空间,如何跑起来?

image-20220612222831233

加载

概述

将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态数据结构转化为方法区中运行的数据结构,并且在堆内存中生成一个java.lang.Class对象作为访问方法区数据结构的入口。

image-20220612222949056

注意:

  • 加载的字节码来源,不一定非得是class文件,可以是符合字节码规范的任意地方,甚至二进制流等
  • 从字节码到内存,是由加载器(ClassLoader)完成的
类加载器

jvm提供了3个系统加载器,分别是Bootstrap loaderExtClassLoaderAppClassLoader

bootstrap loader

负责加载以下路径的文件:

  • %JAVA_HOME%/jre/lib/*.jar
  • %JAVA_HOME%/jre/classes/*
  • -Xbootclasspath参数指定的路径

ExtClassLoader

sun.misc.Launcher$ExtClassLoader,加载以下路径的文件:

  • %JAVA_HOME%/jre/lib/ext/*
  • ext下的所有classes目录
  • java.ext.dirs系统变量指定的路径中类库

AppClassLoader

sun.misc.Launcher$AppClassLoader,ClassLoader中有个getSystemClassLoader方法,此方法返回的就是它

  • 负责加载 -classpath 所指定的位置的类或者是jar文档
  • 也是Java程序默认的类加载器
自定义类加载器

除了系统提供的,jvm也允许自定义类加载器,如tomcat:

image-20220613174526617

双亲委派

参看ClassLoader.loadClass(),类加载器优先调用父类的load方法,避免重复加载、核心类被篡改。

验证

加载完成后,class里定义的类结构就进入了内存的方法区。

之后,验证是连接阶段的第一步。实际上,验证和加载是交互进行的,比如class文件格式验证。

之所以把验证放在加载的后面,是因为除了基本的class文件格式,还需要其他很多验证。

文件格式验证

验证加载的字节码是不是符合规范

  • 是不是CAFEBABYE开头
  • 主次版本号是否在当前jvm虚拟机可运行的范围内
  • 常量池类型对不对
  • 有没有其他不可识别的信息
元数据验证

到java语法级别了。这个阶段主要验证属性、字段、类关系、方法等是否合规

  • 是否有父类?除了Object其他类必须有
  • 是否继承了不该被继承的类,比如final
  • 是不是抽象类,是的话,方法都完备了没
  • 字段有没问题?是不是覆盖了父类里的final

你的类对象结构是ok的了

字节码验证

上面的验证是基本字节表格式验证。而这里主要验证class里定义的方法,看方法内部的code是否合法。

  • 类型转换是不是有问题?
  • 指令是否跳到了方法外的字节码上?

确保你的代码执行时,不会发生大的意外

符号引用验证

最后一个阶段

字节码里有的是直接引用,有的是指向了其他的字节码地址。

而符号引用验证的就是,这些引用的对应的内容是否合法。

  • utf8里记了某个类的名字,这个类存在不?
  • 方法或字段引用,这些方法在对应的类里存在不存在?
  • 类、字段、方法等上面的可见性是否合法

准备

为class中定义的各种类变量分配内存,并赋初始值。

变量类型

是类变量,也就是类里的静态变量,而不是new的那些实例变量。new的在下面的初始化阶段。

  • 类变量 = 静态变量
  • 实例变量 = 实例化new出来的那些
存储位置

理论上这些值都在方法区里

  • 1.6里,在永久代

  • 1.8以后,静态类变量如果是一个对象,其实它在堆里。

初始化值

这个值进入了内存,那到底内存里放的value是啥?是它的初始值!

  • 普通类变量:在准备阶段为它开了内存空间,但是它的value是int的初始值,也就是 0!真正的123赋值,是在类构造器,也就是下面的初始化阶段
  • final修饰的类变量,编译成字节码后,是一个ConstantValue类型,在准备阶段,直接给定值123

解析

解析类之间的关系,需要关联的类被加载

  • 类或接口的解析:类相关的父子继承,实现的接口都有哪些类型?
  • 字段的解析:字段对应的类型?
  • 方法的解析:方法的参数、返回值、关联了哪些类型
  • 接口方法的解析:接口上的类型?

经过解析后,当前class里的方法字段父子继承等对象级别的关系解析完成。

这些操作上相关的类信息也被加载。

初始化

经过这个步骤后,类信息完全进入了jvm内存,直到它被垃圾回收器回收。

这个阶段,是赋值,才是我们应用程序中编写的有主导权的地方。

两个初始化

  • 类变量:初始化是一个class类加载到内存的过程,初始化值是类里面定义的类变量,也就是静态变量。
  • 实例变量:new一个类,实例变量,在执行阶段才创建

实例变量创建的过程

在方法里面写一段代码,执行的过程中,要new一个类的时候:

  • 在方法区找到对应类型的类信息
  • 在当前方法栈帧的本地变量表放置一个reference指针
  • 在堆中开辟一个空间,放这个对象的实例
  • 将指针指向堆里对象的地址

5. 对象创建

jvm里的代码跑起来后,运行的过程中,对象的创建和销毁在内存中经历了哪些事件。

内存分配

类加载检查通过后,要给新的对象分配内存。类型确定后,内部定义哪些结构哪些值,确定所需的内存,

具体内存是怎么划出来?

指针碰撞

分配前提是内存中有整片连续的空间,用的在一边,空闲的在另一边,一个指针指向分界线。需要多少,指针移动多少。

空闲列表

如果不规整,存在内存碎片,就需要单独一个free list记录哪些内存是空的。分配的时候查表并更新。

具体方式需要看GC。

并发性

无论指针移动还是空闲列表的同一个指针空间,并发分配的时候的并发问题如何解决?

方式1:cas原子操作+失败重试,缺点:太慢

方式2:本地线程分配缓冲TLAB,Thread Local Allocation Buffer,需要-XX:+/-UseTLAB

对于栈、计数器、每个线程独享,堆是共享的。实际上,可以让线程在创建时,先独享划走一部分堆。创建对象需要内存时,可以在自己划走的堆上先操作。当前线程空间不够时,再去公共堆上申请,这样就减少了并发冲突的机会。

内存布局

对象拿走的这块内存,它都写了些啥进去呢?可以分为三个部分:对象头、实例数据、对齐填充。

image-20220613224708497

对象访问

对象在堆里,方法相关的变量信息都在栈里,怎么找到对象呢?

句柄访问

image-20220613224921262

栈指针指向堆里的一个句柄的地址,这个句柄再定义俩指针分别指向类型和实例

垃圾回收移动对象的话只需要改句柄即可,不会波及到栈,但是多了一次寻址操作

直接地址

image-20220613225135932

栈指针指向的就是实例本身的地址,在实例里封装一个指针指向它自己的类型。垃圾回收移动对象要改栈里的地址值,但是它减少了一次寻址操作。hostspot使用的是直接地址方式

6. 对象的销毁

也就是垃圾回收,首先了解jvm参数。

jvm参数

三类

  • 标准参数(-)如-help -version
  • 非标准参数(-X)默认jvm实现这些参数的功能,不保证所有jvm都满足,不保证向后兼容。如-Xint
  • 非Stable参数(-XX)各个jvm实现会有所不同,如-XX:+UseSerialGC
标准参数

-Server -Client 64位OS只有server

-D 设置系统参数,其实就是main的args里面的参数。

-X参数

-Xms 初始堆大小

-Xmx 最大堆大小

-Xss 线程堆栈大小

-Xint、-Xcomp、-Xmixed 默认解释和编译的混合

-XX参数

使用有2种方式,一种是boolean类型,一种是非boolean类型:

  • boolean类型 -XX:[+-]<name> 启用或禁用<name>属性 如-XX:+DisableExplicitGC
  • 非Boolean类型,-XX:<name>=<value>-XX:NewRatio=4 年轻代和老年代比值1:4

详细:

#行为参数(功能开关)
-XX:-DisableExplicitGC  禁止调用System.gc();但jvm的gc仍然有效
-XX:+MaxFDLimit 最大化文件描述符的数量限制
-XX:+ScavengeBeforeFullGC   新生代GC优先于Full GC执行
-XX:+UseGCOverheadLimit 在抛出OOM之前限制jvm耗费在GC上的时间比例
-XX:-UseConcMarkSweepGC 对老生代采用并发标记交换算法进行GC
-XX:-UseParallelGC  启用并行GC
-XX:-UseParallelOldGC   对Full GC启用并行,当-XX:-UseParallelGC启用时该项自动启用
-XX:-UseSerialGC    启用串行GC
-XX:+UseThreadPriorities    启用本地线程优先级
 
#性能调优
-XX:LargePageSizeInBytes=4m 设置用于Java堆的大页面尺寸
-XX:MaxHeapFreeRatio=70 GC后java堆中空闲量占的最大比例
-XX:MaxNewSize=size 新生成对象能占用内存的最大值
-XX:MaxPermSize=64m 老生代对象能占用内存的最大值
-XX:MinHeapFreeRatio=40 GC后java堆中空闲量占的最小比例
-XX:NewRatio=2  新生代内存容量与老生代内存容量的比例
-XX:NewSize=2.125m  新生代对象生成时占用内存的默认值
-XX:ReservedCodeCacheSize=32m   保留代码占用的内存容量
-XX:ThreadStackSize=512 设置线程栈大小,若为0则使用系统默认值
-XX:+UseLargePages  使用大页面内存
 
#调试参数
-XX:-CITime 打印消耗在JIT编译的时间
-XX:ErrorFile=./hs_err_pid<pid>.log 保存错误日志或者数据到文件中
-XX:-ExtendedDTraceProbes   开启solaris特有的dtrace探针
-XX:HeapDumpPath=./java_pid<pid>.hprof  指定导出堆信息时的路径或文件名
-XX:-HeapDumpOnOutOfMemoryError 当首次遭遇OOM时导出此时堆中相关信息
-XX:OnError="<cmd args>;<cmd args>" 出现致命ERROR之后运行自定义命令
-XX:OnOutOfMemoryError="<cmd args>;<cmd args>"  当首次遭遇OOM时执行自定义命令
-XX:-PrintClassHistogram    遇到Ctrl-Break后打印类实例的柱状信息,与jmap -histo功能相同
-XX:-PrintConcurrentLocks   遇到Ctrl-Break后打印并发锁的相关信息,与jstack -l功能相同
-XX:-PrintCommandLineFlags  打印在命令行中出现过的标记
-XX:-PrintCompilation   当一个方法被编译时打印相关信息
-XX:-PrintGC    每次GC时打印相关信息
-XX:-PrintGCDetails    每次GC时打印详细信息
-XX:-PrintGCTimeStamps  打印每次GC的时间戳
-XX:-TraceClassLoading  跟踪类的加载信息
-XX:-TraceClassLoadingPreorder  跟踪被引用到的所有类的加载信息
-XX:-TraceClassResolution   跟踪常量池
-XX:-TraceClassUnloading    跟踪类的卸载信息
-XX:-TraceLoaderConstraints 跟踪类加载器约束的相关信息
参数查询

先jps查进程号,再jinfo -flags 进程号

垃圾回收

时间、地点、人物

地点

主要是堆

时间

堆内存达到阈值,或者手动调用system.gc()

人物

回收哪些对象?

两种判定方法:

  • 引用计数法 jvm不用,因为无法解决循环引用
  • 可达性分析
可达性分析

image-20220613233221514

哪些可以作为GCroots?

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 在方法区中类静态属性引用的对象(类变量)。
  • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
  • 所有被同步锁(synchronized关键字)持有的对象。
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
4类引用

强引用

new出来的

软引用

被SoftReference包装的那些类,先回收没用的对象,收完后发现还不够,再触发二次回收,对软引用对象下手

弱引用

被WeakReference包装的那些类,GC时弱引用直接被回收

虚引用

最弱的,为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知,无法通过虚引用来取得一个对象实例。

回收策略

最基础的:标记清除法

进阶:

  • 标记压缩算法 解决碎片化问题,对象需要移动内存位置,效率低
  • 标记复制算法 内存分为两半,每次用一半,回收时将正在用的对象复制到另一半空间,清空这一半

分代策略

具体情况具体分析,相关:

  • 部分收集(Partial GC)
    • 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。
    • 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。(CMS收集器)
    • 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。(G1收集器)
  • 整堆收集(Full GC)
    • 所有的内存整理一遍,包括堆和方法区。轻易不要触发

回收器

前置内容
  • 用户线程:java程序运行后,用户不停请求操作jvm内存,这些称为用户线程

  • GC线程:jvm系统进行垃圾回收启动的线程

  • 串行:GC采用单线程,收集时停掉用户线程

  • 并行:GC采用多线程,收集时同样要停掉用户线程

  • 并发:用户线程和GC线程同步进行,这意义就不一样了

  • STW:stop the world ,暂停响应用户线程,只提供给GC线程工作来回收垃圾(很不爽的事情)

  • 分代:垃圾收集器是要工作在某个代上的,可能是年轻代,老年代,有的可能两个代都能工作

  • 组合:因为分代,所以得有组合

串行

新生代 Serial 采用复制算法,Serial Old采用标记整理算法

在单核或资源有限的环境下,单线程甚至比多线程还要高效。

并行

Parallel Scavenge (新生代的) / Parallel Old (老年代的)

并发 - CMS

CMS收集器,工作在老年代

  • 初始化标记(CMS-initial-mark) :标记root直接关联的对象,会导致stw,但是这个没多少对象,时间短
  • 并发标记(CMS-concurrent-mark):沿着上一步的root,往下追踪,这步耗时最长,但是与用户线程同时运行
  • 重新标记(CMS-remark) :因为上一步是并发进行的,所以再增量过一遍有变化的,会导致stw,但比上一步少很多
  • 并发清除(CMS-concurrent-sweep):标记完的干掉,因为是标记-清除算法,不需要移动存活对象,所以这一步与用户线程同时运行
  • 重置线程:重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行

缺点:它不能等到内存吃紧了才启动收集。因为收集期间用户线程还在跑,得预留

并发 - G1

jdk9中将G1变成默认的垃圾收集器

三步即可完成调优:

  1. 第一步,开启G1垃圾收集器
  2. 第二步,设置堆的最大内存
  3. 第三步,设置最大的停顿时间

依然把内存划分为eden、survivor、old,同时多了一个humongous(巨大的)区来存巨型对象。但是,这些区在物理地址上不再连续。而是把整个物理地址分成一个个大小相等的region,每一个region可以是上面角色中的一个,还可以在某个时刻转变角色,从eden变成old,收集某些性价比高的region回收就可以了。

  • Remembered Set:记忆集,简称RS,每个 Region关联一个。RS 比较复杂,简单来说就是记录Region之间对象的引用关系。

  • Collection Set:简称CSet,在一次收集中,那些性价比高的Region揪出来组成一个回收集,将来一口气回收掉。这个集合里是筛选出来的一些Region

    至于Region里面剩下的存活的对象,多个Region压缩到一个空闲Region里去,这样就完成了一次收集

G1中提供了三种模式垃圾回收模式,Young GC、Mixed GC 和 Full GC,在不同的条件下被触发。

所谓的模式,其实也就是G1收集的时候,Region选哪种,是只选年轻代的Region?还是两种都筛选?

full GC :当mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会改为使用serial old GC(full GC)来收集整个堆。

image-20220613235234303

  • 初始标记:标记出 GC Roots 直接关联的对象,这个阶段速度较快,STW,单线程执行。
  • 并发标记:从 GC Root 开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。
  • 重新标记:修正在并发标记阶段因用户程序执行而产生变动的标记记录。STW,并发执行。
  • 筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划,筛出CSet后移动合并存活对象到空Region,清除旧的,完工。因为这个阶段需要移动对象内存地址,所以必须STW。

优缺点

  • 可预见性:为了缩短停顿时间,G1建立可预存停顿的模型,这样在用户设置的停顿时间范围内,G1会选择适当的区域进行收集,确保停顿时间不超过用户指定时间。
  • 空间整理:G1在回收过程中,会进行适当的对象移动,不像CMS只是简单地标记清理对象。在若干次GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会有效地复制对象,减少空间碎片,进而提升内部循环速度。
  • 并发性:继承了CMS的优点,可以与用户线程并发执行。当然只是在并发标记阶段。其他还是需要STW

建议

  • 经验值上,小内存6G以内,CMS优于G1,超过8G,尽量选择G1
  • 如果应用程序追求低停顿,可以尝试选择G1
并发-ZGC

jdk11新加入,试验阶段。

调优

[外链图片转存中…(img-ioyMqeov-1655175339236)]

  • 初始标记:标记出 GC Roots 直接关联的对象,这个阶段速度较快,STW,单线程执行。
  • 并发标记:从 GC Root 开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。
  • 重新标记:修正在并发标记阶段因用户程序执行而产生变动的标记记录。STW,并发执行。
  • 筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划,筛出CSet后移动合并存活对象到空Region,清除旧的,完工。因为这个阶段需要移动对象内存地址,所以必须STW。

优缺点

  • 可预见性:为了缩短停顿时间,G1建立可预存停顿的模型,这样在用户设置的停顿时间范围内,G1会选择适当的区域进行收集,确保停顿时间不超过用户指定时间。
  • 空间整理:G1在回收过程中,会进行适当的对象移动,不像CMS只是简单地标记清理对象。在若干次GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会有效地复制对象,减少空间碎片,进而提升内部循环速度。
  • 并发性:继承了CMS的优点,可以与用户线程并发执行。当然只是在并发标记阶段。其他还是需要STW

建议

  • 经验值上,小内存6G以内,CMS优于G1,超过8G,尽量选择G1
  • 如果应用程序追求低停顿,可以尝试选择G1
并发-ZGC

jdk11新加入,试验阶段。

调优

可视化工具:gceasy

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值