jvm笔记

1、jvm的生命周期

(1)虚拟机的启动
虚拟机得启动时通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成得,这个类由虚拟机具体实现指定。
(2)虚拟机得执行
具体执行Java程序,其实执行的是一个Java虚拟机进程
(3)虚拟机的退出

2、判断一个类是否是同一个必须同时满足两个条件

(1)类的完全类名必须一致,包括包的路径
(2)加载这个类的类加载器必须相同

3、jvm中的程序计数寄存器有什么用

答:因为CPU会不停的切换各个线程,这个时候切换回来的时候,需要知道前一次的切换执行到的位置,因此需要使用PC寄存器来存储下一个被执行的机器指令。

4、PC寄存器为什么是私有的?

答:因为当CPU切换各个线程的时候,需要记录每个线程下一次执行的机器指令,只有每个线程拥有自己的PC寄存器才不会出现各个线程之间的干扰问题。

5、堆内存

(1)设置堆内存的参数:

1)-Xmsxxxx:-X是虚拟机参数,ms代表的是 memory start,表示设置虚拟机堆内存的初始值大小
2)-Xmx666M:代表是memory max,表示设置虚拟机内存的最大值
3)两个参数设置的大小一般保持一致,因为如果不一致,在容量变化的过程中,会占用过多系统资源,影响系统的性能
4)-NewRatio 可以设置新生代和老年代的比例,默认是默认是老年代/新生代=2/1
5)-XX:SurvivorRatio 设置新生代中eden区与Survivor0和Survivor1区的比例,默认是8:1:1,但是由于自适应的问题,一般情况下不是8:1:1。而关闭自适应策略的参数是-XX:-UseAdaptiveSizePolicy,但是一般不起作用。
6)-Xmn:设置新生代的空间大小(一般不设置)
7)-XX:+PrintFlagsInitial 查看所有参数的默认初始值
8)-XX:+printFlagsFinal 查看所有的参数的最终值
9)-XX:MaxTenuringThrehold 设置新生代垃圾的最大年龄
10)-XX:+PrintGCDetailsed 输出详细的gc日志
11)打印gc的简易信息:-XX:+PrintGC 

(2)堆内存中,包括新生代和老年代,而新生代又包括Eden区、survivor1区、survivor0区
(4)可以使用命令查看gc的情况,jstat -gc 进程id,可以查看虚拟机的内存大小及使用情况
(5)几乎所有的对象都是在eden区被new出来的,而绝大部分的对象都是在新生代进行的
(6)几乎所有的对象和数组都是存放在堆内存中的
(7)堆是分配对象存储的唯一选择吗?

答:不是,基于逃逸分析可以选择不让对象分配在堆上,但是目前是所有的对象都存放在堆上的

(8)字符串常量池和静态变量是存放在堆空间中的

6、方法区(元空间)

(1)方法区存储什么内容

方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存。

1)类信息:

对于加载的每个类型(Class、Interface、Enum、annotation),JVM必须再方法区中存储一下信息
A、这个类的完整有效名称(全限定名)
B、这个类型直接父类的有效名(对于Object和Interface是没有父类的)
C、这个类型的修饰符
D、这个类型直接接口的一个有序列表

2)域信息(Field字段或属性)

JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。域的相关信息包括,域名称、域类型、域修饰符

3)方法信息

A、方法名称
B、方法返回类型
C、方法参数的数量和类型(按顺序)
D、方法的修饰符
E、方法的字节码、操作数栈、局部变量表大小
F、异常表

(2)全局常量

就是被final static修饰的,这些全局常量在编译的时候就会被分配,就是会直接初始化,而单单static修饰的,会在Linking阶段的Prepare阶段赋值初始值,而在Initialzating阶段会赋值

(3)运行时常量池

