JVM(一)

刚才涉及到linux时进行打栈,都和jvm相关

一、说明

jvm是压测过程中java应用重点关注的内容,也是面试的重点
此处的jvm是基于jdk1.8

jvm(基于jdk1.8) 内存型应用才需要去关注物理内存,不是非内存型应用,关注jvm内存就行。

二、基础回顾

2.1、java特点

java特点简单,面向对象、跨平台(一次编译,处处运行)增加了内存管理

之所以能跨平台:是因为在不同平台安装相应的jvm来进行实现
平台有window、linux平台等

安装对应的jvm平台,将其他平台编译好的代码在你的代码进行运行

增加了内存管理,不需要为每一个new操作去写对应的内存回收代码(因为new的时候会创建对象,即会在堆内存中分配空间),有了内存管理机制,就不容易出现内存泄露和内存溢出

内存泄露、内存溢出的区别
内存泄露是一个过程,内存泄露堆积的后果就是内存溢出
内存溢出是一个结果

2.2、jdk、jre、jvm的关系

可以通过官网的图看出
官网: https://docs.oracle.com/javase/8/docs/index.html
在这里插入图片描述

2.3、数据类型及存储

数据类型分为基本数据类型、引用数据类型

基本数据类型的值是存在栈内存

引用数据类型(类、接口、数组)值存在堆内存中,堆中的首地址是存在栈里如果是数组,存的数组在堆中的首地址

堆:主要用来存对象。

栈:运行时单位,一般来说,一个线程对应一个线程栈。(局部变量、返回值)**

三、Run-Time Data Areas(JVM运行时数据区)

3.1、示意图在这里插入图片描述

3.2、官网

3.3、划分

一、类加载流程

官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
先编写java文件,javac文件编译,把.java文件编译成jvm能识别的格式(字节码文件.class)
.class文件可以用反编译工具打开 执行:把.class文件加载到jvm里。 加载字节码(到jvm)步骤:
装载(Loading):先判断是否装载字节码文件。classloader进行装载
包名-类名的路径下找文件,获取文件的字节流,把文件交给jvm。 链接(Linking): 验证(保证类的正确性对字节码、引用、数据进行验证)
准备prepare,为变量分配内存空间。
解析将符号引用转换为直接引用。符号引用和内存无关,只是一个代称,直接引用是地址,数组直接指向数组的首地址,第一个元素的首地址。
初始化(Initializing),类中的静态变量,初始化值,对应的内存的值,为静态变量赋值。
常量必须要初始化,要在代码块初始化。执行静态代码块。 使用use,卸载unload

上述都做完了,就可以去运行main方法(类名.方法名运行) 结束

其它

二、Run-Time Data Areas

在这里插入图片描述
划分:

3.1.1、虚拟机栈(Java Virtual Machine Stacks)

线程私有的,生命周期和线程相同,创建一个线程就要有一个栈保存相关内容。每一个栈就表示一个线程,描述了方法执行的内存模型。

每一个方法在实行的时候都会创一个栈针,调一个方法就叫压栈,最上面的就方法运行完就会出栈,存储局部变量之类的,方法的返回值,
栈帧在虚拟机调用到执行完成过程,对应的就是入栈出栈。
线程的执行顺序也可理解java虚拟机保存的,一个线程执行时调方法,每调一个方法就会入栈,执行结束时会出栈,,保存的栈帧先进后厨每一个方法就是一个栈帧

main方法入栈 去调用test1会将test1压栈 test1调用test2,会将test2压栈
test2执行完后,会将test2的返回值给test1,test1将返回值给main方法。
位于最上面的栈帧他是有效且在执行,下面的方法都是在等上面的方法给他返回值, 栈帧包含: 局部变量、操作数栈、动态链接、方法返回
局部变量存放方法中定义的变量(基本数据类型、引用类型、返回地址)、方法返回
操作数栈存储操作数的(变量运行时需要的数据结构、以出栈和压栈去操作操作数。 动态链接所属方法的引用(方法所属类运行常量的引用)
方法返回正常、返回异常 方法执行过程、字节码指令 压栈的栈帧数量多的话,stackoverflower栈溢出。自己调自己,

6)栈溢出

