JVM03--JVM垃圾收集机制的一些基本概念

简介: 今天来学习下与JVM垃圾收集机制相关的一些基本概念。

前言

今天来学习下与JVM垃圾收集机制相关的一些基本概念。

如何判断对象是否存活

垃圾收集器首要的任务的任务就是判断哪些对象是存活的,哪些对象已经死去了(这里死去的意思是对象不再被任何途径使用)。

引用计数算法

引用计数算法是在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能被使用的对象。

引用计数算法的缺点就是很难解决对象之间相互循环引用的问题。

可达性分析算法

通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索的过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象不可能再被使用的。例如:通过:Objcet 5,Object 6已经Object 7三个对象,虽然互有关联,但是他们到GC Roots是不可达的,因此可以判定为是可以回收的对象。

固定的可作为GC Roots对象包括如下几种:

1.在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程调用的方法堆栈中使用到的参数,局部变量、临时变量等

2.在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量

3.在方法区中常量引用的对象,譬如字符串常量池里的引用

4.在本地方法栈中JNI(即通常所说的Native方法)引用的对象

Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointException、OutOfMemoryError)等,还有系统类加载器。

5.所有被同步锁(synchronized关键字)持有的对象

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

引用的分类

引用分为如下四种:

1.强引用: 强引用是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象

2.软引用: 软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会被这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。

3.弱引用: 弱引用也是用来描述那些非必须对象,但是他的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

4.虚引用: 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

回收方法区

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。回收废弃常量

与回收Java堆中的对象非常类似。同时判断一个常量是否废弃还是相对简单,而要判断一个类型是否属于“不再被使用的类”的条件就比较苛刻了。需要同时满足下面三个条件。

1.该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。

2.加载该类的类加载器已经被回收,这个通常很难达成

3.该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

分代收集理论

分代收集理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上。

1.弱分代假说:绝大多数对象都是朝生夕灭的。

2.强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡

收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。显而易见,如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把他们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象。就能以较低代价回收到大量的空间;如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域。

新生代:每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象,

将会逐步晋升到老年代中存放。

老年代:老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。

3.跨代引用假说:跨代引用相对于同代引用来说仅占极少数

只需要在新生代上建立一个全局的数据结构(该结构被称为“记忆集”)这个结构把老年代划分为若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots 进行扫描。

收集说明

部分收集(Partial GC):

指目标不是完整收集整个Java堆的垃圾收集。其中又分为:

1.新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。

Minor GC触发条件是:

1. Eden区域满了,或者新创建的对象大小>Eden所剩空间
2.  CMS设置了CMSScavengeBeforeRemark参数,这样在CMS的Remark之前会先做一次Minor GC来清理新生代,加速之后的Remark的速度,这样整体的stop-the-world的时间反而短
3. Full GC的时候也会先触发Minor GC。执行Minor GC需要注意:
  A:当JVM无法为一个新的对象分配空间时会触发Minor GC,比如当Eden区满了,所以分配频率越高,越频繁执行Minor GC。

2.老年代收集(Major GC/Old GC): 指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。

3.混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。

整堆收集(Full GC):

收集整个Java堆和方法区的垃圾收集,包括新生代、老年代和永久代。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对Full GC的调节,有如下原因可能导致Full GC:

1.当老年代无法分配内存的时候,会导致Minor GC, 当发生Minor GC的时候可能会触发Full GC,由于老年代要对新生代进行担保,由于进行一次垃圾回收之前无法确定有多少对象存活,因此老年代并不清楚自己要担保多少空间,因此采取用动态估算的方法:也就是上一次回收发送是晋升到老年代的对象容量的平均值作为经验值,这样就会有一个问题,当发生一次Minor GC以后,存活的对象剧增(假设小对象),此时老年代并没有满,但是此时平均值增加了,会造成发生Full GC。

2.System.gc()被显示调用。

面试题

1. Java中什么样的对象才能作为GC Roots,GC Roots有哪些呢?

GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收,方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。

GC Root

常说的GC roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC Roots且没有被GC roots引用的对象。

一个对象可以属于多个Root,GC Root有如下几种:

1.Class - 由系统类加载器(system class loader)加载的对象,这些类是不能被回收的。他们可以以静态字段的方式保存持有其它对象,我们需要注意的一点就是,通过用户自定义的类加载器的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots。

2.Thread — 活着的线程

3.Stack Local - Java方法的local变量或参数

4.JNI Local - JNI方法的local变量或参数

5.JNI Global - 全局JNI引用

6.Monitor Used - 用于同步的监控对象。

7.内存池/线程对象和线程快照对象

8.String常量池

2. 什么时候会触发Full GC