是方法区的一部分,而常量池则是Class文件的一部分,常量池存放的是编译器生成的字面量和符号引用,这部分内容会在类加载后放到方法区的运行时常量池中。
运行时常量池,在加载类和接口到虚拟机时,就会创建对应的运行时常量池。
运行时常量池里面的引用,在常量池里面的引用是符号引用,但是在运行时常量池中就变成了真实的地址

7、类创建的步骤

  • (1)判断对象对应的类是否加载、链接、初始化
    虚拟机遇到一条new指令,首先去检查这个指令的参数是否能在MetadataSpace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载、解析、初始化。即判断类元信息是否存在。如果没有,那么在双亲委派的模式下,使用当前类加载器以ClassLoader+包名+类名为Key进行查找对应的.class文件。如果没有找到文件,则抛出ClassNotFoundException异常;如果找到,则进行类加载,并生成对应的Class对象
  • (2)为对象分配内存
    分为两种情况,一种是内存不规整,一种是内存是规整的
  • (3)处理并发安全问题
    1)采用CAS失败重试、区域加锁保证更新的原子性
    2)每个线程预先分配一块TLAB
  • (4)初始化分配到的空间,给属性赋值默认值
  • (5)设置对象的对象头
  • (6)执行init方法进行初始化

8、对象的内存布局

(1)对象头(12个字节)

1)运行时元数据mark work(8字节)

包括哈希值(主要是记录对象地址)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳

2)类型指针

原来是8个字节的,但是由于默认是压缩的,所以是4个字节
主要是指向该对象的类的元素据指针,确定该对象的类型

(2)实例数据

如果有基本类型以外的信息,没压缩就是8字节,压缩就是4字节。基本类型按照其占用字节计算
它是对象真正存储的有效信息,包括程序代码中定义的各种类型字段(包括从父类中继承下来的和本身拥有的字段信息)

(3)对其填充

如果计算出来的对象所占用的空间不足8的倍数,则补充
对象填充不是必须的

9、对象访问定位

(1)句柄访问

栈上的对象的引用变量指向在堆空间中的句柄池,在句柄池中记录着这个对象的地址和类类型相关信息,栈上的实例变量可以通过这个地址,找到句柄,然后通过句柄找到对应的对象实例的地址。

(2)直接指针(Hotspot)

栈上的对象的引用变量是直接指向了堆空间中的对象的地址,而对象里面存放着存放在方法区的对象类型的元数据。

10、垃圾回收算法

(1)回收阶段的

1)引用计数算法

引用计数算法,对每个对象保存一个整型的引用计数属性,用于记录对象被引用的情况。
当一个对象被引用了,那么该对象的计数器就加1,失去引用时,则减1,只有对象的引用次数为0时,表示不再被使用,可进行回收。

  • 优点
    实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟。
  • 缺点
    需要单独的字段存储计数器,增加了存储空间的开销,伴随着加减,增加了时间的开销。除此之外,还有一个严重的问题,就是无法解决循环引用的问题,也就是Java虚拟机没有使用这个算法的原因
2)可达性分析算法

可达性分析算法,不仅同样具备实现简单和执行高效的特点,更重要的时该算法可以有效的解决对象循环引用的问题,防止内存泄漏的发生。
所谓的GC roots根集合就是一组必须活跃的引用
基本思路:

  • A、可达性分析算法是以根对象集合(GC roots)为起始点,按照从上到下的方式搜索被根对象所连接的目标的对象是否可达
  • B、使用可达性分析算法后,内存中存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称之为引用链
  • C、如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾对象。
  • D、在可达性分析算法中,只有能够被根对象集合直接或间接链接的对象才是存活对象。
3)GC roots有几类元素、
  • A、虚拟机引用的对象
    比如,各个线程被调用方法中使用到的参数。局部变量等

  • B、本地方法栈内JNI引用的对象

  • C、方法区中类静态属性引用的对象
    比如,Java类的引用类型静态变量

  • D、方法区中常量引用的对象
    比如,字符串常量池中的引用

  • E、所有被synchronized持有的对象

  • F、Java虚拟机内部的引用
    比如,基本数据类型对应的Class类,一些常驻的异常对象、系统类加载器

  • G、反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

  • 小技巧
    由于Root采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存在堆内存中,那么它就是一个Root
    注意:如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行。这点不满足的话,分析结果的准确性就无法保证,
    这点也是GC进行时必须进行STW的一个重要原因,

