垃圾回收,顾名思义就是将分配出去的内存,但不在被使用的内存回收回来,以便能够再次分配。垃圾指的是死亡的对象所占据的堆内存。
GC判断算法:引用计数法与可达性分析
引用计数法:
引用计数法是为每个对象添加一个引用计数器。用来统计指向该对象的引用个数。一旦某个对象的引用计数器为0 ,则表示该对象已经死亡,便可以被垃圾回收了。
一个引用被赋值给某一对象,那么该对象的计数引用加1,如果一个指向某对象的引用被赋值给其他对象,则该对象的引用计数减去1。期间我们需要截取所有的引用更新操作,并且相应的增减目标对应的引用计数器。
除了需要额外的空间来存储计数器,以及繁琐的更新操作外,引用计数法还有一个漏洞,无法处理循环引用的对象,对象a,b相互引用,没有其他引用指向a,b,实际上a,b已经死亡,但是他们的引用计数都不是0,则他们被判断是存活的,这些循环引用的内存空间无法被回收,从而造成内存泄露。
可达性分析法:
现在JVM主流的垃圾收集器采用的是可达性分析法。实质上是将依稀下列GC ROOTS作为初始的存活对象集合,然后从该集合出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程就是标记,最后那些未被探索到的对象就是可以被回收的对象。
GC ROOTS:可以理解为堆外指向堆内的引用。
一般包含如下: 1 JAVA方法栈帧中的局部变量。 2 已加载类的静态变量 。 3 JNI handles 。 4 已加载且未停止的JAVA线程。
可达性分析法解决了引用计数法的循环引用问题。但是时间过程中也存在一些问题,比如多线程环境下,其他线程可能会去更新已经访问过对象的引用,造成误报(引用设置为null)或者漏报(将引用设置为未被访问过的对象)。
stop the world:
传统的垃圾收集算法采用简单粗暴的方法,就是stop the world,停止其他非垃圾回收的线程,知道垃圾回收完成。
stop the world是通过安全点来实现的,当JVM收到stop the world的请求时,它会等待所有线程都达到安全点,才允许stop the world线程进行独占工作。安全点不是让其他线程都停下,而是找到一个稳定执行的状态,在这个状态下,JVM的堆栈不会发生变化,垃圾回收器就可以安全的执行可达性分析了。由于本地代码需要通过JNI的API来完成,所以JVM在API的入口处进行安全点检测,测试是否有其他线程请求停留在安全点里,便可以再必要的时候挂起线程。
垃圾回收的三种方式:
1 标记清除算法。 缺点:是会造成内存碎片,由于JAVA虚拟机的堆中对象必须是连续分布的,因此可能会出现总的空闲内存是足够的,但是无法分配的情况。 CMS垃圾收集器。
2 复制算法。 把内存分成两等分,分别用from和to来维护,用from的指针指向的内存区域来分配内存,当发生GC时,便把存活的对象复制到to指针指向的内存区域中。这种方式解决了空间碎片的问题,但是对空间的使用效率低下,实际使用的对空间只有一半。新生代会用复制算法。
3 标记整理算法 / 压缩算法。把存活的对象聚集到内存的区域的起始位置,从而留下一段连续的内存空间,做法也可以解决空间碎片的问题,但是压缩算法比较消耗性能。
4 分代回收算法: 大部分的JAVA对象只能存活一小段时间,而存活下来的小部分对象则会存活很长一段时间,分代回收由此而来。新生代可以使用耗时较短的垃圾回收算法,比如复制算法,复制少量的对象。而老年代的对象存活率较高,并且也没有多余的内存进行分配,建议使用标记清楚算法或者标记整理算法。 G1收集器使用。
JVM的堆划分:
JVM将堆分为新生代和老年代,其中新生代分为一个eden区域和两个大小相等的survivor区域。
默认情况下JVM采取动态分配的策略,根据对象的生成速率,以及survivir区域的使用情况调整eden和survivor的大小比例。也可以通过-XX:SurvivorRatio来固定这个比例。
当调用new指令时会在eden区域划分出一块作为存储对象的内存。由于堆空间是线程共享的,所以划分堆内存是需要进行同步的,否则可能出现两个对象公用一段内存的情况。
当eden区域的空间内存耗尽时,JVM会触发一次Minor GC收集新生代的垃圾。eden区域和from区域指向的survivor区中存活的对象会被复制到to指向的survivor区域,然后交换from指针和to指针,以保证下一次Minor时,to区域是空的。如果一个对象被复制的次数为15次,那么该对象就会存入老年代。如果单个survivir区域占用达到百分之50,那么较高复制次数的对象也会进入老年代。
内存分配和回收策略:
对象优先在eden区域分配,新生代默认为8比1
大对象直接进入老年代,可以使用-XX:PretenureSizeThreshold设置大对象多大进入老年代,新生代只有Serial和ParNew两种新生代垃圾收集器生效。
新生代的年龄超过15岁进入老年代,-XX:MaxTenuringThreshold=15。
垃圾收集器:
新生代: serial 单线程的新生代垃圾收集器
parnew 多线程的垃圾收集器,默认线程数与cpu相同
parallel scavenge 吞吐量优先的垃圾收集器 吞吐量=运行用户代码/(运行用户代码时间+垃圾收集时间)
老年代: serial old serial的老年代版本
parallel old parallel scavenge的老年代版本
CMS 服务器端的首选。
G1
这里留下CMS收集器和G1收集器的问题,后面补上。