java.lang.StackOverflowError

常见:

递归调用没结束条件
调用链过长

在这里插入图片描述

3. 1.2、本地方法栈(Native Method Stacks)

本地方法执行所需要的一块内存区域 被native修饰的就是本地方法

Objetc类里有很多被native修饰的方法,没有方法体,由c语言实现的

和虚拟机栈类似,本地方法在执行时都会创建一个栈帧,用来存储局部变量表、操作数栈、动态链接、方法返回。只是说是为虚拟机使用Native方法时服务的。

在这里插入图片描述

3.1.3、程序计数器(Program Counter Register)

线程私有的,当前线程所执行的字节码的行号的解释器,存放的是下一个即将执行执行指令的位置。
每一个线程都需要有一个程序计数器,线程恢复时都能恢复到正确位置进行执行。

3.1.4、堆(Heap)

存放对象。

特点:内存最大的一块,线程共享,生命周期和虚拟机一样,启动虚拟机会创建堆内存区域,并且只有一个。

在做垃圾回收时,主要是在堆中,是垃圾区域主要管理的区域。

说明:对象内存页可以分配在栈上;逃逸分析。

堆还可以细分年轻代、老年代。

年轻代还可以细分。

3.1. 5、方法区(Method Area)

存放每个类的结构

存放内容:类信息、运行时常量池、静态变量(static修饰的方法)

jdk1.8 jdk1.8之方法存在元空间 MetaSpace(元空间):不在虚拟机里,使用本地内存。
jdk之前有一个持久代的概念,jdk1.8之后没有这个概念。

四、gc机制

(垃圾回收算法、垃圾收集器)

为什么做回收?
jvm内存有限,物理内存都分配给jvm,物理内存有限,若不做 回收,内存会溢出,垃圾回收可以避免内存溢出,有效的使用空闲内存。

触发垃圾回收也有一定条件:
Java虚拟机内存空间有限,创建的大对象放不下会触发gc,通过没有引用的对象清理,可以释放空间分配给后面的对象。

4.1、如何确定是否垃圾对象?

他有垃圾自动回收机制,肯定先看对象是否为垃圾,如果是垃圾达到了触发垃圾回收条件,就会将垃圾回收掉。如果不是,就不会进行回收。

关于如何确定是否为垃圾对象?
主要有两种方式:引用计数法、可达性分析算法

1)引用计数

①图

左边是栈(栈中有局部变量表),右边是堆

此处有三个引用类型(在栈中存的是地址,即就是在堆中的地址,会指向堆中的对象,这三个引用都指向了堆中的对象3的对象),一个基本数据类型int(存在于栈中)

堆中还有两个对象,对象1,对象2,是相互引用
在这里插入图片描述

看下是如何判断对象是否为存活的?

在堆中每个对象都是有一个引用计数的,引用计数可能加也可能减。

引用计数加
当一个对象被创建时,并且将对象赋值给变量时,计数器的值设置为1,
假设此处创建了一个对象3,最开始只有obj1指向对象3,那么引用计数就设置为1,如果obj1又赋值给了obj2,和obj3,即就是赋的地址,那么引用计数都会加1

在这里插入图片描述

引用计数减
第一种是:如果此时的引用目前指向的是对象3,如果将其指向另一个对象,指向对象3的引用就少了一个,这个对象的引用计数就会减1
第二种;引用被释放,引用不存在了,这个对象的引用计数也会减1

优点、缺点

优点:通过引用计数算法判断是否为对象优点是简单,不需要再触发垃圾回收再去计算。
缺点:无法解决对象循环引用导致的问题(循环引用会被认为是垃圾对象)

