GC是什么
GC(garbage collector)是java的垃圾回收器、它解决了c和c++语言要程序自己清理内存的缺点,GC是一个优先级很低的线程,程序员不可以直接操控,具有不可预知性,但是可以通过System.gc()启动。
GC在哪里回收
jvm虚拟机的运行时数据
程序计数器(PC寄存器):用来记录正在执行的虚拟机字节码指令的地址(如果执行的是本地方法则为空)
程序计数器:可以看做是当前线程执行的字节码的行号指示器,通过改变这个计数器的值来读取下一条(指的是字节码的行数,并不是java代码的行数)需要执行的字节码指令,比如一些循环跳转指令。由于java是通过线程轮流切换,然后分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器只会执行一条线程中的指令,所以为了线程切换之后能到正确的执行位置,每条线程都需要有一个独立的线程计数器,这是线程私有的,各个线程互相不影响。
程序计数器百度百科:用来存放下一条指令所在的单元的地址的地方,当执行一条指令时,首先需要根据PC中存放指令的地址,将指令由内存取到指令寄存器中,这个过程叫做取指令,与此同时PC中的地址或者自动加一,或者由偏移地址给出下一条指令的地址。如此循环,执行每一条指令。
虚拟机栈:每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
本地方法栈:本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
堆:(GC主要对象)
所有对象都在这里分配内存,是垃圾收集的主要区域(“GC 堆”)。
现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法。可以将堆分成两块:
新生代(Young Generation)
老年代(Old Generation)
堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
方法区:
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。
运行时常量池:
运行时常量池是方法区的一部分。
Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。
除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
GC回收什么
GC主要回收的对象是堆内存的对象和方法区
GC怎么回收
判断对象是否可回收
引用计数算法
在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就加1,当引用失效的时候(变量记为null),计数器的值就减1。但Java虚拟机中没有使用这种算法,这是由于如果堆内的对象之间相互引用,就始终不会发生计数器-1,那么就不会回收。
优点
实现简单,判定高效;
缺点
(A)、很难解决对象之间相互循环引用的问题
public class Count {
private Object instance;
public static void main(String[] args) {
Count c1 = new Count();
Count c2 = new Count();
c1.instance = c2;
c2.instance = c1;
// 断掉引用
c1 = null;
c2 = null;
//垃圾回收
System.gc();
}
}
(B)、并且开销较大,频繁且大量的引用变化,带来大量的额外运算
可达性分析算法
通过一系列"GC Roots"对象作为起始点,开始向下搜索;
搜索所走过和路径称为引用链(Reference Chain);
当一个对象到GC Roots没有任何引用链相连时(从GC Roots到这个对象不可达),则证明该对象是不可用的;
GC Roots对象
Java中,GC Roots对象包括:
(1)、虚拟机栈(栈帧中本地变量表)中引用的对象;
(2)、方法区中类静态属性引用的对象;
(3)、方法区中常量引用的对象;
(4)、本地方法栈中JNI(Native方法)引用的对象;
主要在执行上下文中和全局性的引用;
3、优点
更加精确和严谨,可以分析出循环数据结构相互引用的情况;
4、缺点
实现比较复杂;
需要分析大量数据,消耗大量时间;
分析过程需要GC停顿(引用关系不能发生变化),即停顿所有Java执行线程(称为"Stop The World",是垃圾回收重点关注的问题);
具体回收实现(垃圾收集算法)
标记——清除
在标记阶段,程序会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。
清除阶段,回收标记,并清除对象,进行空链表合并
在分配时,程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block。如果它找到的块等于 size,会直接返回这个分块;如果找到的块大于 size,会将块分割成大小为 size 与 (block - size) 的两部分,返回大小为 size 的分块,并把大小为 (block - size) 的块返回给空闲链表。
它有两点不足:
一个效率问题,标记和清除过程都效率不高;
一个是空间问题,标记清除之后会产生大量不连续的内存碎片(类似于我们电脑的磁盘碎片),空间碎片太多导致需要分配大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。
标记——整理
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
优点:不会产生内存碎片
不足:需要移动大量对象,处理效率比较低。
复制
复制算法是将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
优点:内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效
缺点:内存缩小为了原来的一半,浪费较大。
分代收集
新生代使用:复制算法
老年代使用:标记 - 清除 或者 标记 - 整理 算法
怎么判断要标记
1、强引用
2、软引用
3、虚引用
4、弱引用