JVM 源码分析09 垃圾回收02

新生代DefNewGeneration的实现

内存初始化实现

     分别通过EdenSpaceContiguousSpace类实现新生代的edenfrom to区域,(其中Contiguous是"连续"的意思,表示一块连续的内存空间),其中EdenSpace在实现上是继承自ContiguousSpace的。

1、如果_eden_space、_from_space、_to_space其中任何一个为空,说明新生代分配内存失败,则虚拟机退出;
2、根据SurvivorRatio计算from to内存区应该分配的大小;
3、剩余的空间大小分配给eden内存区;

 

GC过程实现

基本思路:
1、扫描内存堆所有的根对象集T,并把它们复制到新的内存块,一般为新生代的to内存区或老年代;
2、分析扫描这些根对象集T的所有引用对象集T1,并把它们复制到新的内存块;
3、继续分析对象集T1所引用的对象集T2,一直迭代下去,直到对象集Tn为空;

GC过程位于collect()方法中:

GC检测

1、确保当前是一次FGC,或需要分配的内存大小size大于0,否则不需要执行一次gc操作;
2、因为当前是最年轻代的管理器,确保有下一个内存管理器;

   否则,设置_incremental_collection_failedfalse,即当前minor gc不可用,通知内存堆管理器不要再尝试增量式GC了,因为肯定会失败;

GC开始

GC准备工作:
1、初始化IsAliveClosureScanWeakRefClosure
2、清空age_table数据和to区;
3、初始化FastScanClosure,负责存活对象的标识和复制;

广度优先遍历扫描活跃对象

1、循环条件no_allocs_since_save_marks()

    主要检查当前代、更高代以及永久代scanned指针_saved_mark_word是否与当前空闲分配指针位置相同

    因为在scanned指针到空闲分配指针之间的区域是已分配但未扫描的对象,对这块区域的对象调用遍历函数进行处理,标记所引用的对象,并保存新的scanned指针。

对象的标记和复制实现

对象的标记和复制过程最终由FastScanClosuredo_oop方法实现,其中do_oop方法又调用了do_oop_work方法,do_oop_work究竟做了什么?

使用模板函数解决不同类型的指针(实际oop和压缩过的narrowOop):
1、当该指针对象非空时,通过decode_heap_oop_not_null方法获取对象obj;
2、如果该对象obj在遍历区域(_boudary是在FastScanClosure初始化的时候,为初始化时指定代的结束地址,与当前遍历代的起始地址_gen_boundary共同作为对象的访问边界),则通过obj->is_forwarded()判断该对象是否已经标记过,如果对象没有被标识过,即其标记状态不为marked_value,则通过_g->copy_to_survivor_space(obj)方法把该对象复制到to区域;
3、根据是否使用指针压缩将新的对象地址进行压缩;

 

处理晋升成功

如果GC过程中没有发生对象的晋升失败,则执行如下逻辑:
1、既然所有对象都晋升成功了,说明存活对象都转移到了to区域或老年代,则通过clear方法清空edenfrom区;
2、通过swap_spaces方法交换fromto区域,为下次GC作准备

通过交换_from_space_to_space的起始地址实现fromto区的角色互换,并重新设置eden_next_compaction_space,即eden的下一个内存区域;

3、fromto区互换之后,当前的to区应该已经是块空区域了;
4、调用ageTablecompute_tenuring_threshold方法对晋升阀值_tenuring_threshold重新设置,实现如下:

 当前年龄age=6,如果年龄age=0的对象,到年龄age=6的对象总空间累加,大于了survivor空间的50%。那么就把这个Math.min(age,15) 当做晋升的阈值。如果累加的空间为40%,小于了50%,说明了当前age=6仍然过低,age++,变为age=7,继续下一次while循环计算,看看累计占用空间是否会超过survivor区的50%。

 

处理晋升失败  

  

如果GC过程中存在晋升失败,则执行如下逻辑:
1、当对象被标记为活跃对象时,其对象头markword指向经过复制后对象的新地址,remove_forwarding_pointers负责恢复晋升失败对象的markOop

当对象晋升失败时,对象的oop会被保存在_objs_with_preserved_marks栈中,对应的对象头markOop被保存在_preserved_marks_of_objs栈中,通过这两个栈,可以对晋升失败的对象的对象头进行恢复;

2、对fromto区进行互换,并设置from的下一片需要进行压缩的区域为to区,因为当有对象晋升失败时,并不会清空edenfrom区,这时对fromto区互换,但to区还有活跃对象,这样在随后触发的FGC能够对fromto进行压缩处理;
3、设置新生代的minor gc失败标识,并通知下一个内存代(老年代)发生晋升失败,比如在ConcurrentMarkSweepGeneration会根据参数CMSDumpAtPromotionFailure进行dump输出以供JVM问题诊断

 

1 为什么要有Survivor区

   如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。你也许会问,执行时间长有什么坏处?频发的Full GC消耗的时间是非常可观的,这一点会影响大型程序的执行和响应速度,更不要说某些连接会因为超时发生连接错误了。

 

2 为什么要设置两个Survivor区

设置两个Survivor区最大的好处就是解决了碎片化

那么,顺理成章的,应该建立两块Survivor区,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中。下图中每部分的意义和上一张图一样,就不加注释了

上述机制最大的好处就是,整个过程中,永远有一个survivor space是空的,另一个非空的survivor space无碎片

那么,Survivor为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果Survivor区再细分下去,每一块的空间就会比较小,很容易导致Survivor区满,因此,我认为两块Survivor区是经过权衡之后的最佳方案。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM(Java虚拟机)内存模型和垃圾回收是Java程序中重要的概念。JVM内存模型定义了Java程序在运行时所使用的内存结构,而垃圾回收是一种自动化的内存管理机制,用于回收不再使用的对象以释放内存空间。 JVM内存模型主要包括以下几个部分: 1. 堆(Heap):堆是JVM中最大的一块内存区域,用于存储对象实例。在堆中分配的内存由垃圾回收器自动管理。 2. 方法区(Method Area):方法区用于存储类的信息、常量、静态变量等数据。在JDK 8及之前的版本中,方法区被实现为永久代(Permanent Generation),而在JDK 8之后,被改为元空间(Metaspace)。 3. 虚拟机栈(VM Stack):每个线程在运行时都会创建一个虚拟机栈,用于存储局部变量、方法调用和返回信息等。每个方法在执行时都会创建一个栈帧(Stack Frame),栈帧包含了方法的局部变量表、操作数栈、动态链接、返回地址等信息。 4. 本地方法栈(Native Method Stack):本地方法栈与虚拟机栈类似,但用于执行本地方法(Native Method)。 垃圾回收JVM的一项重要功能,它负责自动回收不再使用的内存。JVM中的垃圾回收器会定期扫描堆中的对象,标记出不再被引用的对象,并将其回收释放。垃圾回收可以有效地避免内存泄漏和内存溢出的问题,提高程序的性能和稳定性。 JVM内存模型和垃圾回收是Java程序员需要了解和理解的重要概念,它们直接影响到Java程序的性能和内存使用情况。合理地管理内存和优化垃圾回收对于编写高效、稳定的Java程序至关重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值