一、对象是生是死
- Java堆几乎存放着所有的对象实例,垃圾回收器回收垃圾前,需要判断哪些是存活的哪些是死去的。
- 引用计数器算法:给对象添加一个引用计数器,当持有这个对象引用时,则计数器加一;当引用失效时,则计数器减一;当计数器为0的时候表示对象没有任何引用。这种算法不适合Java的垃圾回收器,因为无法解决对象间的互相引用。
- 可达性算法:Java定义GC Root,作为引用链(Reference Chain)的依据,并指定哪些可作为GC Root如
java虚拟机栈中的局部变量表引用的对象、方法区的静态变量引用的对象、方法区的常量引用的对象、本地方法栈(Native方法)引用的对象、类加载器、Thread
。但仅仅定义引用和未引用太过于狭隘,因此在jdk1.2以后将引用划分为四种,分别是强引用(强引用类似Object obj = new Object(),如果obj到GC Root可达则JVM永不会回收这对象
)、软引用(描述一些不是特别重要的对象,在JVM即将发生OOM异常时,对这些对象列入回收范围进行第二次回收,如果还是内存不足则会抛出OOM异常)、弱引用(描述哪些不重要的对象,相比于软引用,这些对象只会存活到下一次垃圾回收之前, eg:Map中存放key/value,如果某个key程序再也用不到,此时使用弱引用后将key=null,这样这个key就会被回收)和虚引用(一个对象是否有虚引用完全不会对它的生命周期产生影响,我们无法通过虚引用获取对象,为对象设置虚引用的目的在于对象被垃圾收集器回收时会收到一个系统的通知,java中直接内存的使用就是虚引用) - 对象的生命周期:
(1)对象创建:类加载 + 实例初始化
(2)对象可用:对象创建后,至少有一个强引用指向这个对象
(3)对象不可见:程序没有任何地方持有这个对象的强引用,但是又被jvm中某个GC root强引用了,这样的容易内存溢出
(4)对象不可达:没有任何GC Root可达这个对象
(5)垃圾收集:对象有没有重写finialize方法,如果重写了,那么会放进F-Queue队列中等待执行,且只会被执行一次,就是finialize方法如果重新被强引用,则不会被回收。下一次GC时候,不会再调用这个方法了;如果没重写,则进入垃圾回收准备阶段。 ( JVM不建议使用这个方法)
(6)终结阶段:在finialize方法执行完后还是不可达,则垃圾收集器开始等待进行垃圾回收
(7)对象内存空间被回收:垃圾收集器对内存空间回收或再分配,不在有这个对象了
二、垃圾收集算法
- 标记-清除算法:
标记
:堆中对象扫描一次清除
:清除标记的对象,释放内存空间。
缺点:两步效率都不高,而且回收后内存碎片过多,再分配大对象时会引发下一次GC - 复制算法:
将内存划分为相等的两块,每次只使用一块,当满了的时候将存活的对象复制到另一块内存上,并清除原来的。复制算法适用于young区,IBM公司调查研究对象在堆中98%的情况下都是“朝生夕死”,所以将young区分为Eden区和两个Survirvor区,并且让Old区进行分配担保。 - 标记-整理算法:
标记
:标记可回收的对象整理
:将存活的对象向一端移动,再清理端以外的内存
标记-整理算法一般适用于老年代 - 分代收集算法:
将堆内存划分为:young区和old区
young区对象朝生夕死只有少量存活适合复制算法,只需花很少的代价就能回收垃圾;
old区对象存活率高,也没有分配担保所以适合标记算法
三、垃圾收集器
-
垃圾收集器的分类
-
Serial收集器
发展历史最悠久、最基本的垃圾收集器,它是一个单线程的收集器,不仅仅是使用一个CPU或者一个线程去完成垃圾收集工作,重要的是它在垃圾收集时会暂停用户线程(Stop The World)。优点:简单高效,拥有很高的单线程收集效率 缺点:收集过程需要暂停所有线程 算法:复制算法 适用范围:新生代 应用:Client模式下的默认新生代收集器
-
ParNew收集器
Serial GC的多线程版本,单核CPU不建议使用。优点:在多CPU时,比Serial效率高。 缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差。 算法:复制算法 适用范围:新生代 应用:运行在Server模式下的虚拟机中首选的新生代收集器
-
Parallel Scavenge收集器
相比于ParNew,Parallel Scavenge也是多线程,但不同于CMS关注于GC停顿时间(与用户交互体验更好),Parallel Scavenge更关注吞吐量(程序执行时间/(程序执行时间 + GC时间),高效利用CPU,比如定时任务)-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间 -XX:GCRatio直接设置吞吐量的大小
-
CMS(concurrent mark sweep)收集器
CMS诞生的目标就是为了获取最短的回收停顿时间,希望系统停顿时间最短从而带给用户良好的体验。它分为4步1、初始标记 CMS initial mark 标记GC Root关联的对象并stop the world 2、并发标记 CMS concurrent mark 不会停止用户线程 3、重新标记 CMS remark 修改并发标记过程用户线程变更的内容并 stop the world 4、并发清除 CMS concurrent sweep
优点:并发收集、低停顿 缺点:产生大量空间碎片(CMS采用标记-清除算法)、并发阶段会降低吞吐量(占用 CPU +3 / 4 的线程数量,如果cpu数量小于4则占用资源太多)
-
Serial Old收集器
Serial收集器的老年代版本,单线程收集器,不同的是采用标记-整理算法。 -
Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和"标记-整理算法"进行垃圾回收。同样专注于吞吐量 -
G1(Garbage First)收集器
G1是一个区分其他垃圾收集器的重要产品,都知道堆内存被划分为Eden、Survivor和old,G1虽然也把内存分成了这三大类,但是在G1里面这三大类不是泾渭分明的三大块内存,G1把内存划分成很多小块(几百个region块内存压缩时候代价小), 每个小块会被标记为E/S/O中的一个,可以前面一个是Eden后面一个就变成Survivor了。设计思想类似ConcurrentHashMap,相比于CMS,减少了空间碎片化,CMS如果碎片过多当有稍大点对象要分配时候 就会Full GC。(jdk9默认使用G1)初始标记(Initial Marking) 标记一下GC Roots能够关联的对象,并且修改TAMS的值,需要暂 停用户线程 并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发 执行最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程 筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,相比于几块大内存,分成几百个region块高度并行回收,使得用户能设置GC停顿时间制定回收计划
优点:它在筛选回收内存的时候选择性的回收region块,而不是整个内存的收集,因此用户可以配置暂停时间,时间小就少回收点,时间大就多回收点。
缺点:内存太小的时候不利于G1收集器,回收不彻底,造成Full GC,而且由于G1的收集算法比较复杂,性能不如其他的收集器,所以G1比较适合内存在4G以上的应用
三、垃圾收集器的分类
-
串行收集器Serial和Serial Old:
只能有一个垃圾回收线程执行,用户线程暂停。 适用于内存比较小的嵌入式设备 。 -
并行收集器[吞吐量优先] Parallel Scanvenge、Parallel Old:
多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 适用于科学计算、后台处理等非交互场景 。 -
并发收集器[停顿时间优先] CMS、G1:
用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。 适用于相对时间有要求的交互场景,比如Web 。 -
理解吞吐量和停顿时间
停顿时间:垃圾收集器进行垃圾回收终端应用执行响应的时间
吞吐量:运行用户代码时间/(运行用户代码时间+GC时间) -
如何选择合适的垃圾回收器:
官网1.优先调整堆的大小让服务器自己来选择 2.如果内存小于100M,使用串行收集器 3.如果是单核,并且没有停顿时间要求,使用串行或JVM自己选 4.如果允许停顿时间超过1秒,选择并行或JVM自己选 5.如果响应时间最重要,并且不能超过1秒,使用并发收集器
判断是否需要使用G1收集器?
(1)50%以上的堆被存活对象占用
(2)对象分配和晋升的速度变化非常大
(3)垃圾回收时间比较长 -
如何开启不同的垃圾收集器
(1)串行 -XX:+UseSerialGC -XX:+UseSerialOldGC (2)并行(吞吐量优先): -XX:+UseParallelGC -XX:+UseParallelOldGC (3)并发收集器(响应时间优先) -XX:+UseConcMarkSweepGC -XX:+UseG1G咕
来源:CSDN
作者:路人是妖怪
链接:https://blog.csdn.net/qq_41301079/article/details/103557847