引用计数是通过引用计数值判断对象是否存活。
局部变量表:有基本、引用类型变量。
堆中每个对象都有一个引用计数。

指向对象1,对象1的引用计数加1,
又有一个对象指向对象1,引用计数加1,变成2。

引用计数增加:当一个对象被new时,在堆中分配内存,new后分配一个引用变量,引用计数加1.把直接一个引用指向对象,值也会加1
ref1 创建对象new 赋值给引用变量,引用计数加1 ,ref1 = new Obj();
ref2=ref1;

ref1 \ref2都指向了对象,此时引用计数就会2;

引用计数减:
ref2=null
对象的引用计数减一

局部变量表会释放。

没有引用的对象(即引用计数器为0)即为垃圾,是不是要回收不一定,触发GC还需要一定条件.
创建一个对象时,放不下就需要做GC,不做GC,对象没有引用指向它,也会先存在堆内存,只有触发GC且判断对象引用的计数为0,就当作垃圾回收。

触发垃圾回收时,没有引用的对象会将其清除(即引用计数器为0)

  • 创建对象的时候赋值给一个变量,一个引用变量赋值给另外一个引用变量
  • 引用被重新赋值,引用被释放 优点:简单 缺点:无法解决对象循环引用导致的问题(对象1的属性指向对象2的属性,对象2指向对象1,刚才是通过局部变量表来计算引用计数)

循环引用:

2)可达性分析

①也叫根搜索法
②HotSpot通过可达性分析法判断对象是否存活

在这里插入图片描述

③GC Root

可达性分析是通过GC Root触发一层一层往下找,看对象是否可达,若可达,就会存活对象,不可达(如果目标对象没有与任一个引用相连,意味着对象已经死了)就标记为垃圾。

做GC Root条件:比较长的时间都存在

可以成为gc root:Thread、类加载器

后面讲分析oom,此处就是Thread

虚拟机栈中本地变量变量表,本地方法栈,线程、类加载器、常量,这些做GCroot就可以判断对象是否可达,对象存在循环引用也可判断,被根对象直接或间接连接的对象就是存活的对象。**

java -version
Java HotSpot™ 64-Bit Server VM (build 25.211-b12, mixed mode)
HotSpot™ 就是一个jvm厂商,他的这个jvm就是通过可达性分析来判断对象是否存活。
在这里插入图片描述

4.2、垃圾收集算法

通过两种方法判断哪些对象是否为垃圾,触发GC时,对垃圾进行回收,下面就有垃圾回收算法。

1 )标记-清除(Mark-Sweep)

看一下回收前回收后有什么区别?

在这里插入图片描述
标记清除分为两部分:一部分标记,一部分清除。

标记哪些对象为垃圾,标记会对堆内存中的所有对象都会遍历一次,把垃圾找出来,耗时,效率低。

清除,对全部垃圾对象清除。
回收后,产生大量碎片空间,导致内存不连续,创建对象时申请的内存比较大,就可能放不下会触发GC,会再清理,导致GC次数增多。

缺点:

耗时(标记、清除)、效率低
耗时不仅仅表现在标记处耗时,对整个堆进行遍历比较耗时,清除其实也比较耗时(他要一次对所有标记的对象进行清除,标记对象多,清除也是比较耗时)

产生产生大量碎片空间,可能导致GC次数增多(像这种内存不连续、碎片太多,就会导致gc提前,gc次数增多)

为啥会导致gc提前?

因为标记清除后产生的很多不连续的内存碎片,如果程序在运行过程中需要分配较大空间(对象比较大,占用的存储空间比较大),此时就无法满足分配需求,此时放不下对象,就会触发gc

所以在一定时间内,产生大量碎片的情况下,产生gc的次数会更多。
gc次数多了,用户进程时间就少了,tps就会下降

GC时,用户进程时间少,用户进程要等待gc线程执行完再执行,直接表现是tps低,cpu执行用户进程时间少,处理请求少,时间一定,tps低。