当对象经历15次Minor GC后,在第16次Minor GC后,如果对象还存活则会被复制到老年代,在复制之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代,执行这个判断的原因是,JVM无法知道哪些对象是存活的,所以又相对粗浅的判断。

如果老年代有足够的连续的内存空间,则直接Minor GC,如果没有足够的空间,那么有两种选择,第一种,直接Minor GC,搏一搏,说不定空间还够,第二种,先对老年代进行Full GC,腾出更多的空间用来Minor GC,至于执行哪一种操作,关键看HandlePromotionFailure设置值是否允许担保失败。

如果不允许担保失败的话,就会Full GC之后再进行复制,如果允许担保失败。还不会直接Minor GC,还会在进行一次评估,会继续检测老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。如果大于的话,说明直接Minor GC不出错的可能性大一些。

通常情况下,还是会设置HandlePromotionFailure=true,Full GC能少一次就少一次。

以下是逻辑总结:

发生Minor GC之前,虚拟机会检测:老年代最大可用的连续空间>新生代all 对象总空间?

9.满足,Minor GC是安全的,可以进行Minor GC

10.不满足,虚拟机查看HandlePromotionFailure参数:

(1)为true,允许担保失败,会继续检测老年代最大可用的连续空间>历次晋升到老年代对象的平均大小,若大于,将尝试进行一次Minor GC,若失败,则重新进行一次Full GC。

(2)为false,则不允许冒险,要进行Full GC(对老年代进行GC)。

参考

《深入理解Java虚拟机_JVM高级特性与最佳实践》

相关文章
|
5月前
|
安全 前端开发 Java
【JVM的秘密揭秘】深入理解类加载器与双亲委派机制的奥秘!
【8月更文挑战第25天】在Java技术栈中,深入理解JVM类加载机制及其双亲委派模型是至关重要的。JVM类加载器作为运行时系统的关键组件,负责将字节码文件加载至内存并转换为可执行的数据结构。其采用层级结构,包括引导、扩展、应用及用户自定义类加载器,通过双亲委派机制协同工作,确保Java核心库的安全性与稳定性。本文通过解析类加载器的分类、双亲委派机制原理及示例代码,帮助读者全面掌握这一核心概念,为开发更安全高效的Java应用程序奠定基础。
99 0
|
3月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
68 2
|
3月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
86 3
|
4月前
|
Arthas Java 测试技术
JVM —— 类加载器的分类,双亲委派机制
类加载器的分类,双亲委派机制:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器;JDK8及之前的版本,JDK9之后的版本;什么是双亲委派模型,双亲委派模型的作用,如何打破双亲委派机制
JVM —— 类加载器的分类,双亲委派机制
|
5月前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
5月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
37 3
|
5月前
|
C# UED 开发者
WPF打印功能实现秘籍:从页面到纸张,带你玩转WPF打印技术大揭秘!
【8月更文挑战第31天】在WPF应用开发中,打印功能至关重要,不仅能提升用户体验,还增强了应用的实用性。本文介绍WPF打印的基础概念与实现方法,涵盖页面元素打印、打印机设置及打印预览。通过具体案例,展示了如何利用`PrintDialog`和`PrintDocument`控件添加打印支持,并使用`PrinterSettings`类进行配置,最后通过`PrintPreviewWindow`实现打印预览功能。
534 0
|
5月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
270 0
|
5月前
|
开发者 C# Windows
WPF布局大揭秘:掌握布局技巧,轻松创建响应式用户界面,让你的应用程序更上一层楼!
【8月更文挑战第31天】在现代软件开发中,响应式用户界面至关重要。WPF(Windows Presentation Foundation)作为.NET框架的一部分,提供了丰富的布局控件和机制,便于创建可自动调整的UI。本文介绍WPF布局的基础概念与实现方法,包括`StackPanel`、`DockPanel`、`Grid`等控件的使用,并通过示例代码展示如何构建响应式布局。了解这些技巧有助于开发者优化用户体验,适应不同设备和屏幕尺寸。
144 0
|
5月前
|
算法 Java 程序员
【JVM的秘密花园】揭秘垃圾收集器的神秘面纱!
【8月更文挑战第25天】在Java虚拟机(JVM)中,垃圾收集(GC)自动管理内存,回收未使用的对象以避免内存泄漏和性能下降。本文深入介绍了JVM中的GC算法,包括串行、并行、CMS及G1等类型及其工作原理。选择合适的GC策略至关重要:小型应用适合串行收集器;大型应用或多核CPU环境推荐并行收集器或CMS;需减少停顿时间时,CMS是好选择;G1适用于大堆且对停顿时间敏感的应用。理解这些能帮助开发者优化程序性能和稳定性。
40 0