内存分配
Java堆内存分配
对象分配遵循的规则
- 对象主要分配在新生代的 Eden 区上。
- 如果启动了本地线程分配缓冲,将按线程优先在 TLAB 上分配。
- 少数情况下也可能会直接分配在老年代中。(新生代放不下的情况下、对象比较大的情况下[几乎占据From or To 区的全部,不如直接复制到老年代]。)
查看对象分配在哪个区域
//-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC
public class TestJVMFirst {
private static final int byteSize = 1024 * 1024;
public static void main(String[] args) {
byte[] bytes = new byte[40 * byteSize]; //40MB内存
}
}
控制台输出
Heap
PSYoungGen total 76288K, used 47514K [0x000000076b000000, 0x0000000770500000, 0x00000007c0000000)
eden space 65536K, 72% used [0x000000076b000000,0x000000076de66878,0x000000076f000000)
from space 10752K, 0% used [0x000000076fa80000,0x000000076fa80000,0x0000000770500000)
to space 10752K, 0% used [0x000000076f000000,0x000000076f000000,0x000000076fa80000)
ParOldGen total 175104K, used 0K [0x00000006c1000000, 0x00000006cbb00000, 0x000000076b000000)
object space 175104K, 0% used [0x00000006c1000000,0x00000006c1000000,0x00000006cbb00000)
Metaspace used 3509K, capacity 4498K, committed 4864K, reserved 1056768K
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 区之间发生大量的内存复制。
配置参数含义
-verbose:gc -XX:+PrintGCDetails 开启GC日志打印
-Xms20 M 设置JVM初始内存为20M
-Xmx20 M 设置JVM最大内存为20M
-Xmn10 M 设置年轻代内存大小为10M
TestMaxObject
//-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:PretenureSizeThreshold=3145728
// 3MB
public class TestMaxObject {
public static void main(String[] args) {
byte[] bytes = new byte[1024*1024*10]; //10MB
}
}
控制台输出
Heap
def new generation total 78656K, used 6995K [0x00000006c1000000, 0x00000006c6550000, 0x0000000716000000)
eden space 69952K, 10% used [0x00000006c1000000, 0x00000006c16d4f68, 0x00000006c5450000)
from space 8704K, 0% used [0x00000006c5450000, 0x00000006c5450000, 0x00000006c5cd0000)
to space 8704K, 0% used [0x00000006c5cd0000, 0x00000006c5cd0000, 0x00000006c6550000)
tenured generation total 174784K, used 10240K [0x0000000716000000, 0x0000000720ab0000, 0x00000007c0000000)
the space 174784K, 5% used [0x0000000716000000, 0x0000000716a00010, 0x0000000716a00200, 0x0000000720ab0000)
Metaspace used 3509K, capacity 4498K, committed 4864K, reserved 1056768K
class space used 387K, capacity 390K, committed 512K, reserved 1048576K
逃逸分析
逃逸分析的基本行为就是分析对象动态作用域。
- 当一个对象在方法中被定义后,它可能被外部方法所引用,称为方法逃逸。
public class TestEscape {
public static Object obj;
public void variableEscape(){
obj = new Object(); //方法逃逸
}
public Object methodEscape(){
return new Object(); //方法逃逸
}
}
- 甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
栈上分配
栈上分配就是把方法中的变量和对象分配到栈上,方法执行完后自动销毁,而不需要垃圾回收的介入,从而提高系统性能。
- 开启逃逸分析(JDK1.8默认开启):就进行栈上分配。
- 关闭逃逸分析:堆分配。
参数含义
-XX:+DoEscapeAnalysis开启逃逸分析(jdk1.8默认开启,其它版本未测试)
-XX:-DoEscapeAnalysis 关闭逃逸分析
// 当关闭逃逸分析:频繁日志输出分配内存失败
-verbose:gc -XX:+PrintGCDetails -XX:-DoEscapeAnalysis 关闭逃逸分析
TestEscape
public class TestEscape {
public static Object obj;
public void variableEscape(){
obj = new Object(); //方法逃逸
}
public Object methodEscape(){
return new Object(); //方法逃逸
}
public static void alloc(){
byte[] b = new byte[2];
b[0] = 1;
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for(int i =0; i < 100000000; i++){
alloc();
}
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
控制台输出
-XX:-DoEscapeAnalysis (关闭逃逸分析,堆在分配)
912 毫秒
-XX:+DoEscapeAnalysis(开启逃逸分析,栈上分配)
5 毫秒