4)对象的Finalization机制

当垃圾回收器发现没有引用指向一个对象,,垃圾回收该对象前,总会先调用这个对象的finalize()这个方法。而这个方法在Object类中,允许重写,进行一些对象清理前的一些处理工作。

5)虚拟机对象中的三种状态
  • A、可触及的:从根节点开始,可以到达这个对象
  • B、可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活。
  • C、不可触及的:对象的finalzed()方法被调用,并且没有复活,那么会进入不可触及的状态,不可触及的对象不可能复活,因为finalize()方法只能被调用一次。

(2)清除阶段

1)标记-清除算法
  • 执行过程:当堆中有效内存空间被耗尽的时候,就会停止整个程序,然后进行两项工作,标记和清除
  • 标记:收集器从引用根节点开始遍历,标记所有被引用的对象。一般是在对象Header中记录为可达对象
  • 清除:收集器对堆内存从头到尾进行线性遍历,如果发现某个对象时在Header中没有标记为可达对象的,则将其进行回收,注意,标记的时候,标记的是可达对象,而不是不可达对象。

缺点:效率不算高;GC时需要停顿整个应用程序,用户体验差;清理出来的空间空间是不连续的,产生内存碎片,需要维护一个空闲列表。

2)复制算法

将活着的内存空间分为两块,每次只使用其中的一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收

  • 优点:没有标记和清除过程,实现简单,运行高效;复制过去以后保证空间的连续性,不会出现碎片

  • 缺点:需要两倍的内存空间;对于G1这种分拆成为大量的区域的GC,复制而不是移动,意味着GC要维护区域之间对象引用关系,不管内存占用或者时间开销也不小
    特别的:如果系统中垃圾对象很多,复制算法需要复制的存货对象数量并不会太大,或者说非常低才行

  • 应用场景:在新生代中,对常规的垃圾回收,一次通常可以回收70~80%的内存空间,回收性价比非常高,所以现在商业的虚拟机都是用这种收集算法回收新生代。

3)标记压缩算法(标记-整理算法、Mark-Compact)
  • 执行过程:
    第一阶段和标记清除算法一样,从根节点开始标记所有被引用的对象;第二阶段将所有的存活对象压缩到内存的一端,按顺序排放;之后清理边界外所有的空间。

相比于标记清除算法,标记清除算法时非移动式的回收算法,而标记压缩算法时移动式的。
同时,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉,如此一来,当我们需要给新对象分配内存是,JVM只需要持有一个内存的起始地址即可
指针碰撞:如果内存空间以规整的方式分布,即已用和未用的内存都在各自一边,彼此之间维系着一个记录着下一次分配起始点的标记指针,当新对象分配内存时,只需要通过修改指针的偏移量将对象分配在第一个空闲内存位置上,这种非陪方式就叫指针碰撞。

  • 优点:消除内存分散的缺点,对象分配时,只需要持有一个内存起始地址即可;消除了复制算法中内存减半的高额代价。
  • 缺点:效率比复制算法低;移动对象的同时,如果对象被其他对象引用,还需要整理引用的地址;移动过程中,需要全程序暂停用户程序(STW)
4)分代收集算法

几乎所有的GC都是采用分代收集算法执行垃圾回收的
年轻代、老年代、两个区域。
以Hotspot中的CMS垃圾回收器为例,CMS基于标记清除算法,对于对象的回收效率很高,而对于碎片问题,采用基于标记压缩算法的SerialOld GC回收器作为补偿措施,采用Serial Old执行Gull GC以达到对老年代内存的整理。

5)增量收集算法