2)复制算法Copying

复制算法就是为了解决效率问题

复制算法主要是为了回收新生代对象复制算法不适合老年代

把内存分两块相等区域,每次只使用一块内存。 对象在年轻代的生命周期短朝生夕死,存活的对象较少,内存复制时比较耗时。
复制算法就在s0,s1,s0,s1只用到一块,空间少,浪费不是很多。

在这里插入图片描述
回收前,每次只用一部分,就会导致内存空间浪费。
回收后,有引用的对象会放在连续的位置,会去判断对象是否会有引用,有引用复制一边连续。

堆内存的分配:分为年轻代young、老年代old
年轻代占内存的1/3.老年代占内存的2/3
年轻代又分为:eden区8/10,和存活区
存活区又分为 s0 ,s1 s0 又叫做from 占1/10 s1又叫to 占 1/10
复制算法就在s0,s1,s0,s1只用到一块,空间少,浪费不是很多。

复制算法是如何提高效率?

优点:空间连续,实现简单

缺点:浪费空间

3)标记整理:

在这里插入图片描述

标记过程与标记清除过程一致,标记处理方式不一样,

标记清除标记是对整个标记为垃圾进行清理,而标记整理是整理的操作,标记为存活的对象,会把对象朝某一端移动,一次性清除存活对象以外空间。
相对于复制算法是节约空间。

优缺点:

优点对象连续
缺点标记耗时

应用场景
老年代

4)分代收集算法

堆内存大致分为年轻代、老年代

查看堆内存的使用情况

jmap -heap  进程号

Garbage-First (G1) GC with 1 thread(s)
Heap Configuration: 堆的配置

NewRatio 配置年轻代和老年代的比例,老年代比上年轻代比值是2:1,老年代是2/3,年轻代是1/3
SurvivorRatio 是eden和存活区的比例,8:1,两个存活区

Heap Usage:
年轻代、老年代

在这里插入图片描述

年轻代包括下面这三个

在这里插入图片描述

年轻代采用复制算法,具体在from和to里,每次只有一个在使用,另外一个是空闲状态。
老年代可以用标记整理,也可以用标记清除。
cms用的是标记清除。

在这里插入图片描述

4.3、垃圾收集器

垃圾收集算法是内存回收的理论

算法的具体实现就是垃圾收集器。对于大多数jvm,是需要垃圾收集器组合用,一个是作用于年轻代,一个是作用于老年代,所以在jvm中通常会看到两种收集器组合使用

1)垃圾收集器的分类

①串行收集器(带Serial这种的)

Serial用于年轻代,单线程

SerialOld用于老年代

特点:

单线程,只能有一个垃圾回收线程进行垃圾回收.

暂停用户线程,gc时是不会处理用户进程

效率低

②并行收集器(带 Parallel)

平行收集器分为普通和吞吐量优先的

普通

ParNew收集器,适用于年轻代。
ParNew收集器就是Serial多线程版本

吞吐量优先
它里面又包含Parallelscavenge、ParallelOld

Parallelscavenge适用于年轻代
ParallelOld适用于老年代

特点

是多个垃圾收集线程并行工作,也会暂停用户线程

并发:同时运行,并不是同一个时刻,说的是相对并发,在一个较短的时间内可以交替运行(用户进程和垃圾收集线程交替运行,不会暂停线程)

特点:

多个垃圾收集线程并行,会暂停用户线程。

C .并发收集器

停顿时间优先(只希望很短时间,低延迟,适用于对时间要求严格的应用)
常见的并发收集就是 CMS、G1
让停顿尽可能时间少

CMS老年代
在jvm参数只配置老年代CMS,JVM会适配一个年轻代收集器ParNew。

G1同时应用于年轻代和老年代,他的堆内存与之前的堆内存划分不一样。

结合示意图再看一下
适用的代划分

2)适用年轻代的

