内存分配
Java 堆内存分配
对象分配遵循的规则
- 对象主要分配在新生代的 Eden 区上。
- 如果启动了本地线程分配缓冲,将按线程优先在 TLAB 上分配。
- 少数情况下也可能会直接分配在老年代中。(新生代放不下的情况下、对象比较大的情况下[几乎占据 From or To 区的全部,不如直接复制到老年代]。)
查看对象分配在哪个区域
1//-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC
2
3public class TestJVMFirst {
4 private static final int byteSize = 1024 * 1024;
5 public static void main(String[] args) {
6 byte[] bytes = new byte[40 * byteSize]; //40MB内存
7 }
8}
控制台输出
1Heap
2 PSYoungGen total 76288K, used 47514K [0x000000076b000000, 0x0000000770500000, 0x00000007c0000000)
3 eden space 65536K, 72% used [0x000000076b000000,0x000000076de66878,0x000000076f000000)
4 from space 10752K, 0% used [0x000000076fa80000,0x000000076fa80000,0x0000000770500000)
5 to space 10752K, 0% used [0x000000076f000000,0x000000076f000000,0x000000076fa80000)
6 ParOldGen total 175104K, used 0K [0x00000006c1000000, 0x00000006cbb00000, 0x000000076b000000)
7 object space 175104K, 0% used [0x00000006c1000000,0x00000006c1000000,0x00000006cbb00000)
8 Metaspace used 3509K, capacity 4498K, committed 4864K, reserved 1056768K
9 class space used 387K, capacity 390K, committed 512K, reserved 1048576K
新生代与老年代的垃圾收集
- 新生代 GC (Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度老年代
- GC (Major GC/ Full GC):指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 Parallel Scavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程)。Major GC 的速度一般会比 Minor GC 慢 10 倍以上。
大对象的分配
所谓的大对象是指,需要大量连续内存空间的 Java 对象,最典型的大对象就是那种很长的字符串以及数组。
虚拟机提供了一个-XX: PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制。
配置参数含义
1-verbose:gc -XX:+PrintGCDetails 开启GC日志打印
2-Xms20 M 设置JVM初始内存为20M
3-Xmx20 M 设置JVM最大内存为20M
4-Xmn10 M 设置年轻代内存大小为10M
TestMaxObject
1//-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:PretenureSizeThreshold=3145728
2// 3MB
3public class TestMaxObject {
4 public static void main(String[] args) {
5 byte[] bytes = new byte[1024*1024*10]; //10MB
6 }
7}
控制台输出
1Heap
2 def new generation total 78656K, used 6995K [0x00000006c1000000, 0x00000006c6550000, 0x0000000716000000)
3 eden space 69952K, 10% used [0x00000006c1000000, 0x00000006c16d4f68, 0x00000006c5450000)
4 from space 8704K, 0% used [0x00000006c5450000, 0x00000006c5450000, 0x00000006c5cd0000)
5 to space 8704K, 0% used [0x00000006c5cd0000, 0x00000006c5cd0000, 0x00000006c6550000)
6 tenured generation total 174784K, used 10240K [0x0000000716000000, 0x0000000720ab0000, 0x00000007c0000000)
7 the space 174784K, 5% used [0x0000000716000000, 0x0000000716a00010, 0x0000000716a00200, 0x0000000720ab0000)
8 Metaspace used 3509K, capacity 4498K, committed 4864K, reserved 1056768K
9 class space used 387K, capacity 390K, committed 512K, reserved 1048576K
逃逸分析
逃逸分析的基本行为就是分析对象动态作用域。
- 当一个对象在方法中被定义后,它可能被外部方法所引用,称为方法逃逸。
1public class TestEscape {
2 public static Object obj;
3
4 public void variableEscape(){
5 obj = new Object(); //方法逃逸
6 }
7
8 public Object methodEscape(){
9 return new Object(); //方法逃逸
10 }
11}
- 甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
栈上分配
栈上分配就是把方法中的变量和对象分配到栈上,方法执行完后自动销毁,而不需要垃圾回收的介入,从而提高系统性能。
- 开启逃逸分析(JDK1.8 默认开启):就进行栈上分配。
- 关闭逃逸分析:堆分配。
参数含义
1-XX:+DoEscapeAnalysis开启逃逸分析(jdk1.8默认开启,其它版本未测试)
2-XX:-DoEscapeAnalysis 关闭逃逸分析
3
4// 当关闭逃逸分析:频繁日志输出分配内存失败
5-verbose:gc -XX:+PrintGCDetails -XX:-DoEscapeAnalysis 关闭逃逸分析
TestEscape
1public class TestEscape {
2
3 public static Object obj;
4
5 public void variableEscape(){
6 obj = new Object(); //方法逃逸
7 }
8
9 public Object methodEscape(){
10 return new Object(); //方法逃逸
11 }
12
13 public static void alloc(){
14 byte[] b = new byte[2];
15 b[0] = 1;
16 }
17
18 public static void main(String[] args) {
19 long start = System.currentTimeMillis();
20 for(int i =0; i < 100000000; i++){
21 alloc();
22 }
23 long end = System.currentTimeMillis();
24 System.out.println(end-start);
25 }
26}
控制台输出
1-XX:-DoEscapeAnalysis (关闭逃逸分析,堆在分配)
2 912 毫秒
3-XX:+DoEscapeAnalysis(开启逃逸分析,栈上分配)
4 5 毫秒