如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行,每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程,依次反复,直到垃圾收集完成。
总的来说,增量收集算法的基础仍是传统的标记-清除和复制算法。增量收集算法通过堆线程冲突的妥善处理,允许垃圾收集线程以分阶段完成标记、清除、整理工作。
缺点:由于简短执行应用程序代码,需要切换上下文,会使得垃圾回收的总体成本上升,造成系统的吞吐量下降。

6)分区算法

分区算法将按照对象生命周期长短划分成两个部分,分区算法将整个堆空间划分为连续的不同的小区间
每个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间

(3)内存泄漏

严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况下,才叫内存泄漏。
比如:
1)单例模式,单例对象的生命周期和引用程序一样长的,所以持有对外部对象的引用的话,那么这个对象是不能被回收的,则会导致内存泄漏的产生
2)一些提供close的资源未关闭导致内存泄漏:
数据库连接、网络练级、IO等必须手动close(),否则是不能被回收的。

11、并发与并行

并发,指的是多个事情,在同一时间段内同时发生了;而并行,指的是多个事情在同一个时间点上同时发生了;
并发的多个任务之间是相互抢占资源的;而并行的多个任务之间是不相互抢占资源的;
只有在多个CPU或一个CPU多个核的情况下,才会发生并行;否则看似同时发生的事情,其实都是并发执行。

12、垃圾回收的并发与并行

并行:指的是多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态,比如ParNew、Parallel Scavenge、Parallel Old垃圾回收器。
串行:相较于并行的概念,单线程执行;如果线程不够,则程序展厅,启动JVM垃圾回收器进行垃圾回收。回收完,再启动程序的线程
并发:指的是用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替进行),垃圾回收线程在执行时不会停顿用户程序的运行。如:CMS、G1垃圾回收器

13、安全点(Safe Point)

程序执行时,并非在所有地方都能停顿下来开始GC,只有在特定的位置才能停顿下来开始GC,这写位置称之为安全点。
安全点的选择非常重要,入股如果太少,可能导致GC等待的时间过长,如果太频繁,可能导致运行时的性能问题。大部分指令执行的时间都非常短暂,通常会根据”是否具有让程序长时间执行的特征“为标准。
比如,选择一些执行时间较长的指令作为安全点,如方法的调用、循环跳转和异常跳转等。

14、GC如何检查所有线程都跑到了最近的安全点停顿下来呢

(1)抢占式中断(目前没有虚拟机采用)

首先中断所有线程,如果还有线程不在安全点,就恢复线程,然线程跑到安全点。

(2)主动式中断

设置一个中断标志,各个线程运行到安全点的时候,主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起。

15、安全区域(safe Region)

安全区域,是指在一段代码中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的,我们也可以吧安全区域称为被扩展了的安全点
实际执行过程:
(1)当线程执行到安全区域的代码时,首先标识已经进入安全区域,如果这段时间内发生了GC,JVM会忽略标识为安全区域状态的线程。
(2)当线程即将离开安全区域时,会检查JVM是否已经完成了GC,如果完成了,则继续执行,否则线程必须等待直到收到可以安全离开安全区域的信号为止。

16、Java中的引用

包括,强引用、软引用、弱引用、虚引用,他们都是抽象类Reference的子类

(1)强引用

最传统的引用定义,指的是在程序中普遍存在的引用赋值,比如:Object obj = new Object();这种引用关系,只要强引用关系存在,垃圾回收器永远不会回收掉被引用的对象

(2)软引用

在系统将要发生内存溢出前,将会把这些对象列入回收范围进行二次回收(有引用的情况下才会这样,没有引用,GC的时候时直接回收的),如果这次回收后还没有足够的内存,才会抛出内存溢出的异常

(3)弱引用

被弱引用关联的对象只能生存到下一次GC之前。当垃圾收集工作时,无论内存空间是否足够,都会回收被弱引用关联的对象

(4)虚引用

