jvm Gc的机制

来源:互联网 发布:oracle启动数据库命令 编辑:程序博客网 时间:2024/05/20 10:13

文章分析来自https://zhuanlan.zhihu.com/p/25539690知乎用户


1.GC回收范围



jvm的GC只是在堆区和方法区发生,栈区的数据,超出它们的作用域后会被jvm自动释放,所以不在jvm的GC范围。


2.如何判断一个对象可以被回收了?
(1)对象没有了引用
(2)发生未被捕捉的异常
(3)在其作用域内正常执行完成
(4)执行System.exit()
(5)程序发生意外的终止,如进程被杀死


3.按代划分
新生代:(Minor GC)所有新建的对象基本都集中在这里,由于很多对象创建后就会变得不可达,所以很多被创建后就被超出作用域后对象很快被回收,当发生Minor GC事件的时候,
当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。
新生代中分为几个区域
(1)eden(伊甸园空间)
(2)两个幸存者(Fron Survivor、To Survivor)
默认它们之间的比例  eden:fron survivor:to survivor=8:1:1。
老年代:新生代=2:1所以这也是新生代的GC频率比老年代高的原因
1.绝大多数刚刚被创建的对象会被分配到Eden中,当新生代中执行第一次GC时,幸存的的对象会被放到其中一个survivor
2.后面的gc存活的对象也会被放到该survivor,如果它放满后就会放到另外一个survivor,然后再把放满的那个survivor执行清空。
上面步骤当执行n(N = MaxTenuringThreshold(年龄阀值设定,默认15)次后依然还在存活的就会被转移到老年代中。
(两个幸存者空间,必须有一个是保持空的。如果两个两个幸存者空间都有数据,或两个空间都是空的,那一定是你的系统出现了某种错误)
有一点比较特殊如果有一个很大的内存连续的对象,该对象也会直接进入老年代,这种情况一般都是在survivor不足时发生。


老年代:(Major GC 或者 Full GC )在新生代被幸存下来的对象将会被放到老年代,老年代中的对象主要部分来自于新生代,当新生代中对象执行n次(默认是15)的年龄阀值的改变后,任然幸存的对象
就会进入老年代,那老年代什么时候被GC呢?当新生代中上升到老年代的对象大于老年代中的剩余空间时,这时候就需要执行full GC,当老年代中的剩余空间小于新生代上升的对象数量时
也会发生GC,这时候时通过Handlepromotionfailure来强制执行full GC,调优(write barrier)就是主要针对Full GC的,这里就是通过newRadio来对新生代的年龄阈值进行改变。这里我是这么理解的
如果我们通过调整新生代的对象的年龄阈值,那么新生代上升到老年代的对象就不会那么快,自然FULL GC的频率就低了。老年代这里也许你会疑惑如果老年代中如果有对新生代的对象的引用
怎么办?老年代中会有一个叫做cardtable的512byte大小的东西专门用来存放对新生代对象的引用,我们知道对象在GC之前会判断是否有被引用,如果新生代发生GC时,可以不必要来对老年代全文搜索,只需要
对老年代的cardtable进行搜索判断新生代的对象是否可以被回收即可。上面已经说了由于新生代中的内存比例时老年代的一半,所以老年代的FUll Gc/Major GC不会有新生代的minor GC那么频繁,再次既然这些对象
逃过了新生代的重重GC,自然不是等闲之辈,没必要频繁GC。并且做一次major GC所耗时间差不多是minor GC的10倍。


持久代:(Major GC)持久顾明思议就是存活时间久,它们被回收的条件比较苛刻,那么持久代中究竟是一些什么东西呢?持久代也称为方法区,所以里面主要是存放一些类常量和字符串常量
它们也会发生GC,注意持久代并不像老年代存放从新生代退下来的对象,虽然说它们被GC的条件比较苛刻,那么究竟有那些条件呢?条件有下
(1)所有的实例都被回收
(2)对象变得不可达(没有引用包括反射)
(3)加载该类classLoader也被回收




JVM GC 算法讲解
1.根搜索算法:可以把这个想象成图论中的一棵树,从根开始搜索到叶子结点,如果有发现结点不可达的情况就判断它是一个没有被引用的结点可以进行GC
2.标记 - 清除算法:对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象进行直接回收。
标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活的对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,
并没有对还存活的对象进行整理,因此会导致内存碎片。
3.复制算法:复制算法将内存划分为两个区间,使用此算法时,所有动态分配的对象都只能分配在其中一个区间(活动区间),而另外一个区间(空间区间)则是空闲的。
复制算法采用从根集合扫描,将存活的对象复制到空闲区间,当扫描完毕活动区间后,会的将活动区间一次性全部回收。此时原本的空闲区间变成了活动区间。下次GC时候又会重复刚才的操作,以此循环。
复制算法在存活对象比较少的时候,极为高效,但是带来的成本是牺牲一半的内存空间用于进行对象的移动。所以复制算法的使用场景,必须是对象的存活率非常低才行,而且最重要的是,我们需要克服50%内存的浪费。
4、标记 - 整理算法:在标记清除算法的基础上增加了移动存活对象排序整理,因此成本较高,确解决了内存碎片化问题。




JVM为了优化内存的回收,使用了分代回收的方式,对于新生代内存的回收(Minor GC)主要采用复制算法。因为复制算法是复制存活的对象,所以在存活率低的地方使用会更高效。
而对于老年代的回收(Major GC),大多采用标记-整理算法,上面已经说了老年中的对象是新生代中退下来的非等闲之辈所以GC几率不会很高,也就是对象的存活率比较多,所以用标记整理算法。
原创粉丝点击