成都创新互联网站制作重庆分公司

JVM内存分区及GC知识点是什么

JVM内存分区及GC知识点是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

网站建设哪家好,找创新互联公司!专注于网页设计、网站建设、微信开发、微信小程序开发、集团企业网站建设等服务项目。为回馈新老客户创新互联还提供了张家界免费建站欢迎大家使用!

Java 内存分区

  1. 本地方法栈:native方法调用时的方法调用栈,存储本地栈帧

  2. 虚拟机方法栈:Java的栈,每个线程有一个线程调用栈,栈的元素是栈帧。栈帧包括:局部变量表、操作数栈、指向堆中对象的引用、返回地址、附加信息。每个方法调用时,回向当前指向的线程栈顶部压入一个栈帧,栈帧的大小是固定的,虚拟机通过解析.class文件可以得知。

  3. 堆:堆是所有Java线程共享的一个内存区域,用于分配对象。

  4. 方法区:存储类的信息(类名、方法信息、字段信息)、静态变量、常量池。

  5. 程序计数器:用于记录下一条要执行的指令,每个线程都有自己的程序计数器,配合线程栈用于在线程调度时的线程上下文切换。执行本地方法时程序计数器中没有值或为undefined。

HotSpot堆内存

1.8之前,在HotSpot中堆内存分为新生代、老年代、永久代。永久代就是方法区的实现,占用一部分堆内存,但永久代不参与垃圾回收。 1.8及置换,HotSpot将堆内存分为新生代、老年代。用元数据区实现方法区,且元数据区占用堆外内存。

常见的GC算法

  1. 引用计数:每个对象引用被持有时,引用计数+1。引用赋值为null或销毁时引用计数-1。引用计数为0说明没有再被使用,可以回收。无法解决循环引用问题。

  2. 标记-清除算法:第一个阶段标记,将存活的对象标记出来;第二个阶段是清除,将死亡对象占用的内存回收利用。

  3. 标记-整理算法:第一个节点标记,标记处存货的对象;第二个阶段整理,将存活的对象整理放到另一处空间中去,然后把当前空间的内存全部回收。

  4. 复制算法:每次只利用内存的一半,当内存满时,将存货对象复制到另一半中去,这一半内存全部回收。

垃圾回收算法并没有最好的一种,为了达到降低整体GC时间的目的,一般是采用对内存分代进行垃圾回收,每个代采用不同的回收算法。

HotSpot堆内存分代

  1. 新生代 新生代与老年代内存占比默认为1:2。 新生代又可分为Eden区、Survivor0、Survivor1区,默认占用新生代内存比例为8:1:1。

  2. 老年代 老年代是一大块连续内存,没有再细分。

HotSpotGC类型

  1. MinorGC,也叫做Young GC,YGC, 是发生在新生代的GC,当Eden区满时触发,HotSpot中新生代均GC采用复制算法实现。 新分配的对象一般是在Eden区,除非满足条件时,对象会直接分配到堆上。Eden区满之后会将Eden存活对象拷贝到survivor0区。当survivor0内存不足时,将survivor0中存活的对象拷贝到survivor1区,并回收survivor0区,然后交换survivor0和survivor1的名称。一般也叫from和to。 每次从from到to的拷贝,都会记录对象的年龄+1,当对象年龄达到一个设定的值后(默认15),对象会被分配到老年代。 如果从from向to的拷贝过程中发现to内存不够用,也会将对象转存到老年代。

直接老年代分配内存的情况:

  • 对象所需内存大小超过Eden区大小,所有收集器都支持。

  • Eden区内存不足,且对象所需内存大小超过Eden区一半,直接分配到老年代,且不触发MinorGC。使用ParallelScavenge时支持。

  • 对象大小超过XX:PretenureSizeThreshold设置时,直接进入老年代分配。Serial、ParNew、CMS收集器支持。

  1. FullGC FullGC是发生在老年代的GC,触发时机有:

  • 调用System.gc()时,系统会建议执行GC,但不是立即执行。

  • 从新生代晋升到老年代的对象所需内存大于老年代可用内存时。

GC吞吐量 = 用户代码执行时间 / (用户代码执行时间 + GC时间)

HotSpot垃圾收集器

jdk1.7和1.8默认采用ParallelGC,也就是Parallel Scavenge(新生代)+Parallel Old(老年代)GC。 jdk9采用G1垃圾收集器。

其他的垃圾收集器还有CMS、ParaNew、Serial等 查看java默认垃圾收集器命令:

java -XX:+PrintCommandLineFlags -version

-XX:+UseParallelGC 表示使用Parallel Scavenge + Parallel Old
-XX:+UseConcMarkSweepGC 在启动应用加上这个参数表示使用CMS垃圾收集器
-XX:+UseG1GC 在启动应用时添加这个参数表示使用G1垃圾收集器,jdk8中支持开启
Serial、Serial Old

使用单线程进行垃圾回收,GC时停止用于线程工作。单CPU环境下表现好,因为没有GC线程间的交互。 Serial用于新生代,复制算法。Serial Old用于老年代,基于标记-整理

ParNew

