1. jvm内存划分和内存回收机制

Java中占用CPU的单元都是线程,整个jvm是一个进程。如果jvm挂掉的话,很可能是OOM,OOM是发生在线程上的,可能使发生OOM的线程退出,导致java进程异常。栈溢出StatckOverFlowError并不会导致jvm挂掉。

线程有自己独立的内存:本地方法栈(默认1MB 64位jvm)、程序计数器。new出来的对象存储在堆,由所有线程共享,除了堆,还有Perm去或Metadata区,都是分享的内存区。它们的引用关系如图:

JVM内存分为年轻代(含两个Survivor存活区)和老年区,使用过程如下:

  1. 绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快

  2. 当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的);

  3. 此后,每次Eden区满了,就执行一次Minor GC,并将剩余的对象都添加到Survivor0;

  4. 当Survivor0也满的时候,将其中仍然活着的对象直接复制到Survivor1,以后Eden区执行Minor GC后,就将剩余的对象添加Survivor1(此时,Survivor0是空白的)。

  5. 当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代)之后,仍然存活的对象(其实只有一小部分,比如,我们自己定义的对象),将被复制到老年代

这种垃圾回收的方式就是著名的停止-复制(Stop-and-copy)清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中)

如果对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。

可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。解决的方法是,年老代中维护一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。

Stop-and-copy的清理方法适用于年轻代到老年代的转换,当老年代满到一定程度触发Full GC时,会使用标记-整理算法进行清理。现在用的多的是多线程的CMS算法,CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力于获取最短回收停顿时间,使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。

文档更新时间: 2020-12-15 21:59   作者:nick