jvm GC原理 年轻代(新生代)、老年代、永久代(元空间)

目录

 

前言: 

虚拟机栈和本地方法栈溢出

堆整体

为什么要对堆内存分代

年轻代

老年代

永久代(元空间)

Full GC

有关年轻带的配置参数


前言: 

由于最近项目改版 , 在修改代码过程中因业务逻辑需要现需要采购订单由上至下所有关联数据全部装配至一个实体类 Biz 中 , 一共后续数据操作 , 

期初数据量小时候并未显现问题 , 而后等加入的数据量持续增大时 , 出现了 OutOfMemoryError(内存溢出)      ,后经查看分析代码运行时堆栈数据 , 发现在某一时刻内存的使用急剧上升 ,

, 一切特定使用类始终没被回收  , 然后最终怀疑目标就是 这次项目改版 加进去的体量特别大的 "biz" 所导致的问题 , 因为该类从一开始就在被使用,, 且生命周期长 , 数据量也在持续增加,

最终导致的  OutOfMemoryError(内存溢出)   , 无奈只能删减这部分业务逻辑  , 减少 biz 该类体量 , 通过修改业务逻辑选择其他方式分别装配数据 , 从而缩短该类的生命周期 .

经过了这件事之后 , 算是对jvm GC 还有年轻代,老年代 有了更深层次的了解 , 在此做笔记总结下 . 

虚拟机栈和本地方法栈溢出

两种异常:

  • StackOverFlow异常:线程请求的栈深度大于虚拟机允许的最大深度。
  • OutOfMemory异常:虚拟机扩展栈时无法申请到足够的内存空间。
  • 对于StackOverFlow异常:栈深度(1000-2000)绝大多数情况下都够用了。有错误堆栈可以阅读。
  • 如果由于建立线程过多导致的内存溢出,可以减少堆容量和每个进程的栈容量换取更多的线程。

堆整体

默认配置下各个代内存分配比例

假如总heap max分配1200M,那么年轻代占用1/3就是400M,老年代占2/3就是800M。

Eden占年轻代的8/10就是320M。Survivor占年轻代的2/10就是80M,from和to各占40M

为什么要对堆内存分代

     我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。年轻代和老年代的划分是为了更好的内存分派及回收。提高效率。

年轻代

也叫新生代,顾名思义,主要是用来存放新生的对象。新生代又细分为 Eden区、SurvivorFrom区、SurvivorTo区。

一般情况下,新创建的对象都会被分配到Eden区(如果该对象占用内存非常大,则直接分配到老年代区),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC(young GC),年龄就会增加1岁,当它的年龄增加到一定程度(15岁)时,就会被移动到年老代中。

在Minor GC开始的时候,对象只会存在于Eden区和Survivor from区,Survivor to区是空的。

Minor GC操作后,Eden区如果仍然存活(判断的标准是被引用了,通过GC root进行可达性判断)的对象,将会被移到Survivor To区。而From区中,对象在Survivor区中每熬过一次Minor GC,年龄就会+1岁,当年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置,默认是15)的对象会被移动到年老代中,否则对象会被复制到“To”区。经过这次GC后,Eden区和From区已经被清空。

“From”区和“To”区互换角色,原Survivor To成为下一次GC时的Survivor From区, 总之,GC后,都会保证Survivor To区是空的。

奇怪为什么有 From和To,2块区域?
这就要说到新生代Minor GC的算法了:复制算法

把内存区域分为两块,每次使用一块,GC的时候把一块中的内容移动到另一块中,原始内存中的对象就可以被回收了,

优点是避免内存碎片。

老年代

随着Minor GC的持续进行,老年代中对象也会持续增长,导致老年代的空间也会不够用,最终会执行Major GC(MajorGC 的速度比 Minor GC 慢很多很多,据说10倍左右)。Major GC使用的算法是:标记清除(回收)算法或者标记压缩算法

    标记清除(回收):1. 首先会从GC root进行遍历,把可达对象(存过的对象)打标记

                                    2. 再从GC root二次遍历,将没有被打上标记的对象清除掉。

        优点:老年代对象一般是比较稳定的,相比复制算法,不需要复制大量对象。之所以将所有对象扫描2次,看似比较消耗时间,其实不然,是节省了时间。举个栗子,数组 1,2,3,4,5,6。删除2,3,4,如果每次删除一个数字,那么5,6要移动3次,如果删除1次,那么5,6只需移动1次。

        缺点:这种方式需要中断其他线程(STW),相比复制算法,可能产生内存碎片。

     标记压缩:和标记清除算法基本相同,不同的就是,在清除完成之后,会把存活的对象向内存的一边进行压缩,这样就可以解决内存碎片问题。 


当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。

永久代(元空间)

 在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间,Metaspace)的区域所取代。


值得注意的是:元空间并不在虚拟机中,而是使用本地内存(之前,永久代是在jvm中)。这样,解决了以前永久代的OOM问题,元数据和class对象存在永久代中,容易出现性能问题和内存溢出,毕竟是和老年代共享堆空间。java8后,永久代升级为元空间独立后,也降低了老年代GC的复杂度。

上面说到了minor gc 和major gc,那么看下full gc

Full GC

 是清理整个堆空间—包括年轻代和老年代。

什么时候触发:

1. 调用System.gc

2. 方法区空间不足

2.老年代空间不足,包括:

  • 新创建的对象都会被分配到Eden区,如果该对象占用内存非常大,则直接分配到老年代区,此时老年代空间不足
  • 做minor gc操作前,发现要移动的空间(Eden区、From区向To区复制时,To区的内存空间不足)比老年代剩余空间要大,则触发full gc,而不是minor gc
  • 等等

GC优化的本质,也是为什么分代的原因:减少GC次数和GC时间,避免全区扫描。

有关年轻带的配置参数

1)-XX:NewSize和-XX:MaxNewSize

   用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。

2)-XX:SurvivorRatio

   用于设置Eden和其中一个Survivor的比值,这个值也比较重要。

3)-XX:+PrintTenuringDistribution

   这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。

4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold

   用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。

 

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值