Serial的多线程版本,采用复制算法。在单CPU时不如Serial,多CPU效果较好。 ParNew用于新生代。

Parallel Scavenge

Parallel Scavenge采用复制算法。算法目的是提高吞吐量,降低GC时间(但可能提升GC次数),让用户代码得到更多执行时机。 Parallel代表并行,即多个GC线程同时进行,但还是会暂停用户线程,在GC完成后用户线程才继续执行。 相比较于ParNew收集器,可以添加启动参数XX+UseAdaptiveSizePolicy,添加参数后可以不用设置Eden和Survivor区比例,也不用设置晋升老年代对象年龄等细节参数,该算法会自动根据系统运行情况动态调节参数。

Parallel Old

Parallel Old是Parallel Scavenge的老年代版本,采用标记-整理算法。可以并发GC,整个GC过程是暂停用户线程执行的。

CMS垃圾收集器

CMS全名Concurrent Mark Sweep,并发标记-回收垃圾收集器。是老年代的GC处理器。 CMS分为四个步骤:

  1. 初始标记:仅标记GCRoots和新生代能够直接引用到的老年代对象,速度较快,触发STW。

  2. 并发标记:此阶段GC标记线程与用户线程同时执行,遍历初始标记的对象,并递归标记这些对象引用的全部对象,这个过程比较慢,但是不触发STW。 因为这个阶段是并发标记,可能发生 对象从新生代晋升到老年代、直接在老年代分配对象、老年代引用对象关系发生变化、新生代对老年代对象引用发生变化 等现象,对于这些对象都是要重新标记的。 做法是将这些对象所在的Card Table中的位设置为dirty,把并发标记阶段新产生的对象和对象引用关系的变化记录下来(记录到Mod-Union Table)。

Card Table:将老年代内存分为相等大小的CardPage,每个CardPage用一个二进制位表示其内部的对象在并发标记阶段是否发生了引用变化,这个二进制位数组就是CardTable。 Mod-Union Table:是一个和CardTable类似的结构; 3. 并发预清理:从Mod-Union Table中找到并发标记阶段标记为dirty的内存区域,重新标记这些引用关系发生过变化的对象。可以通过参数CMSPrecleaningEnabled来关闭这个阶段,默认开启的。
4. 并发可中断预清理:循环处理From和To区对象,标记可达的老年代对象;并且循环处理DirtyCard的标记,不触发STW。循环的退出条件有三种

  1. 循环次数超过CMSMaxAbortablePrecleanLoops设置,默认0,没有次数限制;

  2. 循环时间超过CMSMaxAbortablePrecleanTime设置,默认5s,超过会退出;

  3. 并发预清理时Eden使用率低于10%,而某一次循环后Eden使用率达到CMSScheduleRemarkEdenPenetration(默认50%)后会退出;

这个阶段循环执行的目的是尽量减小下一个重新标记阶段需要处理的新生代对象引用老年代对象的情况,因为下一个阶段会触发STW,为提高吞吐量自然是dirty状态的内存越少越好。如果刚好在这个循环执行的5秒内触发了YGC,然后这个阶段又并发了处理了dirty的引用,则下个节点需要重新标记的对象就没那么大,STW时间也就短了。 上一个并发预清理节点可以不要的原因就是这一步其实也能达到预清理的效果,而且是循环操作的。 5. 重新标记:较慢,CMS的瓶颈点,触发STW,并发重新标记。虽然Preclean和AbortablePreclean已经尽量处理了DirtyCard,但是不能保证完全处理掉,因此还需要重新标记,要进行如下处理:

  1. 遍历新生代对象,重新标记; 这个节点要遍历新生代对象,如果新生代使用率很高,对象很多的话会耗时很久去遍历,因此如果在进行重新标记之前触发了YGC,这个步骤的耗时会减少很多。CMS可以通过参数CMSScavengeBeforeRemark来强制在重新标记阶段之前进行一次YGC,该配置默认关闭。开启的话可能会出现连续的两次YGC,也挺浪费时间。 进行一次YGC之后并不是要把DirtyCard交给重新标记阶段的5.3执行,而是交给4可中断预处理来执行。

  2. 遍历GCRoots,重新标记;

  3. 遍历DirtyCard,重新标记,这时候经过前两个阶段的处理,DirtyCard已经减少了很多。

  4. 并发清除:根据标记结果清除垃圾对象,不触发STW,速度较慢。

  5. 并发重置:重置CMS内部的数据,如CardTable、Mod-Uniod Table等,为下一次GC做准备,不触发STW。

CMS的两个标记阶段,都会触发STW,不一样之处在于,初始标记仅标记GCRoots和新生代可达的老年代对象,没有再递归遍历标记,较快完成;而重新标记阶段,标记GCRoots、新生代可达、DirtyCard中的老年代对象,且递归遍历标记,比较耗时。

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注创新互联行业资讯频道,感谢您对创新互联的支持。


当前名称:JVM内存分区及GC知识点是什么
分享路径:http://cxhlcq.com/article/giigho.html

其他资讯

在线咨询

微信咨询

电话咨询

028-86922220(工作日)

18980820575(7×24)

提交需求

返回顶部