哪些内存需要回收
什么时候回收
如何回收
垃圾回收线程是守护线程,平常到达安全点和安全区域时会回收,当堆内存占用到达上限时Full GC
3.1引用计数算法和可达性分析算法
3.1.1引用计数算法
在对象中添加一个引用计数器,每当有一个引用它时计数器就加一;当引用失效时计数器就减一;任何时刻计数器为零的对象就是不可能再被使用的;
缺点:如果两个对象相互引用不会被回收
3.1.2可达性分析算法
一般虚拟机采用的是这种方法
基本思路:通过一系列被称为“GC Roots”的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索过程走过的路径称为“引用链”,如果某个对象到GC Root之间没有任何引用链,代表对象不可达,则证明此对象不会再被使用
GC Root 有(可以根据具体的场景指定,下边的相当于是全局回收时用到的):
虚拟机栈 中引用的对象
方法区 中类静态属性引用的对象,常量引用的对象
虚拟机内部的引用 Class对象中的一些异常对象 空指针,堆溢出
同步锁持有的对象 synchronized
当分代回收时,是局部回收,就可以指定要回收的代为GC Root
强引用、软引用、弱引用、虚引用
为了再次细化垃圾回收的时机,又将已经被引用的对象细分为这四种状态
强引用:一般理解的引用 Object object = new Object();
软引用:还有些用但非必须的对象,GC之后内存还是不够,会在内存溢出之前对这些对象进行二次回收
弱引用:表示只能生存到下次垃圾回收
虚引用:虚引用不会影响回收时间,只是会在回收时收到一个系统通知
3.2方法区的回收
方法区的回收性价比比较低,是否回收可以通过配置设置
回收条件:
1.类所有的实例已经被回收,包括所有的派生类
2.这个类的类加载器已经被回收,这种场景存在于OSGI JSP的重新加载
3.类的Class对象没有任何地方被引用
3.3垃圾回收算法
准备工作:分代,堆分为年轻代和老年代(G1是将堆分为若干Region)
针对不同的分代采用不同的回收类型
Minor GC(年轻代) Major GC(老年代) Full GC(堆+方法区)
不同的区域也可以采用不同的算法:标记-复制、标记-清除、标记-整理
3.3.1标记清除算法
标记要清除的对象或者标记不需要清除的对象,然后进行清除
缺点:
1.如果需要回收的对象过程,需要进行大量的标记清除动作,执行效率会降低
2.容易产生内存碎片,内存利用率低
“分区空闲链表” 表明现在的可用空间
3.3.2标记复制算法
基本思想“半区复制”,将内存安装大小分为两个相同的区域,每次只使用其中的一块,当这块用完时,将活着的对象复制到另一块,再将原来的一块清空。
实际使用中不按1:1的比例划分,好比是9:1的比例,复制时第二块就有可能装不下就需要有其他内存做为担保,一般用老年代做担保。
老年代没有担保空间,就不方便采用这种算法
HotSpot对年轻代采用的是这种算法,将年轻代分为Eden和两个Survivor
3.3.3标记整理
标记对象,将存活的对象移动至内存前端
缺点:对象移动时需要用户程序停止即“Stop The World”
可以将标记清除和标记整理配合使用,当内存碎片影响内存分配时使用一次标记整理
3.4OopMap、安全点、安全区域
一旦触发了垃圾回收,如何确定垃圾回收线程的执行时间
Hotspot为了确定垃圾回收的时间引入了安全点和安全区域的概念,安全点和安全区域代表内存区域没有发生变化,这个时候能够允许回收线程和用户线程可以同时进行
3.4.1用OopMap埋点 这个重新看一下,寻找对象的引用关系应该是内存的角度
Hotspot在类加载完成时用OoMap的数据结构记录了对象的引用关系,在编译完的字节码文件中有这种记录的体现,当进行GC时不需要从GC Roots开始进行查找
OopMap在字节码文件表明从某个位置到某个位置引用了某个对象
3.4.2安全点
OopMap记录的位置即为安全点
用户线程到达安全点之后会将自己挂起,等待GC线程执行
安全点:方法调用、循环跳转、异常跳转
3.4.3安全区域
某一段代码片段之中,引用关系不会发生变化
用户线程进入安全区域之后会意识到自己进入了安全区域,当要出安全区域时先判断GC操作是否完成如果没有就会等待直到完成