我是靠谱客的博主 潇洒犀牛,最近开发中收集的这篇文章主要介绍Java内存模型、GC Roots可达性分析、垃圾回收机制,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

      • 一 内存模型
      • 二 可达性分析与GC Roots
        • 2.1 可达性分析算法
        • 2.2 什么样的对象可以作为GCRoots
        • 2.3 什么时候会触发垃圾回收
      • 三 如何回收垃圾
        • 3.1 标记-清除算法
        • 3.2 复制算法
        • 3.3 标记-整理算法
        • 3.4 分代收集算法
          • 分代收集算法中的内存分配与回收策略
      • 四 参考

一 内存模型

内存模型

其中,堆和方法区属于线程共享区域,虚拟机栈、本地方法栈、程序计数器属于线程私有区域

  • 堆区
    提供所有类实例和数组对象存储区域,属于所有线程共享区域
  • 方法区
    跟堆区一样,方法区也属于共享区域,方法区中存放着所有class文件及static变量,常量池也是在方法区中。
  • 栈区
    栈区属于线程私有,栈中只存储基本数据类型的对象及自定义对象的引用(注意,栈中存放的是自定义对象的引用,对象是在堆区中),栈又可以分为虚拟机栈、本地方法栈。
    • 虚拟机栈
      虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
      每一个栈帧中又包含了以下信息:
      • 局部变量表:表长度在编译阶段确定。调用方法时传递的参数,以及在方法内部创建的局部变量都保存在局部变量表中。
      • 操作数栈:也称为操作栈,是一个后入先出栈(LIFO),操作数栈的深度也是在编译阶段确定。当一个方法刚开始执行时,这个方法的操作数栈是空的,在方法执行过程中,会有各种字节码指令被压入和弹出操作数栈。
      • 动态链接:主要是为了支持方法调用过程中的动态连接。比如在一个方法中去调用其他方法。
      • 返回地址:用来帮助当前方法恢复它的上层方法执行状态。
    • 本地方法栈
      同虚拟机栈基本相同,区别在于虚拟机栈是为Java方法服务,本地方法栈是为虚拟机栈中使用到的Native栈服务。
  • 程序计数器
    线程私有,程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。字节码解释器可以通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程 恢复等都依赖这个计数器完成。每个线程都需要一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。

如果线程正在执行的是一个Java方法,这个线程对应的计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的是Native方法,这个计数器值为空(Undefined)。程序计数器所在的内存区域是唯一一个没有规定任何OutOfMemoryError情况的区域,生命周期随线程的创建而创建,随线程的消失而死亡

PS: 我们开发中常见的两个异常:

StackOverflowError:栈溢出异常,原因是因为线程请求的深度大于虚拟机所允许的深度。一般无限递归会产生该异常,因为每调用一次递归的方法时,都会在虚拟机栈中创建一个栈帧,且方法不会退出,因为是无限循环,当超过虚拟机允许的最大深度时,就会抛出该异常。

OutOfMemoryError:内存溢出异常,理论上在堆、栈、方法区都有发生OOM的可能,但是实际中一般发生在堆中,当前堆中需要申请的内存+已使用的内存>虚拟机分配的内存时,就会导致内存不足,进而抛出该异常。

二 可达性分析与GC Roots

在垃圾收集器准备回收对象时,首先要判断对象是否还存活着,那么怎么判断对象的存活呢?先介绍一种引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加一;当引用失效时,计数器值减一,当对象的计数器为0时代表该对象不再被使用。引用计数法实现简单,判定效率也很高,但是有个比较大的问题在于引用计数法很难解决对象之间相互循环引用的问题

2.1 可达性分析算法

可达性分析算法是通过一系列称为"GC Roots"的对象作为起点,从这些节点开始往下搜索,搜做走过的路径称为引用链,当一个对象到GC Roots没有任何引用链时,则证明该对象是不可用的,该对象会被判定为可回收的对象。

可达性分析.png

2.2 什么样的对象可以作为GCRoots

  • Java虚拟机栈(局部变量表)中引用的对象
  • 处于存活状态中的线程对象
  • 方法区中静态引用指向的对象
  • Native方法中JNI引用的对象

2.3 什么时候会触发垃圾回收

一般下面两种情况下会触发GC:

  • Allocation Failure:在堆内存中分配时,如果因为可用剩余空间不足导致对象内存分配失败,会触发一次GC
  • System.gc():应用层主动调用此API来请求一次GC

