IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> JVM(Java内存模型,GC) 知识总结复习 -> 正文阅读

[Java知识库]JVM(Java内存模型,GC) 知识总结复习

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一.java内存模型

在这里插入图片描述

主要关注运行时数据区,可以分为线程私有区域和共享区域。

线程私有区域: 虚拟机栈,本地方法栈,程序计数器

线程共享区域: 堆,方法区,运行时常量池

1.虚拟机栈

  • 每个方法被执行的时候,都会在虚拟机栈中创建一个栈帧用于存储局部变量表、操作数栈、动态连 接、方法出口等信息。

  • 局部变量表存放了编译期可知的各种基本数据类型,对象引用。

  • 局部变量表中的存储空间以局部变量槽(Slot)来表示, 其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。

  • 此区域常见的异常: 如果线程请求的栈深度大于虚拟机所允许的深度(-Xss设置栈容量),将会抛出StackOverFlowError异常。 递归调用没有边界限制就会出现这个异常。

2.本地方法栈

  • 本地方法栈与虚拟机栈的作用完全一样,区别是本地方法栈为虚拟机使用的Native方法服务。
  • 有些java虚拟机(如Hot-Spot)中,本地方法栈与虚拟机栈是同一块内存区域。

3.程序计数器

  • 可以看做是当前线程所执行的字节码的行号指示器, 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令 。

  • 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。

4.堆

  • JVM 所管理的最大内存区域,所有的对象实例以及数组都要在堆上分配"。
  • Java堆是垃圾收集器管理的内存区域 。
  • 如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展 时,Java虚拟机将会抛出OutOfMemoryError异常。

5.方法区

  • 它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

  • 在JDK8以前的HotSpot虚拟机中,方法区也被称为"永久代"(JDK8已经被元空间取代)。 此区域的内存回收主要是针对常量池的回收以及对类型的卸载。

  • JVM规范规定:当方法区无法满足内存分配需求时,将抛出OOM异常。

6.运行时常量池

  • Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信 息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面 量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池 中。

字面量 : 字符串(JDK1.7后移动到堆中) 、final常量、基本数据类型的值。

符号引用 : 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。

二. 垃圾回收

1. 怎么找到垃圾?

  • 引用计数算法:

    给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器 为0的对象就是不能再被使用的,即对象已"死"。

    引用计数法实现简单,判定效率也比较高,在大部分情况下都是一个不错的算法。比如Python语言就采用引用计数 法进行内存管理。

    存在对象之间相互循环引用的时候计数器不为0无法GC的问题

  • 可达性分析算法 :

    核心思想为 : 通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,也就是从GC Roots到这个对象不可达时,证明此对象是不可用的。

在这里插入图片描述

? 这也是Java选用的算法。

哪些可以被当成GC Roots?

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中Native方法引用的对象
  5. 所有被同步锁(synchronized关键字)持有的对象。

总之就是一些程序运行期间一直存在的对象。

可达性算法补充(了解):

即使在可达性分析算法中判定为不可达的对象,也不是“非死不 可”的,这时候它们暂时还处于“缓刑”阶段,要真正宣告一个对象死 亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有 与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛 选,筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有 覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟 机将这两种情况都视为“没有必要执行”。

如果这个对象被判定为确有必要执行finalize()方法,那么该对象将 会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自 动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法。 这里所说的“执行”是指虚拟机会触发这个方法开始运行,但并不承诺一 定会等待它运行结束。这样做的原因是,如果某个对象的finalize()方法 执行缓慢,或者更极端地发生了死循环,将很可能导致F-Queue队列中 的其他对象永久处于等待,甚至导致整个内存回收子系统的崩溃。 finalize()方法是对象逃脱死亡命运的最后一次机会,稍后收集器将对FQueue中的对象进行第二次小规模的标记,如果对象要在finalize()中成 功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬 如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在 第二次标记时它将被移出“即将回收”的集合;如果对象这时候还没有逃 脱,那基本上它就真的要被回收了。

2.回收方法区

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

回收变量的条件:已经没有任何字符串对象引用常量池中的这个常量,且虚拟机中也没有其他地方引用这个字面量 ,就可以回收该变量。

回收无用类的条件:

  1. 该类所有实例都已经被回收(即在Java堆中不存在任何该类的实例)
  2. 加载该类的ClassLoader已经被回收
  3. 该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法 。

3.垃圾回收算法

? 垃圾回收器都用了分代算法的思想,将GC堆分成两个部分,老年代和新生代

? 新生代:又可以分为Eden区和两个 Survivor 区。新生代的垃圾回收被称为 YGC( Young GC ) ,一般非常频繁,回收速度也比较快。

? 老年代: 老年代垃圾回收又称为OGC(old GC)或者 Major GC 。 频率不高,速度也很慢。

