我们的对象被分配在哪里
首先来看这么一段代码:
public class Test {
private static Teacher teacher = new Teacher();
public static void main(String[] args) {
load();
}
public static void load() {
User user = new User();
}
}
上面的对象分配如下
首先是静态类变量分配在方法区中,然后在堆新生代创建Teacher,由teacher指向
其次是在main方法中执行 load方法,在java虚拟机栈压栈,创建一个load栈帧,栈帧中包含user局部变量,同时在堆内存中创建User实例对象,由user指向User实例对象
需要注意的是基本所有的对象创建都是在新生代,除非是大对象直接进入老年代
GC分类
gc一般分为miner gc 和full gc
- miner gc:发生在新生代的垃圾回收,速度较快
- full gc: 发生在老年代的垃圾回收,速度很慢
什么时候发生GC
- 什么时候触发miner gc?
可以看到在load方法执行后,出栈,user变量销毁,User对象就没有有变量指向了,成了需要回收的垃圾对象。当新生代里的对象越来越多,都快满了,此时就会触发垃圾回收,把新生代没有人引用的对象给回收掉,释放内存空间。这个过程叫miner gc。 - 什么时候触发full gc呢?
有如下几个条件
- 老年代可用内存小于新生代全部对象大小,没有开启空间担保参数
- 老年代可用内存小于历次新生代GC后进入老年代的平均对象大小
- 新生代Minor GC后存活对象大于Survivor,进入老年代,此时老年代内存不足
- 老年代占比默认超过92%
聊聊GC收集算法
我们GC垃圾回收会如何回收呢?一般有如下几种算法
- 复制算法
简单来说将新生代内存分为两块,S1快用于存放现在存活的对象,发生垃圾回收后就把S1存活的对象复制到S2,清空S1,后续新建的对象分配到S2,下次垃圾回收清空S2如此循环
这种算法的缺点很明显,假如我们给新生代1G的内存空间,那么只有512MB的内存空间是可以用的 - 标记清除算法
这种算法就是我们在新生代中将存活的对象标记出来,然后直接清除,这样看好像是用到了所有内存
这样我们只需要将user对象清除就好了,但是会带来另一个问题:内存碎片,由于这些存活的对象分布不均,产生了许多内存碎片也会造成大量内存浪费
复制算法的优化
复制算法的缺点很明显,我们如何优化呢,将年轻代分为 :Eden区和Survivor区
每次分配对象在Eden区分配,当Eden区满了后就将Eden存活的对象和Survivor存活的对象移到另一个Survivor,这样可以保证使用内存为Eden区和一个Survivor,减少了内存的浪费,同时每次都是清除Eden和一个Survivor,不会产生内存碎片
垃圾收集器
目前主要的垃圾收集器有如下几种
- Serial收集器
使用新生代垃圾回收期,单线程收集,适合用于单核CPU操作系统使用。目前都是多核系统,所以Serial收集器基本已经被淘汰了
- Serial Old收集器
老年代垃圾回收器,也是单线程收集,同Serial收集器一样,不同的是Serial Old还会用到,就是当老年代发生Concurrent Mode Failure,系统就会由CMS垃圾收集器强制切换使用Serial Old收集器
- ParNew收集器
目前用的最多的新生代垃圾收集器,多线程收集,收集线程默认和CPU核数保持一致,如果要设置可通过参数 -XX:ParallelGCThreads设置,采用的算法是优化的复制算法
- CMS收集器
目前使用最多的老年代垃圾收集器,CMS垃圾收集器过程比较复杂包括这五个过程:-
初始标记:这个阶段会Stop the World,作用就是标记处所有的GC Roots直接引用的对象,但是耗时不会太多,因为仅仅标记GC Roots直接引用的对象
-
并发标记:这个阶段不会发生Stop the World,作用是已存在对象进行GC Root追踪,耗时最久,因为需要追踪所有对象是否从根源上被GC Roots引用,但是不用担心,因为这时候系统是可以运行的
-
重新标记:这个阶段会Stop the World,作用就是标记因为并发标记系统运行新增的存活对象和垃圾对象,耗时不多,因为2阶段已经标记大量对象,这里只是标记新增的少量对象
-
并发清除:这个阶段不会Stop the World,作用就是清理标记出来的垃圾对象,耗时会很久,但是系统可以运行,所以不用担心
-
碎片整理:这个阶段也会 Stop the World。默认每次full gc后都会对内存进行整理,因为使用的标记清除算法,会产生内存碎片,所以需要碎片整理。是否需要每次full gc后整理还是full gc几次后整理可由参数 --XX:+UseCMSCompactAtFullCollection 设置
CMS对full gc作了大量优化,就是为了减少full gc时间,但是full gc时间相对minor gc来说时间还是很久,所以一般jvm调优就是尽量减少full gc。其次CMS垃圾收集器也是有一些缺点的:
1.耗用CPU,CMS默认垃圾回收线程数量:(CPU核数 + 3) / 4 -
浮动垃圾:并发清除阶段系统继续运行,然后一些对象进入老年代,同时又变成垃圾对象,CMS只会回收之前标记出来的垃圾对象,这些浮动垃圾需要下次full gc才能回收
-
- G1垃圾收集器
最新的G1垃圾收集器,统一收集新生代和老年代,和传统的几种垃圾收集器有所不同又有所相同,G1垃圾收集器是把堆内存分为很多个Region,其中Region即有新生代,也有老年代,而且Region也不是固定的
G1和CMS后续会对这些垃圾收集器作详细说明