①Serial收集器

单线程,不涉及到线程之间的竞争,收集也是较高效,缺点会暂停用户线程,用复制算法。
在这里插入图片描述
黄色是gc线程,红色是用户线程
蓝色代表应用线程停止在安全点,即暂停用户线程去执行垃圾回收,再从安全点执行应用程序。

图中意思:如果触发了gc,到下一个时间点,就只有gc线程在运行(且只有一个),gc暂停后又会开始之前的用户线程,明显特点是单线程,如果是单线程,就只会用一个cpu,去做垃圾收集的工作,更重要的是在做垃圾收集同时,还要暂停用户现场

如:
有多个cpu,用户进程是配了多线程,多线程在运行。

优点

简单高效(基于适用的场景而言:桌面应用(内存比较小,进行垃圾回收时间比较短),客户端模式,客户端默认新生代垃圾收集器就是Serial收集器,内存不是很大,进行垃圾回收的时间也不是很大。)

缺点

会暂停所有用户线程

应用场景:client端垃圾回收

用到的算法:复制算法

②ParNew收集器

适用范围:新生代

Serial收集器的多线程版本

是针对于Serial收集器的改进版,现在都是多CPU,如果只是一个线程跑,会暂停所有用户线程,其实cpu资源是没有被充分利用。
Serial收集器的多线程版本

算法:也是复制算法

优点:效率比Serial收集器高

缺点:会暂停所有用户线程

③Parallel Scavenge收集器

多线程收集器

在这里插入图片描述

也是用复制算法

与ParNew收集器的区别

和ParNew收集器差不多,Parallel Scavenge但是更关注系统的吞吐量

吞吐量=用户线程时间/总时间(用户线程+gc时间),因为用户线程与gc时间不能同时运行。

总时间=用户线程时间+gc时间

适用范围:新生代

3)适用老年代的

①Serial Old收集器

Serial收集器的老年代版本

在这里插入图片描述
不管是新生代还是老年代,都只有一个线程再跑。
年轻代用的是复制算法,而老年代用的是标记整理算法。
配置参数可以看到两种收集器配合使用。

②Parallel Old收集器

在这里插入图片描述

Parallel Old收集器是Parallel Scavenge收集器的老年代版本
Parallel Old收集器是多线程+标记整理

③CMS收集器

CMS收集器用的是并发标记清除

它是以一种获取最短回收停顿时间目标的收集器,追求低停顿

示意图
在这里插入图片描述
算法

之所以用的是并发标记清除?
因为是垃圾收集时,用户现场和gc线程并发执行。
所以在垃圾收集过程中实际用户使用系统,不会明显感到明显卡顿。虽然在进行垃圾回收,他也会去进行处理用户请求

过程

在这里插入图片描述

过程分为四个初始标记,并发标记,重新标记,并发清除。

初始标记:为了标记gcroot下一级对象是否可达,直接关联对象,停止用户线程(比较快)对用户影响小。

并发标记:根据初始标记的结果继续标记其他所有关联的对象,和用户线程同时跑,对用户线程没有影响。

重新标记:再标记一次,上一步并发标记用户线程在跑,就会产生新的垃圾,没有用户线程(耗时短,对用户影响比较小)

并发清除:用户线程与垃圾收集线程同时进行,并发清除过程中,会产生新的垃圾,在下一次做gc在做标记清除。

同时运行,并不是同一个时刻,说的是相对并发,在一个较短的时间内可以交替运行(用户进程和垃圾收集线程交替运行,不会暂停线程)
并发标记:既有垃圾收集线程、也有用户线程,并发清除也是有,在重新标记阶段是会暂停用户线程,初始标记也会暂停用户线程。

CMS是为了获得最小的响应停顿时间。
算法

