一.可回收对象的判定方法
1. 引用计数算法
基本原理:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
引用计数算法的缺陷:循环引用
Objective-C ARC的循环引用解决方案:在每一个对象的引用之中明确的加上一些关键字,比如Strong、Week来指定不同的引用类型,在Objective-C中,假如一个类型为Strong的引用,这个被引用的对象它的计数器会加1,但如果一个类型为Week的引用,被引用对象的计数器则并不会增加,我们只需要保证循环引用中其中一方的引用为Week,就可以打破这种循环,让系统可以自动回收掉循环引用的两个对象。
由于Java语言在语言层面上,没有直接对循环引用提供这样的支持,所以Java虚拟机的实现也一般不会考虑使用引用计数算法来进行内存管理。
2.可达性分析算法
在Java虚拟机中,一般用可达性分析算法来进行内存管理。
基本原理:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始进行向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
Java语言中的GC Roots:
a.在虚拟机栈(栈帧中的本地变量表)中的引用的对象
b.在方法区中的类静态属性引用的对象
c.在方法区中的常量引用的对象
d.在本地方法栈中JNI(即一般说的Native方法)的引用的对象
二.垃圾收集算法
1.标记-清除算法
基本原理:算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
它是最基础的垃圾收集算法。
主要缺陷:标记清除之后会产生大量不连续的内存碎片。另外标记和清除的效率都不是太高。
2.复制算法
基本原理:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
主要缺陷:将内存缩小为了原来的一半。
新生代的垃圾回收一般都采用复制算法,新生代的对象98%都是朝生夕死的,不过不是以1:1的比例来划分内存,而是将内存划分为一块较大的使用空间、两块较小的拯救空间,在JVM运作时,将会使用可用空间和其中一块拯救空间的内存,当发生内存回收时,将可用空间和拯救空间中仍然存活的对象一次性复制到另一块备用的拯救空间中,同时回收掉可用空间和拯救空间的内存。HotSpot默认可用空间和拯救空间的比例是8:1,也就是说在虛拟机工作时,可以使用整个新生代容量的90%作为可用内存,仅有10%的内存损失。当然,在一些极端情况下,并没有办法保证在每一次内存回收中存活下来的对象所占用的内存空间都不多于10%,因此,假如当剩下的一块拯救空间不够用时,需要依赖其它的内存,比如老年代来进行空间分配担保,让这些无法存入拯救空间的对象直接进入老年代。
复制算法主要用于解决标记清理算法之中效率不高的问题。
3.标记-整理算法
标记-整理算法主要用于解决标记清理算法之中产生空间碎片的问题。另外,复制算法在对象存活率很高的情况下需要进行较多的对象复制操作,这样收集效率也会下降,更关键的是如果不想浪费一半的内存空间,使用复制算法时就必须用额外的内存空间对它进行空间分配的担保来应对被使用内存中大量对象存活的极端情况,所以当没有另外的空间可以对被回收空间进行分配担保时,一般不会采用复制算法。在JVM中堆内存中的老年代就很符合这种特点,因此标记-整理算法是老年代应用的最多的算法。
基本原理:标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
主要缺陷:相比标记清理算法对系统的停顿时间会更长,因为无论是在标记的阶段还是在整理的阶段,都必须停止JVM的执行线程,对一个静止的JVM堆内存的快照来进行收集。
4.分代收集算法
基本原理:根据对象的存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
分代收集算法是当代商用VM都采用的垃圾收集算法。根据对象存活周期的不同,把JVM的内存区域分为不同的块,根据不同代中JAVA对象存活的时间选择最恰当的算法,如在新生代中每次垃圾收集都有大批量的对象死去,只有少量的对象存活,适合采用复制算法,只需要付出少量存活对象的复制成本就可以完成垃圾收集;而在老年代之中,对象存活的比例相对新生代而言很高,也没有额外的空间分配对它进行空间担保,就必须使用标记清理或者标记整理算法来进行内存回收。
三.HotSpot虚拟机的算法实现
1.通过GC Roots枚举来完成可回收对象的判定
GC Roots枚举的难点:检查范围大,必须在快照中进行,时间敏感。
准确式GC的含义:虚拟机可以知道内存中某个位置的数据具体是什么类型。举例:内存中有一个32位的整数123456,它到底是一个reference类型指向123456的内存地址还是一个数值为123456的整数,虚拟机将有能力分辨出来,这样才能在GC的时候准确判断堆上的数据是否还可能被使用。
HotSpot虚拟机为了能得到哪些地方存放了对象的引用,使用了一组称为OopMap的数据结构来完成这个目的,当类加载完成时,HotSpot虚拟机就把对象内存布局当中什么偏移量上的数值是一个什么类型的数据计算出来,在HotSpot虚拟机的JIT编译过程之中,也会在特定位置记录上栈和寄存器之中哪些位置保存的是对象的引用,这样在GC发生时就可以直接扫描OopMap得到这些信息。
下面这个代码清单是HotSpot虚拟机所生成的一个String类型的HashCode方法的本地代码,在这段代码所生所在注释之中,我们可以看到第一条call指令上生成一个OopMap的提示,在这个提示中它提示了寄存器ebx和栈上偏移量为16的位置上各有一个对象指针,这两个对象指针的作用范围是从这段代码的第一条指令开始,然后以开始位置加上偏移量142得到的内存位置为结束,对于这段代码,指令开始的第一条偏移量尾数为730,加上142之后正好得到尾数为7be的指令,即hlt指令为止。
2.通过安全点和安全区来确定垃圾收集器的运作出发点
在OopMap数据结构的协助之下,HotSpot虚拟机可以快速准确的完成GC Roots的枚举,但是由于可能导致引用关系的变化或者导致OopMap内容变化的指令已非常的多,如果主每一条指令都生成对应的OopMap,那将会需要大量的额外空间,带来的空间成本不可接受。
Safepoint的含义:会导致OopMap内容变化的指令非常多,如果为每一条指令都生成对应的OopMap,那将会需要大量的额外空间,这样GC的空间成本将会变得很高,所以HotSpot只是在“特定的位置”记录了这些信息,这些位置被称为安全点(Safepoint)。
换句话说,在程序执行的时候,并非所有的指令都可以停顿下来开始GC过程,只有在指令流在执行到安全点的时候才能暂停并开始GC。安全点的选择既不能太少,以至于让GC等待的时间过长,也不能太多,以至于GC Roots枚举所带来的空间成本变得过高。在HotSpot虚拟机中选择哪些指令作为安全点,是以这些指令是否具备让程序长时间执行的特征为准则进行率选的,每一次计算机指令的执行时间都非常短暂,程序也不大可能因为输入指令流的长度太长而导致长时间的运行,可以使程序长时间运行的一个最明显的特征就是指令序列的复用,比如方法的调用、循环的跳转、异常的跳转等等,因此也只有具备这些功能的指令才会被选作安全点。对于安全点,另外一个需要考虑的是,如何让垃圾将要发生的时候,令所有线程都能跑到安全点上再停顿下来,在这里有如下两种方案供选择。
在Safepoint上停止线程执行:
a.抢先式中断:不需要线程的执行代码主动去配合,在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程的执行,再进行下一次的中断,直至所有的线程都中断在安全点上。
b.主动式中断:GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自已中断挂起。目前所有的商用虚拟机都采用主动式中断。
HotSpot虚拟机所选择的校验中断标志的位置与安全点选定的位置是重合的,譬如以下代码片段所示:test是轮循中断标志的指令,当需要暂停线程执行的时候,HotSpot会把线程标志的内存页置为不可读,在这里是160100这个地址,当这条test指令执行的时候就会产生一个异常信号,这个异常信号会被HotSpot预先注册的异常处理器捕捉到,在异常处理器之中实现这个线程的挂起,这样只需要一条简单的汇编指令就完成了安全点的轮循和触发线程中断的动作。
使用安全点似乎已经可以完美的解决什么时候以及如何开始垃圾收集的问题,但实际情况并不一定,安全点的机制仅仅是保证了程序执行的时候,不需要太长时间就可以遇到一个可以进入到GC的安全点,但前提是在程序正常执行的时候,当JAVA线程并不处于正常执行状态(即线程没有被分配任何CPU时间,如线程处理休眠或阻塞的状态)的时候怎么办呢?这时线程是没有办法相应JVM的中断请求的,显然,JVM也大可能等待这些休眠或被阻塞的线程被重新分配CPU时间,为了解决这个问题,就要使用到安全区域。安全区域是指在一段代码片段中,引用关系不会发生变化,在这个区域当中任何地方开始GC都是安全的,我们可以把安全区域看作一个被扩展了的安全点,当一个线程执行到一个被标识为安全区域的代码中,首先会标识自已已经进入了安全区域,当这段时间里面,当JVM需要发起GC就不会去管已经标识自已处理安全区域状态的线程,仅仅在线程需要离开安全区域的时候再去检查JVM是否已经完成根节点的枚举过程,甚至是完成了整个GC过程,如这个过程已完成,则线程继续执行,否则在安全区域等待根节点枚举的结束,直到收到可以安全离开安全区域的信号为止。
Saferegion的含义:首先标识自已已经进入了Safe Region,那样当这段时间里JVM要发起GC,就不用管标识自已为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。
相关推荐
在Java编程中,JVM(Java虚拟机)的垃圾回收机制是自动管理内存的重要部分。垃圾回收机制负责回收那些不再被程序使用、即无法达到的对象所占用的内存空间。我们通过深入探讨以下几个关键点来理解JVM垃圾回收机制的...
在JVM中,垃圾回收(GC)是自动管理内存的关键技术,其目的是回收不再使用的对象所占用的内存空间,以避免内存泄漏和提升系统性能。 首先,JVM内存分配策略主要涉及对象如何在堆内存的不同区域进行分配。对象在...
Java自动内存管理机制包含两部分:内存分配和内存回收,要想理解内存分配和回收的机制,则需要了解下Java内存区域(Java运行时数据区),这篇随笔将按照下面的线索进行逐步解析:1.Java运行时数据区2.对象“已死”的...
**垃圾回收**是JVM自动管理内存的重要机制,旨在提高系统性能,解决内存溢出和泄漏问题。在Java堆和方法区,垃圾回收针对无用的对象和废弃的常量、类进行回收。 **对象存活判定**主要有两种方法: 1. **引用计数法*...
- **垃圾收集(Garbage Collection, GC)**:自动内存回收,包括新生代、老年代、永久代(或元空间)的划分,以及各种GC算法如标记-清除、复制、标记-整理和分代收集。 - **对象分配与存活判定**:如Eden区、...
GC(垃圾回收)是JVM自动管理内存的关键机制,它能自动回收不再使用的对象占用的内存空间。JVM中垃圾回收的判定方法主要有引用计数法和引用链法。引用计数法因为无法处理相互引用的场景而未被采用,而引用链法则通过...
- **概念**: 根据对象生存周期的不同,JVM将堆空间划分为新生代和老年代,并针对不同区域选择不同的垃圾收集算法。 - **选择依据**: 新生代对象生命周期较短,适合采用标记复制算法;而老年代对象存活时间较长,更...
新生代主要负责年轻对象的生命周期管理,通常采用复制算法进行垃圾回收;老年代则存放生命周期较长或较大的对象。 - **栈区(Stack)**:每个线程拥有一个独立的栈空间,栈区主要用于保存线程的局部变量、操作数栈...
垃圾回收 (GC) 是 JVM 自动管理内存的重要机制之一。GC 主要解决以下问题: - **垃圾对象的判定**:通过可达性分析等方法来确定哪些对象已经不再被任何活动线程所引用。 - **选择合适的 GC 算法**:根据对象的生命...
1. **垃圾回收机制**:Java的自动内存管理主要依赖于垃圾回收器(Garbage Collector, GC)。GC负责清理不再使用的对象,以释放堆空间。理解GC的工作原理、不同类型的GC算法(如新生代、老年代GC,CMS,G1,ZGC等)...
当对象被判定为垃圾对象时,由 JVM 自动调用此方法,用以标记垃圾对象,进入回收队列。垃圾对象是指没有有效引用指向此对象时,为垃圾对象。垃圾回收是指由 GC 销毁垃圾对象,释放数据存储空间。有自动回收机制和...
Java内存模型是Java虚拟机(JVM)管理内存的核心机制,它定义了程序中各部分如何访问和存储数据。在面试中,理解Java内存模型对于理解程序的性能和避免内存泄漏至关重要。 首先,我们来看Java内存模型的主要组成...
垃圾回收(GC)是JVM自动管理内存的重要功能,通过识别并清除不再使用的对象来释放内存。不同的JVM版本提供了多种垃圾收集器,如Serial、Parallel、CMS和G1,它们各有优缺点,适用于不同的场景。GC的判定主要依赖于...
垃圾回收(GC)是JVM的垃圾收集器自动管理工作,其算法主要考虑如何确定对象被判定为垃圾,常见的算法有引用计数法和可达性分析。引用计数法通过计数器判断对象是否可达,而可达性分析是通过GC Roots作为起点,向下...
- **直接内存**: 不属于JVM自动管理的内存范围,但仍然会受到本机系统总体内存大小的限制。 **2. 类的加载** - 加载: 通过类的全限定名找到对应的字节码文件,然后将其转换为二进制流,再通过`ClassLoader`将这些...
然而,Java通过内置的垃圾收集机制,自动追踪和释放不再使用的对象,极大地简化了内存管理过程,使得开发者能够更专注于业务逻辑的实现。 尽管如此,理解GC的运作原理对于优化程序性能、避免内存溢出等问题仍然至关...
- **动态对象年龄判定**:对象年龄阈值动态变化,提升内存回收效率。 - **空间分配担保**:当Survivor空间不足时,对象直接进入老年代。 #### 五、虚拟机类加载机制 1. **类加载的时机** - 类首次主动使用时...
- **JVM中垃圾收集算法及垃圾收集器详解**:不同的垃圾回收算法及它们的特点。 - **JVM怎么判断对象是否已死?**:对象存活状态的判定条件。 #### 四、Android基础 - **Activity全方位解析**:Android四大组件之一...