一个对象是否有虚引用存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取到一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知,然后再c++层面清除虚引用对应的内存空间。相当于管理堆外内存,因为每一个虚引用都会一旦被回收就会放入到队列里,是通过这个队列进行通知和清除内存

17、垃圾回收器

(1)分类

按照工作模式分类,可以分为并发式垃圾回收器和独占式垃圾回收器
并发式垃圾回收器与应用程序交替工作,以尽可能减少应用程序的停顿时间
独占式垃圾回收器一旦运行,就停止应用程序的所有用户线程,直到垃圾回收过程完全结束。

按照碎片处理方式分,可分为压缩式垃圾回收器和非压缩式垃圾回收器
压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片
非压缩式的垃圾回收器不进行这部操作

按照工作区间分,又可以分为年轻代垃圾回收器和老年代垃圾回收器

(2)、垃圾回收期的性能指标

吞吐量:运行用户代码时间占总运行时间的比例(总运行时间=程序运行时间+内存回收的时间)
暂停时间:执行垃圾回收时,程序的工作线程被暂停的时间
内存占用:Java堆区所占的内存大小
以上为主要的三种,往后的趋势时注重暂停时间和吞吐量。

18、7种经典的垃圾回收器

串行回收器:Serial、SerialOld
并行回收器:ParNew、Parallel Scavenge、Parallel Old
并发回收器:CMS(Concurrent Mark Sweep)、G1(JDK1.8后默认使用的Server端的回收器,称之为整堆垃圾回收器,就是年轻代和老年代都能收集)

19、垃圾回收器相关参数

(1)-XX:+PrintCommandLineFlags 查看命令行相关参数
(2)使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程ID

20、回收器

(1)Serial GC,串行回收(-XX:+UseSerialGC)

最基本、历史最悠久的垃圾回收器,在JDK1.3之前回收新生代的唯一选择,并且作为Hotspot虚拟机Client模式下默认新生代的垃圾回收器。
采用复制算法、串行回收和Stop the word机制方式执行内存回收
对应的老年代的回收器时Serial Old GC,采用的串行回收和STW机制,采用标记-压缩算法

(2)ParNew GC,并行回收器

只能处理新生代,采用并行回收的方式,采用的是复制算法、STW机制
对应的参数:

-XX:+UseParNewGC、-XX:ParallelGCThreads(限制线程数量)

(3)Parallel Scavenge回收器,吞吐量优先

采用复制算法、并行回收、STW机制,具有自适应调节策略
在JDK1.6时,提供了执行老年代的收集器Parallel Old GC,采用标记-压缩算法,并行回收,STW机制
这也是JDK1.8默认使用的组合
相关参数:

-XX:+UseParallelGC或者-XX:+UseParallelOldGC
-XX:ParallelGCThreads(设置年轻代收集器的线程数),一般的,与CPU的数量相等,默认情况下,当CPU数量小于8个,线程数量就等于CPU数量;大于8个,线程数量就等于3+[5*CPU_Count/8]
-XX:MaxGCPauseMillis 设置垃圾收集器的最大停顿时间(STW时间),单位为毫秒,但是相对应的,垃圾回收器在工作时会调整堆大小或者一些其他参数
-XX:GCTimeRatio 垃圾收集时间占总时间的比例(1/(N+1))
-XX:+UseAdaptiveSizePolicy 设置Parallel Scavenge收集器具有自适应策略

(4)CMS回收器:低延迟

1)CMS的垃圾收集算法采用标记-清除算法,STW机制
2)工作原理
  • A、初始标记
    此阶段,所有工作线程都会STW,主要在这个阶段标记出GC roots能直接关联的对象,而不是整个引用连。一旦完成之后,立即恢复用户线程
    B、并发标记
    此阶段,不会发生STW,从GC Roots直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不会STW,可以与垃圾线程一起并发运行。
  • C、重新标记过程
    此阶段时为了修正并发标记阶段,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段会STW,会比初始标记时间长,比并发标记短
  • D、并发清除
    此阶段清除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,这个阶段可以与用户线程并发执行。