3.1 标记-清除算法 (Mark-Sweep)

  • 用于老年代的回收算法
  • 算法实现:首先标记处所有需要回收的对象,再统一回收被标记的对象。
  • 问题如下图:会导致内存空间变得零散,难以存储大对象。
    在这里插入图片描述

3.2 标记-整理算法(Mark-Compact)

  • 老年代回收算法

  • 算法思路:首先标记—>再将所有存活对象向内存空间的一段移动---->直接清理掉边界之外的内存

  • 和Mark-Sweep不同的就是空间连续。

在这里插入图片描述

3.3 标记-复制算法 ( Copying )

**也叫半区复制 Semispace Copying ,因为他将内存按容量划分为大小相等的两块,一半用于存储,一半用于复制和回收 **

  • 算法思路:将所有存活的对象复制到另一半区域,然后将这一半全部清理掉。
  • 速度快,复制的是存活对象,所以适用于新生代,因为新生代垃圾回收频繁,而且对象往往朝生夕灭。
  • 为了效率将可用的内存缩减了一半。

在这里插入图片描述

3.4 优化的复制算法

现在的很多虚拟机报货 HotSpot都是采用这种收集算法来回收新生代

将新生代内存划分为 一个较大的Eden(伊甸园)区和两个较小的Survivor(幸存者)区,也叫作Survivor From : Survivor To

HotSpot默认 : Eden:Survivor From : Survivor To = 8:1:1

算法实现流程:

  1. 当Eden区满了,触发第一次YGC,将存活对象复制到 Survivor From区 ,并将Eden区清空。
  2. 后续再次触发YGC,会扫描Eden区和Survivor From区,将存活对象复制到Survivor To区。
  3. 再触发就扫描Eden区和Survivor To区,同上。
  4. 一直存活的对象就会在To和From区复制来复制去, 当次数超过MaxTenuringThreshold(JVM参数参数 默认是15), 就会进入老年代。

4.垃圾回收器

在这里插入图片描述

HotSpot的这些垃圾回收器,各有特点,没有最好,只有更适合

并行GC:表示所有GC线程一起执行

并发GC:表示GC线程和用户线程一起执行


4.1 Serial收集器 (新生代收集器,串行GC)

  • 复制算法

  • Stop The World, 单线程收集器,在进行垃圾回收的时候,停止所有线程,回收完恢复。视觉上会造成停顿,卡顿。

  • 简单高效,可用于新生代内存很小的场合,当收集的内存很小, Stop The World的时间可以忽略。

4.2 ParNew收集器 (新生代收集器,并行GC)

  • Serial收集器的多线程版本, 其他和Serial收集器没有区别。
  • 搭配CMS收集器,在用户体验优先的程序中使用:

4.3 Parallel Scavenge收集器 (新生代收集器,并行GC)

  • 复制算法, 多线程
  • Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。 不同于CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,

4.4 Serial Old收集器 (老年代收集器,串行GC)

  • 单线程
  • 标记-整理算法

4.5 Parallel Old收集器(老年代收集器,并行GC)

  • 多线程
  • 标记-整理算法
  • Parallel Scavenge老年代版,也是吞吐量优先

4.6 CMS收集器(老年代收集器,并发GC)

  • 多线程,并发收集,低停顿

  • 标记-清除算法

  • 过程

    1. 初始标记(CMS initial mark): 初始标记仅仅只是标记一下GC Roots能直接关联到的对象, 速度很快, 需要“Stop The World”。
    2. 并发标记(CMS concurrent mark): 并发标记阶段就是进行GC Roots Tracing的过程, 标记所有存活对象。
    3. 重新标记(CMS remark):重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,仍然需要“Stop The World”。
    4. 重新标记(CMS remark)
    5. 并发清除(CMS concurrent sweep): 并发清除阶段会清除对象。
  • 标记采用的是三色标记法,标记完的对象为黑色,自己标记完,对象链没有标记完为灰色,没遍历到的对象为白色。

在这里插入图片描述

重新标记:当GC线程标记到B时,线程调度为用户线程执行,用户将B->C关系链去掉,加上了A->C关系链。这时候在GC线程看来,A已经是标记完了的,而从B又不再能找到C,导致C被误认为是垃圾。所以需要回头来 “Stop The World”,然后标记上C这个对象。

