一、JVM概述
1、1为什么要学习JVM
1、2虚拟机
1、3JVM作用
1、4JVM整体组成部分
二、JVM结构--类加载器
2、1类加载子系统
2、2类加载过程
2、2、1加载
2、2、2链接
2、2、3初始化
2、3类加载器分类
2.3.1 引导类加载器(启动类加载器 BootStrap ClassLoader)
2.3.2 扩展类加载器(Extension ClassLoader)
2.3.3 应用程序类加载器(系统类加载器 Application ClassLoader)
2.4 双亲委派机制
2.5 如何打破双亲委派机制
三、JVM运行时数据区
3、1运行时数据区组成概述
3.1.1 程序计数器(Program Counter Register)
3.1.2Java 虚拟机栈(Java Virtual Machine Stacks)
3.1.3 本地方法栈(Native Method Stack)
3.1.4Java 堆(Java Heap)
3.1.5 方法区(Methed Area)
3.2.程序计数器(Program Counter Register)
3.3.Java 虚拟机栈(Java Virtual Machine Stacks)
3.4.本地方法栈(Native Method Stack)
3.5.Java 堆内存
3.5.1Java堆内存概述
3.5.2堆内存区域划分
3.5.3 为什么分区(代)?
3.5.4 对象创建内存分配过程
3.5.5 新生区与老年区配置比例
3.5.6 分代收集思想 Minor GC、Major GC、Full GC
3.5.7 堆空间的参数设置
3.5.8 字符串常量池
3.6方法区
3.6.1 方法区的基本理解
3.6.2 方法区大小设置
3.6.3 方法区的内部结构
3.6.4 方法区的垃圾回收
四、本地方法接口
4.1 什么是本地方法
4.2 为什么要使用 Native Method
五、执行引擎
5.1 概述
5.2 什么是解释器?什么是 JIT 编译器?
5.3 为什么 Java 是半编译半解释型语言?
六、垃圾回收
6.1 垃圾回收概述
6.1.1 概述
6.1.2 什么是垃圾?
6.1.3 为什么需要 GC?
6.1.4 早期垃圾回收
6.1.5Java 垃圾回收机制
6.2 垃圾回收相关算法
6.2.1 垃圾标记阶段算法
6.2.2 垃圾回收阶段算法
6.3 垃圾回收相关概念
6.3.1System.gc() 的理解
6.3.2 内存溢出与内存泄漏
6.3.3Stop the World
6.4 垃圾回收器
6.4.1 垃圾回收器概述
6.4.2 垃圾回收器分类
6.4.3GC 性能指标
6.4.4HotSpot 垃圾收集器
6. 4.5CMS 回收器
6.4.6G1(Garbage First)回收器
6.4.7 查看 JVM 垃圾回收器设置垃圾回收器
中高程序员必备技能:
项目管理、性能调优
Java虚拟机负责装载字节码到其内部,解释/编译为对应平台上的机器码指令执行,每一条java指令,java虚拟机中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪。
特点:
现在的JVM不仅可以执行java字节码文件,还可以执行其他语言编译后的字节码文件,是一个跨语言平台。
程序在执行之前先要把 java 代码转换成字节码(class 文件),jvm 首先需要把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中的运行时数据区(Runtime Data Area) ,而字节码文件是 jvm 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由CPU 去执行,而这个过程中需要调用其他语言的接口 本地库接口(Native
Interface) 来实现整个程序的功能,这就是这 4 个主要组成部分的职责与功能。 而我们通常所说的 JVM 组成指的是运行时数据区(Runtime Data Area),因为通常需要程序员调试分析的区域就是“运行时数据区”,或者更具体的来说就是“运行时数据区”里面的 Heap(堆)模块。
类加载子系统负责从文件系统或者网络中加载class文件。classLoader只负责class文件的加载,至于它是否可以裕兴,则由Execution Engine决定。
加载的类信息存放于一块称为方法区的内存空间。
class file 存在于硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载 JVM 当中来,根据这个模板实例化出 n 个实例.
class file 加载到 JVM 中,被称为 DNA 元数据模板. 此过程就要有一个运输工具(类加载器 Class Loader),扮演一个快递员的角色
除了以上几种主动使用,以下情况被动使用,不会加载类:
类的初始化顺序
对static修饰的变量或语句块进行赋值
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
顺序是:父类 static –> 子类 static
public class ClassInit{ static{ num = 20; } static int num = 10; public static void main (String[] args) { //num从准备到初始化值变化过程 num=0 -> num=20 -> num=10 System.out.println(num);//10 } } public class ClassInit{ static int num = 10; static{ num = 20; } public static void main (String[] args) { //num从准备到初始化值变化过程 num=0 -> num=10 -> num=20 System.out.println(num);//20 } }
站在JVM的角度看,类加载器可以分为两种:
工作原理:
双亲委派优点:
如图:红色的为多个线程共享,灰色的为单个线程私有的,即线程间共享:堆,方法区. 线程私有:程序计数器,栈,本地方法栈.
- 它是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域.
- 在 JVM 规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程生命周期保持一致.
- 程序计数器会存储当前线程正在执行的 Java 方法的 JVM 指令地址.
- 它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成.
- 它是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
栈的运行原理栈中会出现异常,当线程请求的栈深度大于虚拟机所允许的深度时 , 会出现StackOverflowError.
不同线程中所包含的栈帧(方法)是不允许存在相互引用的,即不可能在一个栈中引用另一个线程的栈帧(方法).
如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧.
Java 方法有两种返回的方式,一种是正常的函数返回,使用 return 指令,另一种是抛出异常.不管哪种方式,都会导致栈帧被弹出.
将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及 GC 频率。 针对分类进行不同的垃圾回收算法,对算法扬长避短。
例如:
public static void main(String[] args) { List list = new ArrayList(); while(true){ list.add(new Random().nextInt()); } }
-XX:+PrintFlagsInitial | 查看所有参数的默认初始值 |
-XX:+PrintFlagsFinal | 查看所有参数的最终值(修改后的值) |
-Xms | 初始堆空间内存(默认为物理内存的 1/64) |
-Xmx | 最大堆空间内存(默认为物理内存的 1/4) |
-Xmn | 设置新生代的大小(初始值及最大值) |
-XX:NewRatio | 配置新生代与老年代在堆结构的占比 |
-XX:SurvivorRatio | 设置新生代中 Eden 和 S0/S1 空间比例 |
-XX:MaxTenuringTreshold | 设置新生代垃圾的最大年龄 |
XX:+PrintGCDetails | 输出详细的 GC 处理日志 |
public static void main(String[] args) { String temp = "world"; for (int i = 0; i < Integer.MAX_VALUE; i++) { String str = temp + temp; temp = str; str.intern();//将字符串存储到字符串常量池中 } }
方法区,是一个被线程共享的内存区域。其中主要存储加载的类字节码、class/method/field 等元数据、static final 常量、static 变量、即时编译器编译后的代码等数据。另外,方法区包含了一个特殊的区域“运行时常量池”。
Java 虚拟机规范中明确说明:”尽管所有的方法区在逻辑上是属于堆的一部分,但对HotSpotJVM 而言,方法区还有一个别名叫做 Non-Heap(非堆),目的就是要和堆分开. 所以,方法区看做是一块独立于 java 堆的内存空间. 方法区在 JVM 启动时被创建,并且它的实际的物理内存空间中和 Java 堆区一样都可以是不连续的.
方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展.
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出, 虚拟机同样会抛出内存溢出的错误
关闭 JVM 就会释放这个区域的内存.
Java 方法区的大小不必是固定的,JVM 可以根据应用的需要动态调整.
javap -v -p Demo.class > test.txt
下面也称作类卸载 判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了。需要同时满足下面三个条件:方法区的垃圾收集主要回收两部分内容:运行时常量池中废弃的常量和不再使用的类型。
MibBridge *pBridge= new cmBaseGroupBridge(); //如果注册失败,使用 Delete 释放该对象所占内存区域 if(pBridge->Register(kDestroy)!=NO ERROR) delete pBridge;
这种方式可以灵活控制内存释放的时间,但是会给开发人员带来频繁申请和释放内存的管理负担。倘若有一处内存区间由于程序员编码的问题忘记被回收,那么就会产生内存泄漏,垃圾对象永远无法被清除,随着系统运行时间的不断增长,垃圾对象所耗内存可能持续上升,直到出现内存溢出并造成应用程序崩溃。 有了垃圾回收机制后,上述代码极有可能变成这样 MibBridge *pBridge=new cmBaseGroupBridge(); pBridge->Register(kDestroy);
现在,除了 Java 以外,C#、Python、Ruby 等语言都使用了自动垃圾回收的思想,也是未来发展趋势,可以说这种自动化的内存分配和来及回收方式已经成为了现代开发语言必备的标准。垃圾收集器可以对年轻代回收,也可以对老年代回收,甚至是全栈和方法区的回收,其中,Java 堆是垃圾收集器的工作重点 从次数上讲: 频繁收集 Young 区 较少收集 Old 区 基本不收集元空间(方法区)
6.2.1.3 可达性分析算法 可达性分析算法:也可以称为根搜索算法、追踪性垃圾收集
GC Roots 可以是哪些元素?
protected void finalize() throws Throwable { }
永远不要主动调用某个对象的 finalize()方法,应该交给垃圾回收机制调用。理由包括下面三点: 6.2.1.5 生存还是死亡?
由于 finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态。当成功区分出内存中存活对象和死亡对象后,GC 接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。目前在 JVM 中比较常见的三种垃圾收集算法是:
6.2.2.1 标记-复制算法
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。在垃 圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除 正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
复制算法的优缺点
优点
复制算法的应用场景
6.2.2.2 标记-清除算法
执行过程
当堆中的有效内存空间被耗尽的时候,然后进行这项工作.
清除:这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放(也就是覆盖原有的地址)
标记-清除算法的优点:
非常基础和常见的垃圾收集算法容易理解
标记-清除算法的缺点:
标记清除算法的效率不算高
这种方式清理出来的空闲内存是不连续的,产生内碎片。
6.2.2.3 标记-压缩算法
背景
复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在新生代经常发生,但是在老年代,更常见的情况是大部分对象都是存活对象。 如果依然使用复制算法,由于存活对象较多,复制的成本也将很高。因此,基于老年代垃圾回收的特性,需要使用其他的算法。
标记-清除算法的确可以应用在老年代中,但是该算法不仅执行效率低下,而且在执行完内存回收后还会产生内存碎片,所以 JVM 的设计者需要在此基础之上进行改进
标记压缩算法执行过程
第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象
第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。
标记-压缩算法与标记-清除算法的比较
标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法。
二者的本质差异在于标记-清除算法是一种非移动式的回收算法(空闲列表记录位置),标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。
可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时JVM 只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。
标记-压缩算法的优缺点
优点
6.2.2.4 垃圾回收算法小结
效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存。而为了尽量兼顾上面提到的三个指标,标记-压缩算法相对来说更平滑一些,但是效率上不尽如人意,它比复制算法多了一个标记的阶段,比标记-清除多了一个整理内存的阶段。
标记清除 | 标记整理 | 复制 | |
速率 | 中等 | 最慢 | 最快 |
空间开销 | 少(会堆积碎片) | 少(无堆积碎片) | 通常需要活动对象的两倍空间(无堆积碎片) |
移动对象 | 否 | 是 | 是 |
6.2.2.5 分代收集
为什么要使用分代收集
前面所有这些算法中,并没有一种算法可以完全替代其他算法,它们都具有自己独特的优势和特点。分代收集应运而生。
分代收集,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。一般是把 Java堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法,以提高垃圾回收的效率。
在 Java 程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关: 比如 Http 请求中的 Session 对象、线程、Socket 连接,这类对象跟业务直接挂钩,因此生命周期比较长。 但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String 对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。
目前几乎所有的 GC 都采用分代手机算法执行垃圾回收的在 HotSpot 中,基于分代的概念,GC 所使用的内存回收算法必须结合年轻代和老年代各自的特点。
年轻代(Young Gen)
年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。
这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因此很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过 hotspot 中的两个 survivor 的设计得到缓解。
老年代(Tenured Gen)
老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。
这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记- 清除或者是标记-清除与标记-压缩的混合实现。
分代的思想被现有的虚拟机广泛使用。几乎所有的垃圾回收器都区分新生代和老年代。
在默认情况下,通过 System.gc()者 Runtime.getRuntime().gc() 的调用,会显式触发 Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
然而 System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用(不能确保立即生效)。
JVM 实现者可以通过 System.gc() 调用来决定 JVM 的 GC 行为。而一般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太过于麻烦了。在一些特殊情况下,我们可以在运行之间调用 System.gc()。
内存溢出
内存溢出相对于内存泄漏来说,尽管更容易被理解,但是同样的,内存溢出也是引发程序崩溃的罪魁祸首之一。
由于 GC 一直在发展,所有一般情况下,除非应用程序占用的内存增长速度非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现 OOM 的情况。
大多数情况下,GC 会进行各种年龄段的垃圾回收,实在不行了就放大招,来一次独占式的 Full GC 操作,这时候会回收大量的内存,供应用程序继续使用。
Javadoc 中对 OutofMemoryError 的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存。
内存泄漏
内存泄漏也称作“存储渗漏”。严格来说,只有对象不会再被程序用到了,但是 GC 又不能回收他们的情况,才叫内存泄漏。
但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致 OOM,也可以叫做宽泛意义上的“内存泄漏”。
尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会被逐步蚕食,直至耗尽所有内存,最终出现 OutofMemory 异常,导致程序崩溃。注意,这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。
注意,这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。
常见例子
单例模式
单例的生命周期和应用程序是一样长的,所以在单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生。
一些提供 close()的资源未关闭导致内存泄漏
数据库连接 dataSourse.getConnection(),网络连接 socket 和 io 连接必须手动 close,否则是不能被回收的。
Stop-the-World,简称 STW,指的是 GC 事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为 STW。
可达性分析算法中枚举根节点(GC Roots)会导致所有 Java 执行线程停顿,为什么需要停顿所有 Java 执行线程呢?
STW 是 JVM 在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。
如果说垃圾收集算法是内存回收的方法论,那么收集器就是内存回收的实践者.
垃圾收集器没有在 java 虚拟机规范中进行过多的规定,可以由不同的厂商、不同版本的 JVM 来实现。
由于 JDK 的版本处于高速迭代过程中,因此 Java 发展至今已经衍生了众多的垃圾回收器。从不同角度分析垃圾收集器,可以将 GC 分为不同的类型。
实际使用时,可以根据实际的使用场景选择不同的垃圾回收器,这也是 JVM 调优的重要部
按线程数可以分为单线程(串行)垃圾回收器和多线程(并行)垃圾回收器
单线程垃圾回收器(Serial)
只有一个线程进行垃圾回收,使用于小型简单的使用场景,垃圾回收时,其他用户线程会暂停.
多线程垃圾回收器(Parallel) 多线程垃圾回收器内部提供多个线程进行垃圾回收,在多 cpu 情况下大大提升垃 圾回收效率,但同样也是会暂停其他用户线程.
按照工作模式分,可以分为独占式和并发式垃圾回收器。
按工作的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器。
图中展示了 7 种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。
CMS 概述
CMS(Concurrent Mark Sweep,并发标记清除)收集器是以获取最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和 GC 线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。
垃圾回收过程
初始标记:
Stop The World,仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记。
并发标记:
垃圾回收线程,与用户线程并发执行。此过程进行可达性分析,标记出所有废弃对象。
重新标记:
Stop The World,使用多条标记线程并发执行,将刚才并发标记过程中新出现的废弃对象标记出来。
并发清除:
只使用一条 GC 线程,与用户线程并发执行,清除刚才标记的对象。 这个过程非常耗时。
并发标记与并发清除过程耗时最长,且可以与用户线程一起工作,因此,总体上 说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。
CMS 的优点:
可以作到并发收集
CMS 的弊端:
三色标记算法
为了提高 JVM 垃圾回收的性能,从 CMS 垃圾收集器开始,引入了并发标记的概念。引入并发标记的过程就会带来一个问题,在业务执行的过程中,会对现有的引用关系链出现改变。
三色标记法将对象的颜色分为了黑、灰、白,三种颜色。三色标记的过程:
为了解决并发的问题,引入中间状态(灰色),当一个对象被标记的时候,会有下面几个过程:
这个过程正确执行的前提是没有其他线程改变对象间的引用关系,然而,并发标记的过程中,用户线程仍在运行,因此就会产生漏标和错标的情况。
漏标
假设 GC 已经在遍历对象 B 了,而此时用户线程执行了 A.B=null 的操作,切断了 A 到 B 的引用
B 到 D 的引用被切断,且 A 到 D 的引用被建立。 此时 GC 线程继续工作,由于 B 不再引用 D 了,尽管 A 又引用了 D,但是因为 A 已经标记为黑色,GC 不会再遍历 A 了,所以 D 会被标记为白色,最后被当做垃圾回收。 可以看到错标的结果比漏表严重的多,浮动垃圾可以下次 GC 清理,而把不该回收的对象回收掉,将会造成程序运行错误。解决错标的问题 错标只有在满足下面两种情况下才会发生:
只要打破任一条件,就可以解决错标的问题。
原始快照和增量更新
原始快照打破的是第一个条件:当灰色对象指向白色对象的引用被断开时,就将这条引用关系记录下来。当扫描结束后,再以这些灰色对象为根,重新扫描一次。
增量更新打破的是第二个条件:当黑色指向白色的引用被建立时,就将这个新的引用关系记录下来,等扫描结束后,再以这些记录中的黑色对象为根,重新扫描一次。相当于黑色对象一旦建立了指向白色对象的引用,就会变为灰色对象。
总结
CMS 为了让 GC 线程和用户线程一起工作,回收的算法和过程比以前旧的收集器要复杂很多。究其原因,就是因为 GC 标记对象的同时,用户线程还在修改对象的引用关系。因此 CMS 引入了三色算法,将对象标记为黑、灰、白三种颜色的对象,将用户线程修改的引用关系记录下来,以便在「重新标记」阶段可以修正对象的引用。
虽然 CMS 从来没有被 JDK 当做默认的垃圾收集器,存在很多的缺点,但是它开启了「GC 并发收集」的先河,为后面的收集器提供了思路。
既然我们已经有了前面几个强大的 GC,为什么还要发布 Garbage First(G1)GC?
原因就在于应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有GC 就不能保证应用程序正常进行,而经常造成 STW 的 GC 又跟不上实际的需求,所以才会不断地尝试对 GC 进行优化。G1(Garbage-First)垃圾回收器是在 Java7 update 4 之后引入的一个新的垃圾回收器,是当今收集器技术发展的最前沿成果之一.
与此同时,为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time),同时兼顾良好的吞吐量。
官方给 G1 设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担当起“全功能收集器”的重任与期望。
G1 是一款面向服务端应用的垃圾收集器。
为什么名字叫做 Garbage First(G1)呢?
因为 G1 是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的逻辑上连续的)。使用不同的 Region 来表示 Eden、幸存者0 区,幸存者 1 区,老年代等。
G1 GC 有计划地避免在整个 Java 堆中进行全区域的垃圾收集。G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region.
由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给 G1 一个名字:垃圾优先(Garbage First)。
G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备多核 CPU 及大容量内存的机器,以极高概率满足 GC 停顿时间的同时,还兼具高吞吐量的性能特征。
如下图所示,G1 收集器收集器收集过程有初始标记、并发标记、最终标记、筛选回收,和 CMS 收集器前几步的收集过程很相似:
适用场景:要求尽可能可控 GC 停顿时间;内存占用较大的应用。
打印默认垃圾回收器
-XX:+PrintCommandLineFlags -version
JDK 8 默认的垃圾回收器
年轻代使用 Parallel Scavenge GC
老年代使用 Parallel Old GC
打印垃圾回收详细信息
-XX:+PrintGCDetails -version
设置默认垃圾回收器
Serial 回收器
-XX:+UseSerialGC 年轻代使用 Serial GC, 老年代使用 Serial Old GC
ParNew 回收器
-XX:+UseParNewGC 年轻代使用 ParNew GC,不影响老年代。
CMS 回收器
-XX:+UseConcMarkSweepGC 老年代使用 CMS GC。
# G1 回收器
-XX:+UseG1GC 手动指定使用 G1 收集器执行内存回收任务。 -XX:G1HeapRegionSize 设置每个 Region 的大小。
上一篇:[Qt的学习日常]--窗口