jvm GC

java内存模型主要分为线程私有的:jvm虚拟栈(栈帧), 本地方法栈,程序计数器
以及堆,方法区(runtime constant poll)。
线程中的内存随着线程结束而被回收,不需要考虑
但是堆和方法区需要gc回收,堆中存着基本所有对象实例。
如Object obj = new Object()此类所有new出来的东西,他们都在堆中。
哪怕值相同可是地址也是不同的,所以对于new的对象判断==是一定false的。
那么首先有这样的概念。
新生代,老年代, 永久代(在hotspot vm中的概念)。
以及
标记/清除算法, 标记/整理算法, 复制算法。

首先介绍标记/清除算法。
我们了解一个概念叫做GC roots.GC roots是一组活跃着的引用
也许是当前栈帧中指向堆中的引用,也许是当前正在调用的方法的类型参数,局部变量等…
我们通过GC roots 去遍历引用链,能够遍历到的对象我们就标记下来。
一遍遍历完了,我们将那些没有标记的,全部清除掉,那些就是GC回收的内存。
如 ABCDEFG 这一排连续的内存,假设A C E是没有被标记的。那么清除ACE
此时内存会变成这样 B D FG。
这个算法的缺点在于:1.全部遍历太耗时,如果被标记的是100%,那么无需清除,这样的代价是很大的,因为在进行GC时,会进入一个stop the world的状态,所有的程序是停止的,为什么这样做,因为如果在运行时,引用是会不停变化的,这样我们无法通过引用链去遍历对象,无法清除正确的垃圾。
所以如果在全部遍历而回收甚少的时候,是很亏的。
2.内存不连续,如果我们new了一个大的对象,需要用到一片连续的内存,那么这一片内存就无法使用了。

针对这两个缺点,出现了复制算法。
复制算法是将内存分成两半,先遍历一半之后,将清除完的这一片内存,复制到另一半空的内存中。注意这里复制过去后,不再是断断续续的内存了,而是一片连在一起的完整内存。

这个算法同样具有缺点。一个是内存分半的代价,一个是复制的代价。
内存分半意味着要减少一半的内存使用率,复制同样需要花费代价。

最后的标记整理算法,与清除算法区别不大,在进行回收以后,会根据某一段,将之后的内存全部并拢到一边,于是不再出现内存不连续的情况。

那么介绍新生代和老年代。

新生代使用复制算法,因为新生代“朝生夕死”, 死亡率高达98%。在JVM中我们的复制算法不再是用1:1一半的比例分配,而是8:1:1。
8:eden 1:from survivor 1: to survivor
首先使用eden和from。
再一次清除以后eden中仍然存活的对象被移到to区。此时eden应该为空了,而from中如果age超过阈值(好像为15,每一次回收为一轮)的对象会被移到old gen,老年代中。然后留下来的会被移到to里面。接着to会被该做from,而以前的from会被改为to,在每一轮回收以后to都应当是空的。
为什么这里使用复制算法,我们前面提及如果标记的是100%那么就是做了一次无用的遍历,可是新生代死亡率高达98%那么清除掉98%,这样无疑是一次划算的遍历,而且剩余%2的复制率,也很低。

老年代一般使用标记/清除或者标记/整理算法。
老年代存活率高,适合这两个算法。

永久代在一般情况下不做回收。

那么什么时候做Minor GC(新生代GC)和Full GC(老年代GC):
Minor GC:Minor GC非常频繁,因为新生代基本都是朝生夕死,所以在Eden空间不足以为对象分配内存时触发一次GC。

Full GC:Full GC一定会伴随着一次以上的Minor GC,也好理解,因为Minor GC后会也许会有对象进入老年代。当老年代满的时候自然会GC。Full GC速度非常慢,是Minor GC 的十倍以上。当老年代内存满的时候或者显式地调用System.gc()时会Full GC。

最后贴一下参考的可作为GC Roots的对象
①虚拟机栈(栈桢中的本地变量表)中的引用的对象,就是平时所指的java对象,存放在堆中。
②方法区中的类静态属性引用的对象,一般指被static修饰引用的对象,加载类的时候就加载到内存中。
③方法区中的常量引用的对象,
④本地方法栈中JNI(native方法)引用的对象

指路一篇深度好文,讲解十分详细。链接放在底下。
https://www.jianshu.com/p/76959115d486

你可能感兴趣的:(JVM)