浮动垃圾:当GC线程标记到B时,线程调度为用户线程执行,用户将B->C关系链去掉,这时候C就是新制造的垃圾对象,但这一轮GC就处理不了这个对象,只能留到下一次GC,这个垃圾对象就叫浮动垃圾。

  • 应用场景: 目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的 需求。

  • 缺陷:

    1. CMS会抢占CPU资源。并发阶段虽然不会导致用户线程暂停,但却需要CPU分出精力去执行多条垃圾收集线程,从而使得用户线程的执行速度下降。
    2. CMS无法处理浮动垃圾(Floating Garbage),这导致了CMS不能在老年代几乎完全被填满了再去进行收集,必须预留一部分空间提供给并发收集时程序运作使用。 在JDK1.5默认设置下,老年代使用了68%(JDK1.6是92%)的空间后CMS的垃圾收集就会被激活,其实这是一个比较保守的设置,只要应用中老年代增长不是很快,可以适当地调高参数 XX:CMSInitialingOccupancyFraction来提高触发百分比,降低回收的频率来获得更好的性能。
    3. 而且由于浮动垃圾和并发的原因,如果CMS在收集期间,内存无法满足程序的需要,就会出现“Concurrent Mode Failure”,这 时JVM将启动Plan B,也就是临时调用单线程的Serial Old收集器来重新进行老年代的垃圾收集,这样的话,CMS原本降低停顿时间的目的不仅没完成,和直接使用Serial Old收集器相 比,还增加了前面几个阶段的停顿时间。
    4. CMS的“标记-清除”算法,会导致大量空间碎片的产生:
      • 碎片的积累会给分配大对象带来麻烦,往往会出现明明老年代还有很多空间剩余,但是却无 法找到连续的空间分配对象的情况,这时候就不得不触发一次Full GC。
      • 为了解决这个问题。CMS提供了一个-XX:+UseCMSCompactAtFullCollection的开关参数(默 认是开启的),用于在CMS收集器进行Full GC时对内存碎片进行合并整理,整理的过程是需要暂停用户线程的,这样碎片虽然没有了,但停顿时间又变长了。
      • CMS的设计初衷可是降低停顿,于是又提供了一个参数-XX:CMSFullGCsBeforeCompaction,用于设置执行多少次不压缩碎片的Full GC后,跟着来一次带压缩的Full GC(默认值为0,即每次都会)。

4.7 G1收集器(全区域的垃圾回收器)

? 停顿时间模型:能够支持指定在一个长度为M毫秒的时间片段内,消耗在垃 圾收集上的时间大概率不超过N毫秒这样的目标,这几乎已经是实时Java(RTSJ)的中软实时垃圾收集器特征了, G1就做到了停顿时间模型

将Java堆划分为多个大小相等的独立区域 (Region),每一个Region都可以根据需要,扮演新生代的Eden空间、 Survivor空间,或者老年代空间 。收集器能够对扮演不同角色的Region 采用不同的策略去处理。

Region中还有一类特殊的Humongous区域,专门用来存储大对象。 G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。 每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范 围为1MB~32MB,且应为2的N次幂。 而对于那些超过了整个Region容 量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1 的大多数行为都把Humongous Region作为老年代的一部分来进行看待

Region作为单次回收的最小单元 , 垃圾收集的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使 用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理 回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。

在这里插入图片描述

年轻代的垃圾收集
依然STW, 使用复制算法, 把Eden区和Survivor区的对象复制到新的 Survivor区域 。

TAMS(Top at Mark Start): 由于GC和用户线程是并发的。 所以在Region中的一部分空间被划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。

SATB(原始快照 snapshot at the beginning): 当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之 后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。

G1收集器的运作过程

  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行YGC的时候同步完成的,所以 G1收集器在这个阶段实际并没有额外的停顿。

  • 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段 耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。

  • 最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。

  • 筛选回收(Live Data Counting and Evacuation):负责更新Region 的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收 集,然后把决定回收的那一部分Region的存活对象复制到空的Region 中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移 动,是必须暂停用户线程,由多条收集器线程并行完成的。

5.垃圾回收的时机

5.1 Minor GC触发条件:

  • 创建对象在Eden区,且Eden区空间不足时。

5.2 Majar GC触发的条件:

  • 当对象需要放到老年代,但老年代空间不足时。分别有如下情况:
    • 新生代老年对象进入老年代。
    • 大对象直接进入老年代。

5.内存分配回收策略

  • 对象优先分配在Eden区

  • 大对象直接进入到老年代

  • 长期存活的年龄大的对象进入老年代,见上面的复制算法。

  • 动态对象年龄判定:

    • 为了能更好的适应不同程序的内存状况,JVM并不是永远要求对象的年龄必须达到 MaxTenuringThreshold才能晋升老年代。
    • 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
  • 空间分配担保

    • 当Minor GC时,Survivor空间不够时,会通过分配担保机制,将Minor GC存活下来的在Survivor存放不下的这些对象进入老年代

    • 在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间

      • 如果大于,则此次Minor GC是安全的;如果小于,则虚拟机会查看 HandlePromotionFailure设置值是否允许担保失败。
      • 如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。
      • 大部分时候都是能够成功分配担保的,避免了过于频繁执行Full GC
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-18 12:34:18  更:2021-08-18 12:35:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:17:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码