3)缺点

由于CMS采用的是标记清除算法,不可避免的出现内存碎片,所以需要空闲列表执行内存分配。同时有可能用户线程可用空间不足的时候,无法分配大对象空间,不得不提前触发Full GC。
CMS对CPU资源非常敏感。由于是与用户线程并发执行,会占用一部分线程导致应用程序变慢,降低总吞吐量。
CMS无法处理浮动垃圾。可能出现Concurrent Mode Failure 而导致Full GC出现。并发过程,如果产生新的垃圾,CMS无法对这些垃圾对象进行标记,最终会导致新产生得垃圾对象没有及时被回收,只能下一次执行GC的时候回收这些空间。

4)为何不使用标记-压缩算法

由于标记压缩算法是STW,无法与用户线程并发执行。要保证并发执行,前提是用户线程运行的资源不受影响

(5)G1回收器:区域分代化

在延迟可控得情况下获得尽可能高得吞吐量,称之为全功能收集器。

1)区域分代化

G1有计划的避免在整个堆中进行全区域垃圾回收。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间以及所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。这种方式的侧重点在于回收垃圾最大区间

2)特点

G1采用的是分区算法
并行与并发
并行:垃圾回收期间,可以有多个GC线程同时在工作,有效利用多核计算能力,此时STW
并发:G1拥有与应用程序交替执行的能力,部分工作可以与应用程序同时进行。一般来说,不会整个回收阶段发生完全阻塞应用程序的情况

  • 分代收集
    从分代上看,G1仍然属于分代垃圾回收器,区分年轻代与老年代。但是从整个堆上来看,不要求整个Eden区。年轻代、老年代是连续的,也不再固定大小与固定数量。
    将堆空间分为若干个区域,这些区域包含了逻辑上的年轻代与老年代。
  • 空间整合
    G1将内存分为一个一个Region,内存的回收是以Region为基本单位的。Region之间是采用复制算法,但整体上可以看作是标记压缩算法,两种算法都可以避免内存碎片。
    可预测的停顿时间模型
    可以让使用者明确指定一个长度为M毫秒的时间片段内,消耗垃圾收集上的时间不超过N毫秒。
    由于分区的原因,G1只能Region回收,缩小了回收的范围,对于全局停顿的情况也能得到了好的控制。
    G1跟踪每个Region的垃圾堆积的价值大小,后台维护一个优先列表,每次根据允许收集时间,优先回收价值最大的Region,保证G1收集器在有限的时间内可以获取尽可能高的收集效率。
    相比于CMS,G1未必能做到CMS最好的情况下的停顿时间,但是 最差情况要好很多。
3)缺点

在应用程序运行过程中,G1无论是为了垃圾收集产生的内存占用,还是程序运行时的额外执行负载,都要比CMS高。因此,小内存的应用上大概率CMS>G1;G1在大内存应用上则发挥其优势,平衡点在6~8G之间

4)相关参数
-XX:+UseG1GC 手动指定使用G1垃圾收集器
-XX:G1HeapRegionSize 设置每个Region的大小,值是2的次幂,范围是1M到32M之间。
-XX:MaxGCPauseMillis 设置期望达到的最大的GC停顿时间,默认是200毫秒。
-XX:ConcGCThreads 设置并发标记的线程数。
5)调优三步骤

第一步:开启G1
第二步:设置堆的最大内存
第三步:设置最大的停顿时间
G1提供了三种垃圾回收模式:YangGC、Mixed GC、Full GC,不同条件下触发

6)使用场景

面向服务端应用,具有大内存,多处理器的机器
最主要应用需要低延迟,并具有大堆的应用程序
下面的情况,G1可能比CMS好
超过50%Java堆被活动数据占用
对象分配频率或年代提升频率变化很大
GC停顿时间过长