标记-清除
问题:为什么CMS不用标记-整理算法?用的是标记清除算法。
因为标记整理可以避免浮动垃圾,换成标记整理,把垃圾清除后存活对象还要整理,还要进行排序,对象地址就发生变化,此时用户线程和垃圾收集线程同时进行,把存活对象地址改掉,有引用对象的就会引用不到。

优点

并发收集,并发标记和并发清除这两个阶段去阻断用户线程执行

停顿时间短,而初始标记和重新标记停顿时间少)

缺点

产生空间碎片(因为用的是标记清除算法)

对cpu要求稍高,多核

无法处理并发清除阶段产生的垃圾,浮动垃圾

空间碎片也叫浮动垃圾,对服务器要求较高。
并发清除产生的垃圾是没法回收的,只能下一次gc才能清理掉。
在这里插入图片描述

总结:
串行收集器:
cpu单核,对堆内存的要求较小可以用serial(串行收集器),缺点会暂停用户线程,stw
并行收集器:
parnew 是serial收集器升级版本,是年轻代的收集器。
吞吐量优先的,Parallel Scavenge 年轻代,Parallel old 老年代
特点:多个收集线程,会暂停所有用户线程。

并发收集器:
停顿时间优先,CMS,仅适用于老年代,与parnew 组合用。
G1同时应用于年轻代和老年代,与之前的堆内存划分不一样。

jdk1.8以上是默认用的是Parallel集器

4)堆内存划分(除G1)、分配机制、回收过程

在这里插入图片描述
在这里插入图片描述
年轻代采用复制算法,主要体现在存活区中的s0,s1,所以有空间浪费,虽然空间浪费,但是只是占年轻代的1/10,解决了空间碎片,存在空间碎片,在放大对象时,放不下时会触发gc,若空间是规则,没有空间碎片,利用率会更高,gc时间会延迟。若划定固定的时间范围,gc次数减少,若有空间碎片,会提前gc,gc次数增加,对于的用户线程时间减少,吞吐量减少。

jvm调优重点是关注吞吐量和停顿时间、gc次数,
目标是减少停顿时间。

为什么年轻代用1/10的空间就可以解决空间碎片?
因为大部分对象是朝生夕死,生命周期短,很多对象在eden园区做外gc时就会被回收,存活下来就比较少,存活区就只需要2/10再通过复制算法解决空间碎片,而eden区就就是8/10,若对象能放得下初始分配在eden园区,这样也是最大化利用了样区。

关于s0,s1采用复制算法同一时间就只有一个在使用(只有一个有数据)

堆内存:
Garbage-First (G1) GC with 1 thread(s)
Heap Configuration: 堆的配置
NewRatio 配置年轻代和老年代的比例,老年代比上年轻代比值是2:1,老年代是2/3,年轻代是1/3
SurvivorRatio 是eden和存活区的比例,8:1,两个存活区
默认配置

非堆:
就是用的操作系统的本地内存,独立于jvm之外的。
jdk1.8新特性
metaspace,存类,方法、字段,常量池,符号引用。
主要包含:
codecache:存放编译后的本地代码,就是在非堆区域。
ccs:压缩类空间,存放的是32位的class,前提是必须启用短指针,类型才会存此处。
用这个jvm参数启用短指针:-XX:+UseCompressedClassPointers

在这里插入图片描述

分配机制:
创建对象后优先分配在年轻代的eden园区,若对象很大放不下就会触发ygc,(有的对象在触发ygc也不能回收),一直有引用的,长期存活的对象是不能一直在年轻代,它有一个年龄可以设置-XX:MaxTenuringThreshold,默认值是15,超过这个就会进入到老年代。

空间担保机制:95%对象都是朝生夕死,也有保留,保留下来做外gc,存活区也不一定把保留,存活区没有足够的空间存放之前留下的对象,通过担保机制直接进入old区,保证后续程序运行。

回收过程:
在这里插入图片描述

在这里插入图片描述

第一步:创建对象后,对象优先分配在eden园区,有大对象,大对象可以设置大小(-XX:MaxTenuringThreshold,默认值是15),直接进入old区。

