一、JVM内存分配与回收
1、Minor GC和Full GC 有什么不同
Minor GC/Young GC指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
Major GC/Full GC:一般会回收老年代,年轻代,方法区的垃圾, Major GC的速度一般会比Minor GC的慢10倍以上
2、对象优先在Eden区分配
大多数情况下,对象在新生代中Eden区分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
// 添加运行JVM参数:‐XX:+PrintGCDetails
public class GCTest {
public static void main(String[] args) throws InterruptedException {
}
}
运行结果如下:
Heap
PSYoungGen total 38400K, used 4662K [0x00000000d5d80000, 0x00000000d8800000, 0x0000000100000000)
eden space 33280K, 14% used [0x00000000d5d80000,0x00000000d620d8f0,0x00000000d7e00000)
from space 5120K, 0% used [0x00000000d8300000,0x00000000d8300000,0x00000000d8800000)
to space 5120K, 0% used [0x00000000d7e00000,0x00000000d7e00000,0x00000000d8300000)
ParOldGen total 87552K, used 0K [0x0000000081800000, 0x0000000086d80000, 0x00000000d5d80000)
object space 87552K, 0% used [0x0000000081800000,0x0000000081800000,0x0000000086d80000)
Metaspace used 3484K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 381K, capacity 388K, committed 512K, reserved 1048576K
结论: 即使程序什么也没做,新生代也会使用至少几兆内存。
// 添加运行JVM参数:‐XX:+PrintGCDetails
public class GCTest {
public static void main(String[] args) throws InterruptedException {
byte[] byte1 = new byte[25000*1024];
}
}
运行结果如下:
Heap
PSYoungGen total 38400K, used 29662K [0x00000000d5d80000, 0x00000000d8800000, 0x0000000100000000)
eden space 33280K, 89% used [0x00000000d5d80000,0x00000000d7a77900,0x00000000d7e00000)
from space 5120K, 0% used [0x00000000d8300000,0x00000000d8300000,0x00000000d8800000)
to space 5120K, 0% used [0x00000000d7e00000,0x00000000d7e00000,0x00000000d8300000)
ParOldGen total 87552K, used 0K [0x0000000081800000, 0x0000000086d80000, 0x00000000d5d80000)
object space 87552K, 0% used [0x0000000081800000,0x0000000081800000,0x0000000086d80000)
Metaspace used 3484K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 381K, capacity 388K, committed 512K, reserved 1048576K
结论: 对象优先分配在新生代中Eden区(前提条件是Eden区能够容纳被分配对象)
public class GCTest {
public static void main(String[] args) throws InterruptedException {
byte[] byte1,byte2;
byte1 = new byte[25000*1024];
byte2 = new byte[10000*1024];
}
}
运行结果如下:
[GC (Allocation Failure) [PSYoungGen: 28996K->1000K(38400K)] 28996K->26000K(125952K), 0.0959246 secs] [Times: user=0.09 sys=0.00, real=0.10 secs]
Heap
PSYoungGen total 38400K, used 11666K [0x00000000d5d80000, 0x00000000da880000, 0x0000000100000000)
eden space 33280K, 32% used [0x00000000d5d80000,0x00000000d67ea800,0x00000000d7e00000)
from space 5120K, 19% used [0x00000000d7e00000,0x00000000d7efa020,0x00000000d8300000)
to space 5120K, 0% used [0x00000000da380000,0x00000000da380000,0x00000000da880000)
ParOldGen total 87552K, used 25000K [0x0000000081800000, 0x0000000086d80000, 0x00000000d5d80000)
object space 87552K, 28% used [0x0000000081800000,0x000000008306a010,0x0000000086d80000)
Metaspace used 3203K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 350K, capacity 388K, committed 512K, reserved 1048576K
结论: 给byte2分配内存的时候,Eden区没有足够空间进行分配,虚拟机将会发起一次Minor GC,GC期间虚拟机又发现byte1无法存入Survior空间,所以只好把新生代中存活的对象部分转移到老年代,部分可能还会放在Survivor区,老年代上的空间足够存放byte1,所以不会出现Full GC
3、大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如字符串、数组等)。JVM参数-XX:PretenureSizeThreshold可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在Serial和ParNew两个收集器下有效。
// 添加运行JVM参数:-XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC
public class GCTest {
public static void main(String[] args) throws InterruptedException {
byte[] byte1 = new byte[25000*1024];
}
}
运行结果如下:
Heap
def new generation total 39296K, used 4900K [0x0000000081800000, 0x00000000842a0000, 0x00000000abaa0000)
eden space 34944K, 14% used [0x0000000081800000, 0x0000000081cc92d8, 0x0000000083a20000)
from space 4352K, 0% used [0x0000000083a20000, 0x0000000083a20000, 0x0000000083e60000)
to space 4352K, 0% used [0x0000000083e60000, 0x0000000083e60000, 0x00000000842a0000)
tenured generation total 87424K, used 25000K [0x00000000abaa0000, 0x00000000b1000000, 0x0000000100000000)
the space 87424K, 28% used [0x00000000abaa0000, 0x00000000ad30a010, 0x00000000ad30a200, 0x00000000b1000000)
Metaspace used 3484K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 381K, capacity 388K, committed 512K, reserved 1048576K
结论: byte1对象直接进入了老年代
优点: 为了避免为大对象分配内存时的复制操作而降低效率。
4、长期存活的对象将进入老年代
虚拟机采用了分代收集的思想来管理内存,内存回收时能识别哪些对象应放在新生代,哪些对象应放在老年代。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。如果对象在Eden区出生并经过第一次Minor GC后仍然能够存活,并且能被Survivor区容纳,将被移动到Survivor区,并将对象年龄设为1。对象在Survivor区中每经过一次Minor GC,年龄就增加1,当它的年龄增加到一定程度(默认为15岁),就会被移到老年代中。对象被移到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold来设置。
5、对象动态年龄判断
当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总 大小大于这块Survivor区域内存大小的50%,那么此时大于等于这批对象年龄最 大值的对象,就可以直接进入老年代了,例如Survivor区域里现在有一批对象, 年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就 会把年龄n以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活 的对象,尽早进入老年代。对象动态年龄判断机制一般是在minor gc之后触发 的。