jvm内存中的线程私有区区域划分如下图所示:
java中最常见的问题之一就是堆内存溢出,所以了解jvm堆工作及GC的原理非常重要。jvm堆内存从GC的角度划分可分为:新生代(eden区、survivor form区和survivor to区)和老年代。
- 新生代
新生代也是顾名思义就是用来存放新生的对象。新生代通常占据着堆内存的1/3空间。因为java对象频繁的创建,所以新生代会频繁的触发Minor GC进行垃圾回收。新生区分为Eden区、Survivor form区和Survivor to区。
- Eden区:java新对象的出生地(当然如果新创建的对象占用的内存非常大是,则直接将其分配至老年代),当Eden区中的内存不足时,就会触发Minot GC对新生代区进行一次垃圾回收。
- Survivor To区:用于保留Minor GC中的幸存者。
- Survivor From 区:用于存放上一次 Minor GC中幸存者,并且作为本次Minor GC的被扫描者。
Minor GC过程:Minor GC通常采用复制算法。首先将Eden区和Survivor From 区中存活的对象复制到Survivor To区之中(如果对象的年龄到达了老年代的标准时则赋值到老年代(通常年龄大于15即可));然后清空Eden区和Survivor From 区,最后将 Survivor To区和Survivor From 区互换,原来的 Survivor To区变成下一次的Survivor From 区。
2.老年代
老年代主要存放在程序中生命周期长的对象。老年代因为其中对象比较稳定,所以Major GC不会频繁的执行。在进行Major GC之前通常都会先执行一次Minor GC,Minor GC执行完后可能会有新生代的对象晋升到老年代之中,然后导致老年代的空间不足才触发Major GC。当无法找到足够大的连续空间分配给新创建的较大的对象时也会提前触发一次Major GC进行垃圾回收来腾出空间。当触发Major GC时,GC期间会停止一切线程等待至GC完成。
Major GC过程:因为老年代每次只回收少量的对象,所以Major GC通常推荐采用标记整理算法。首先扫描一次所有的老年代,标记出所有存活的对象和需要回收的对象,将存活的对象移向内存的一端,然后清除边界外的对象。
3.永久代
永久代顾名思义就是就是指永久保存内存的区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候放入永久代。他和存放实例的区域不同,GC不会在主程序运行期间对永久区进行清理。而这也导致了永久区会随着不断增加的Class而膨胀,最终导致OOM异常。所以在JAVA8之中,移除了永久代,用一个叫元数据区的代替了永久代。元空间和永久代之间最大差异就是元空间使用的不是虚拟机中的内存,而是使用本地内存,这样的好处在于其元空间内存的大小仅仅受限于本地内存的大小,这样就可以避免永久代OOM的问题了。