第二步:随着创建的对象越来越多,eden满,触发ygc(会去判断对象是否有引用,通过gcroot 判断是否还有对象引用,若没有引用直接就就判断是垃圾,有引用的放在其中一个存放区)
第奇数次ygc,存活数据存在s0,s1就没有数据,老年代没有满还可以放对象,对象继续分配在eden园区
第偶数次ygc(会去判断eden对象是否有引用,还会去判断存活区有对象的存活区s0是否还有引用,不管是eden还是存活区中的s0区只要还有引用的都会移动到s1),即eden园区和s0就没有对象,即是复制算法,就是s0,s1是不可能同时存在数据的情况。

第三步:gc完后跑用户线程,创建对象后又往eden园区放,eden区会慢,又再一次触发ygc(会去判断eden,存活区有对象的存活区s1是否还有引用,不管是eden还是存活区中的s1区只要还有引用的都会移动到s0,即eden园区和s1就没有对象,即是复制算法,就是s1,s0是不可能同时存在数据的情况。
总结就是:偶数次触发ygc,对象是存在s1,奇数次触发ygc是放在s0.

第四步:不断做ygc,总有对象是有引用指向它们,它们到达年龄后就会进入到old即就是老年代,创建的对象超过设置的大小也会进入老年代,即老年代也会满,老年代是触发fullgc,老年代就是对老年代的对象做寻根判断(根搜索法,看对象是否可达),没有就回收,有就回收。

ygc会暂停所有用户线程,直到ygc完成。

老年代满,触发fgc

触发ygc,只有eden满,触发fgc,老年代满,调用gc

G1的堆内存划分与之前堆内存的不一样

5)新老生代都适用的

G1同时适用与新生代与老年代,因为内存布局。
G1和cms都是停顿时间优先,停顿时间短,只是CMS仅适用于老年代。
CMS的缺点:
使用的是标记清除算法,就会产生空间碎片,不能很好利用空间,可能导致多次gc,在相同时间内,吞吐量下降,虽然cms停顿时间短,但是也可以继续优化,空间碎片问题也可以继续优化,就出现了first,

Garbage-First
在这里插入图片描述
CMS
在这里插入图片描述

官网:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#garbage_first_garbage_collection

简介:jdk1.9默认是g1(server模式)
jdk1.8默认是parllergc,对应的老年代是parller old

g1内存布局:取消物理隔离
之前的内存布局是物理隔离
在这里插入图片描述
特点:逻辑保留E/S/O区域,每个region可以扮演不同的区域
g1内存布局:取消物理隔离划分,将整个堆划分成独立大小相等的区域,保留年轻代和老年代概念。
每个region可以扮演不同的区域,如eden区域,存活区,老年代,他自己有一个h区(存大对象
超过region一半就是大对象,有的对象不仅一个regin来存,可能会用n个连续region来表示)
一个region大小:1-32M,必须是2的N次方,regin也可以设置大小。
h区是特殊的old区。

G1 divides the heap into fixed-sized regions (the gray boxes) as in Figure 9-1, “Heap Division by G1”.
需要大的堆内存,并且需要延迟低(停顿时间)的延迟,就可以选择g1
如6gb,更大的堆内存。
g1就是为大堆涉及,就需要多处理器,不然效率低。
停顿时间低于延迟,还是可以预测的,满足停顿时间,会选择性做回收,维护了一个回收价值表。

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#g1_gc_tuning

G1过程(和CMS类似)
CMS(初始标记、并发标记、重新标记、并发清除)
G1 (初始标记,并发标记,最终标记、筛选回收)
之所以可以预测停顿时间,是因为维护价值表,尽可能回收多的对象,又能满足停顿时间,即是筛选回收。

