大对象分配是什么?
- 所谓的⼤对象是指,需要⼤量连续内存空间的 Java 对象,最典型的⼤对象就是那种很⻓的字符串以及数组
- 虚拟机提供了⼀个-XX: PretenureSizeThreshold 参数,令⼤于这个设置值的对象直接在⽼年 代分配。这样做的⽬的是避免在 Eden 区及两个 Survivor 区之间发⽣⼤量的内存复制
实战代码演练
public class A{
public static void main(String[] args) {
byte[] bytes = new byte[1024*1024*5];
}
}
接着右键项目或文件——Run As——Run Configurations。点击Arguments,在VM arguments中填写
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -
XX:PretenureSizeThreshold=3145728
参数解释
-verbose:gc -XX:+PrintGCDetails 开启GC⽇志打印
-Xms20 M 设置JVM初始内存为20M
-Xmx20 M 设置JVM最⼤内存为20M
-Xmn10 M 设置年轻代内存⼤⼩为10M
-XX: PretenureSizeThreshold大小为3M 令⼤于这个设置值的对象直接在⽼年 代分配。这样做的⽬的是避免在 Eden 区及两个 Survivor 区之间发⽣⼤量的内存复制
运行代码后
补充:
GC日志说明:
GC打印时间: [垃圾回收类型回收时间: [收集器名称: 年轻代回收前占用大小->年轻代回收后占用大小(年轻代当前容量), 年轻代局部GC时JVM暂停处理的时间] 堆空间GC前占用的空间->堆空间GC后占用的空间(堆空间当前容量) ,GC过程中JVM暂停处理的时间]。
垃圾回收类型:分为GC和Full GC.
GC一般为堆空间某个区发生了垃圾回收,
Full GC基本都是整个堆空间及持久代发生了垃圾回收,通常优化的目标之一是尽量减少GC和Full GC的频率。
收集器名称:一般都为收集器的简称或别名,通过收集器名称基本都能判断出那个区发生了GC。
DefNew:年轻代(新生代)发生了GC (若为DefNew可知当前JVM年轻代使用的串行收集器)
ParNew:年轻代(新生代)发生了GC (若为ParNew可知当前JVM年轻代使用了并行收集器)
Tenured:老年代发生了GC
Perm:持久代发生了GC
逃逸分析
逃逸分析的基本行为就是分析对象的动态作用域:当一个对象在方法中被定义后,它可能被外部的方法所引用,例如作为方法返回值或者被当作调用参数传入其他方法中,称为方法逃逸。甚至还有可能被其他线程访问到,例如赋值给类变量或者可以在其他线程中访问的实例变量,称为线程逃逸。
如果能够证明一个对象不会逃逸到线程或者方法之外,这就是其他方法或者线程无法通过任何途径访问到这个实例,则可能对这个变量进行一些高效的优化:
栈上分配:如果确定一个对象不会逃逸出方法之外,就让这个对象在栈上分配内存,那么这个对象所占用的内存空间就会随着栈帧的出栈而被回收。
同步消除:如果确定一个对象不会逃逸出线程之外,即无法被其他线程访问到,那么这个变量在进行读写操作时一定不会有竞争,对这个变量的同步措施也可以消除掉。
标量替换:标量就是不可再被拆分的变量,例如Java中的基本数据类型和reference类型,都不可被拆分,它们就可以被称之为标量。相对的,如果一个变量可以被分解,那么它就称为聚合量,Java中的对象就是最典型的聚合量。如果逃逸分析证明一个变量不会被外部访问,并且这个对象可以被分解的,那么程序真正执行的时候可能不会创建这个对象,而改为直接创建这个对象若干个被这个方法使用到的成员变量来代替。对象拆分以后,除了有很大的概率让对象的成员变量在栈上分配和读写以外,还可以为后续进一步的优化创建条件。
结论
至此,我们就知道了,对象不仅可以在Java堆中分配内存,如果该对象被逃逸分析确定不会逃逸出方法之外,那么这个对象就可以在栈中分配内存了。