三 如何回收垃圾

常用的垃圾回收算法有下面的几种:

3.1 标记-清除算法

标记-清除(Mark-Sweep)算法是最基础的收集算法,它分为"标记"和"清除"两个阶段:标记出所有需要回收的对象(找到内存中所有的GC Root对象,和GC Root直接或者间接相连的标记为存活对象,否则标记为垃圾对象),标记完成后统一回收被标记垃圾对象。

标记-清除算法是最基础的算法,后续的算法都是在它基础上进行改进得到的。它有两个缺点:1、效率不高,标记和清除两个过程效率都不高 2、标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

标记-清除算法.jpg

3.2 复制算法

复制算法将可用内存按容量划分为大小相等的2块,每次只使用其中的一块。当其中一块内存用完了,就将还存活的对象复制到另一块内存中去,然后把已经使用过的内存空间一次性清理掉。

优点:实现简单,运行高效,不用考虑内存碎片等复杂情况。
缺点:内存使用直接缩小为原来的一半;且在对象存活率较高时会进行较多的复制操作,效率会变低。

复制算法.jpg

3.3 标记-整理算法

标记-整理(Mark-Compat)算法的标记过程与标记-清除算法是一样的,但后续步骤不是直接对可回收对象进行清除操作,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

优点:避免了内存碎片的产生,也不用将内存一分为二,性价比较高。
缺点:仍需要进行局部对象移动,当存活对象较多时还是会影响效率。

标记-整理.jpg

3.4 分代收集算法

分代收集算法是根据对象存活周期的不同将内存划分为几块。一般是把Java堆划分为新生代和老年代,这样可以根据各个年代的特点采用最适当的收集算法。一般在新生代中,每次垃圾收集时会有大批对象死亡,只有少量对象存活,所以采用的是复制算法,只需对少量存活对象进行复制就可以完成收集(注:这里使用的复制算法,并不是按1:1的空间去划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,内存比例是8:1:1,每次使用Eden空间和其中一块Survivor空间。当回收时,将Eden和Survivor中还活着的对象一次性复制到另外一块Survivor空间中,最后清理Eden和刚才用过的Survivor空间;当另一块Survivor空间没有足够空间存放收集下来的存活对象时,这些对象将通过分配担保机制直接进入老年代);而在老年代中因为对象存活率高,所以更适合使用标记-清除或标记-整理算法进行回收

分代收集算法中的内存分配与回收策略

GC可以分为Minor GC和Full GC,他们有什么区别?

  • 新生代GC(Minor GC):发生在新生代的垃圾收集动作,Java对象大多都具备朝生夕灭的特点,所以Minor GC非常频繁,一般回收速度也比较快。
  • 老年代GC(Major GC/Full GC):发生在老年代的GC,Full GC的速度一般会比Minor GC慢10倍以上。

当对象被创建时,优先分配在新生代的Eden区中,当Eden区中没有足够的空间时,会进行一次Minor GC。

  • GC期间虚拟机会尝试将存活对象复制到Survivor区(新生代采用的是复制算法),如果复制成功,那么会将Eden区进行清理,然后存放新申请的对象;
  • 如果Survivor区也没有足够的空间来存放Eden区中存活的对象,那么会直接根据分配担保机制直接将Eden区中存活的对象转移到老年代中去。

每个出生的对象都有一个年龄(Age)计数器,如果是在Eden区出生的对象经过第一次Minor GC后仍然存活,并且能被Survivor容纳,将会被移动到Survivor区,对象年龄设为1。对象在Survivor区每经过一次Minor GC,对应的年龄就增加1岁,当增加到一定年龄时(默认是15岁),就将会晋升到老年代。但是这里还有2个例外情况:

  • 如果是大对象(如很长的字符串以及数组),需要比较大的、连续的内存空间,会直接进入老年代;
  • 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到规定的年龄(默认15岁)。

四 参考

【1】部分内容摘自《深入理解Java虚拟机》
【2】https://juejin.im/post/5df2ec71f265da33d7442433
【3】https://www.jianshu.com/p/0269237a229d

最后

以上就是潇洒犀牛为你收集整理的Java内存模型、GC Roots可达性分析、垃圾回收机制的全部内容,希望文章能够帮你解决Java内存模型、GC Roots可达性分析、垃圾回收机制所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(75)

评论列表共有 0 条评论

立即
投稿
返回
顶部