初始标记:为了标记gcroot下一级对象是否可达,直接关联对象,停止用户线程(比较快)对用户影响小。
并发标记:根据初始标记的结果继续标记其他所有关联的对象,和用户线程同时跑,对用户线程没有影响。
最终标记:上一步用户线程在跑,就会产生新的垃圾,暂停用户线程(耗时短,对用户影响比较小),会把指向自己的做记录
筛选回收:回收废弃的对象,暂停线程,多个垃圾线程并发执行,G1 会根据每个regin所获得的空间大小,回收所需时间,维护一个价值表,根据设置停顿时间,优先去回收价值最大的对象(回收多对象,时间少),避免了对整个堆回收。

每个regin扮演不同角色,Regin使用率是不一样的。

gc:ygc,mixedGC(不是fgc,回收年轻代和老年代,混合收集,触发条件,堆内存达到多少比例)

优势
1)算法:标记-整理,也就是regin之间实现的是复制算法,如ygc,回收regin,会通过复制算法进行垃圾回收,有引用的复制在待分配的regin,最后释放,他又是待分配,不会产生碎片空间,提高内存利用,因为提供规则内存。
2)预测停顿:可调整的停顿时间,jvm参数
停顿时间可指定为较小范围内,如200ms之类,其实也不建议设置过小。

倾向用G1的场景(50%的堆都被存活对象占用可考虑g1,对象分配率或提示率差异很大,停顿时间超过0.5-1s)
More than 50% of the Java heap is occupied with live data.The rate of object allocation rate or promotion varies significantly.The application is experiencing undesired long garbage collection or compaction pauses (longer than 0.5 to 1 second).

其它:jdk11,Z-GC,停止用户线程时间很短,小于10ms,g1可以设置,但是如果太小,导致垃圾不能被回收,最后ooms

4.4、如何选择垃圾收集器

官网:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref28
在这里插入图片描述
在这里插入图片描述
选择收集器
除非应用程序具有相当严格的暂停时间要求,否则请先运行应用程序并允许 VM 选择收集器。如有必要,请调整堆大小以提高性能。如果性能仍未达到您的目标,请使用以下准则作为选择收集器的起点。

如果应用程序具有较小的数据集(最多约 100 MB),则

使用选项 选择串行收集器。-XX:+UseSerialGC
如果应用程序将在单个处理器上运行,并且没有暂停时间要求,则让 VM 选择收集器,或使用选项选择串行收集器。-XX:+UseSerialGC

如果 (a) 峰值应用程序性能是第一优先级,并且 (b) 没有暂停时间要求或 1 秒或更长时间的暂停是可以接受的,则让 VM 选择收集器,或者选择并行收集器。-XX:+UseParallelGC

如果响应时间比总体吞吐量更重要,并且垃圾回收暂停必须短于大约 1 秒,则选择带有 或 的并发收集器。-XX:+UseConcMarkSweepGC-XX:+UseG1GC

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

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

4.5、垃圾收集器组合

在这里插入图片描述
连线是可以组合的,
没有连线是不可以组合。
因为是分代收集,年轻代用的是标记整理或者标记清除算法,就会看到有两个Jvm参数,一个是年轻代参数,另一个是老年代参数。

串行:
有颜色标注的就是最常用的配置。
serial,对应的老年代就是serialold,jvm参数配置时就是只需要写–XX+UseSerialGC,会根据写的适配另一半,适配到另一半的话就是serialold收集器。

并行:
吞吐量优先
paraller Scavenge收集器,多线程垃圾收集器,对应的老年代收集器就是paraller old,这里用指定–XX+paraller GC-XX+UserPallelOldGC

停顿时间优先:
CMS,G1
CMS主要是对老年代进行收集,这里一般指定后面一部分–XX+UserPallelNewGC–XX+ConcMarkSweepGC,自动适配到PallelNewGC
停顿时间优先和吞吐量优先不能混用,paraller Scavenge是年轻代吞吐量优先,CMS是停顿时间优先,它们两者没有连线,是不能混用的。

G1参数:
–XX+User

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值