7)G1的化整为零

每个Region只可能属于一个角色(Eden、Survivor、Old、Humongous用来存放大对象)

8)垃圾回收过程

G1 GC垃圾回收过程主要包括以下三个环节
年轻代GC Young GC、老年代并发标记过程、混合回收Mixed GC
当年轻代的Eden区用尽时开始年轻代的回收过程(独占式、STW),当堆内存使用达到一定的阈值(默认45%)时,开始老年代的并发标记过程。
标记完成马上开始混合回收。对于一个混合回收期,G1 GC从老年区移动存活对象到空闲区间,这些空闲区间就变成了老年代的一部分。G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代Region就可以。

9)Remember Set
一个对象被不同的Region引用的问题
一个Region不可能时孤立的,一个Region中的对象可能被其他任意Region中的对象引用,判断对象是否存活时,是否需要扫描整个Java堆才能保证准确
在其他的分代收集器中,也存在这样的问题,而G1更加突出
回收新生代也不得不同时扫描老年代?

对于上面问题的解决:

无论G1还是其他分代收集器,JVM都是使用Remember Set来避免全局扫描的
每个Region都有一个对应的Remember Set
每次Reference类型数据写操作时,都会产生一个Write Barrier暂时终端操作
然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region
如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remember Set中
当进行垃圾收集时,在GC节点的枚举范围加入RememberSet,可以保证不进行全局扫描,也不会有遗漏。
10)年轻代GC,Young GC
  • 第一阶段:扫描根
    根引用链同Remember Set记录的外部引用作为扫描存活对象的入口
  • 第二阶段:更新Remember Set
    处理Dirty Card Queue中的Car,更新Remember Set,此阶段后,Remember Set可以准确反映老年代对所在的内存分段中的对象的引用
  • 第三阶段:处理Remember Set
    识别被老年代对象指向Eden中的对象,这些被指向的Eden中的对象被认为是存活对象
  • 第四阶段:复制对象
    此阶段,对象树被遍历,并开始回收对象,使用复制的方式
  • 第五阶段:处理引用
    处理引用,最终Eden空间数据为空,GC停止工作,而且目标内存中的对象是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。
11)并发标记过程
  • 初始标记过程:标记从根节点直接可达的对象,这个阶段是STW的,并且会触发一次年轻代GC
  • 根区域扫描:G1 GC扫描Survivor区直接可达老年代的区域对象,并标记被引用的对象,这一过程必须在young GC 之前完成
  • 并发标记:在整个堆中进行并发标记(不会STW),此过程可能会被Young GC中断。若发现区域对象中所有对象都是垃圾,这个区域会被立即回收。同时,并发标记过程中,会计算每个区域对象的火星
  • 再次标记:由于应用程序持续进行,需要修正上一次的标记结果。是STW的,G1采用了比CMS更快的初始快照算法
  • 独占式清理:计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域,是STW的
  • 并发清理过程:识别并清理完全空闲的区域
12)混合回收
当越来越多的对象晋升到老年代时,未必秒堆内存被耗尽,虚拟机会触发一个混合的垃圾回收器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分老年代的Region,而不是全部老年代。可以选择哪些Old Region进行收集,从而可以对垃圾回收耗时时间进行控制,也要注意Mixed  GC 不是Full GC

并发标记结束后,老年代中百分百为垃圾的内存分段会被回收,部分为垃圾的内存分段被计算出来。默认情况下,这些老年代的内存分段会分八次

混合回收的会收集,保活八分之一的老年代内存分段。垃圾占内存分段的比例越高,约会被回收。并且有一个阈值会决定内存分段是否被回收:-XX:G1MixedGCLiveThresholdPercent,默认时65%。如果垃圾占比比较低,以为存活的对象比例高,复制的时候会花费更多的时间

混合回收不一定要进行8次。有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思就是允许整个堆内存中有10%空间浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不进行混合回收

21、jvm架构图

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值