GC垃圾回收算法
GC的意思是java垃圾回收,主要由两个步骤组成
- 使用某种算法寻找需要回收的对象
- 使用某种算法回收垃圾
垃圾回收主要针对的是堆中的对象,有变量引用的对象表示是存活的,没有一个变量引用的对象就是需要回收的对象。
寻找需要回收的对象
1.引用计数法
引用计数法原理很简单,就是有对象引用你,数就加一,当对象不再引用你的时候,数就减一。当数为0的时候就可以回收。
无法解决循环引用问题
循环引用就是 A -> B B -> A
这样的情况如果想释放A,就得B要向A发起release请求,而B要向A发起release请求,就得执行deallocate,要执行deallocate就得收到A向B发出的release请求,双方就都等待对方的release请求而导致陷入死循环,只要对象持有关系在形成环就存在了循环引用的问题,所以JVM不使用引用计数法。
引用计数法效率高,但是无法解决循环引用的问题,而且每个对象都要有一个计数器,增加了程序执行的成本。
2. 根搜索算法
根搜索算法,是有被标记为GCroots的对象出发,不断地寻找他们的引用节点,寻找并标记所有的存活的对象。
GCroots对象包括:
- 栈帧中的本地变量表
- 方法区中的常量引用的对象
- 本地方法栈中JNI(Native方法)的引用对象
没有被根搜索算法标记的对象就是可回收的对象。
注意点
- 根搜索算法开始标记之前,要暂停应用线程 stop the world,因为程序如果是活动的,那么变量和对象之间的引用关系是不确定的,那么无法正确的标记哪些对象是存活的,所以出发STW,让JVM去回收垃圾称之为安全点。
- stw时间的长短取决于存活对象的多少
- 第一次遍历对象图没有被标记的对象,会被放入一个队列中,稍后GC会对这些对象进行一次小规模的标记,如果还是没有变量引用这些对象就会被回收掉
- 判断对象可达是根据的是强引用
回收垃圾对象的算法
标记清除算法
使用根搜索算法得到不需要的对象之后,直接清理掉对象所在的内存空间,并把这块内存放入空闲表。缺点就是多次标记清理算法之后内存中的碎片越来越多,之后无法分配大对象。如果无法分配只能再次出发GC操作。
标记整理算法
把所有存活的对象往一个方向移动,这样就不会有内存碎片产生,缺点就是GC的时间会增长,因为需要移动存活的对象,并更新他们的引用地址。
复制算法
该算法把内存空间分为两块,一块对象面用于分配新的对象,一块空闲面是在触发GC之后,把存活的对象都移到空闲面上,把对象面的内存一次性都清除掉。这样之后对象面和空闲面就互换了。
优点是 标记和复制可以同时进行,每次对整块内存进行回收,效率高。只需要移动栈顶指针重新分配内存。没有内存碎片。
缺点是 存活对象很多的时候,需要的时间很多。分配新对象的空间减少了。
java 堆内存
java堆内存基于Generation算法,分为新生代,老生代和持久代。
新生代
新生代分为 eden和survivor0和survivor1区 内存空间比例按照8:1:1比例分配。
新对象放在 eden区,如果eden区满了以后就会触发scavenge GC 把 eden区存活的对象放在survivor0中,清空eden区,如果survivor0区也满了,就把eden 和 suivivor0区存活的对象放入survivor1 区,如此反复,当survivor1区的对象也满了,就会在存活对象中寻找年龄 > 15(在一次 scavenge GC存活下来的对象年龄 + 1)的对象,把它放入老年代。
新生代使用的是复制算法
年老代
老年代存放在年轻代中经过15次GC之后存活的对象,老年代对象也满了之后会触发FULL GC。由于年老代的内存空间是新生代的要大近2陪,所以大对象的分配一般都是直接分配到年老代。FULL GC会对三个年代的区域都进行回收。因此要减少FULL GC的次数。
有如下原因可能导致Full GC:
- 年老代(Tenured)被写满;
- 持久代(Perm)被写满;
- System.gc()被显示调用;
年老代由于存活的对象较多,所以不适合使用复制算法,一般是使用标记清除或者标记处理算法。根据具体的垃圾回收器来确定那种垃圾回收算法
持久代
持久代存放的是class文件,静态方法,常量还有JVM自己的反射对象等。永久代空间在Java SE8特性中已经被移除。取而代之的是元空间(MetaSpace)。元空间的大小受限于本地内存限制(32或者64位的虚拟内存大小)。FULL GC的时候就不会考虑到这块区域,不好的地方就是如果存在内存泄漏,元空间的内存会越来越大,导致机器内存不足。
垃圾收集器
有的是基于单线程的垃圾回收器,有的是基于多线程的垃圾回收器,不同的垃圾回收器可能基于不同的垃圾回收算法。
内存泄漏
静态变量是不会被垃圾回收器回收的,当hashmap,list等集合作为静态变量,往里面添加的所有对象都不会被回收。从而导致内存泄漏。
Static Vector v = new Vector();
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
这个V对象和100个o对象都不会被回收,从而导致内存泄漏。
还有各种连接使用完了未关闭就会导致内存泄漏,比如数据库连接,网络连接,IO连接没有使用close方法,就不会被GC回收导致内存泄漏。