1 对象创建流程
设置对象头(Object Header)
初始化零值之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data)和对齐填充(Padding)。
HotSpot虚拟机的对象头包括两部分信息:
1.Mark Word标记字段(32位占4字节,64位占8字节),主要是自身的运行时数据, 如哈希码(HashCode)、GC分代年龄(4bit,所以分代年龄最大不超过15)、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
2.Klass Pointer类型指针(开启压缩占4字节,关闭压缩占8字节),即对象指向它的类元数据(不同于类class对象,class对象是为开发人员通过反射拿到类信息而提供的;而JVM内部拿这些信息是通过类型指针指向类元数据去获取,类元数据是C++的struct对象来实现的)的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
3.数组长度(4字节,只有数组对象才有)
2 对象内存分配流程
一个对象创建在什么位置,jvm会有一个比较细节的流程,根据数据的大小,参数的配置,决定如何创建分配,以及其位置,如下图:
1 首先是否满足栈上分配,如果满足对象入栈,不满足进入2;
2 是否满足直接进入老年代(是否为大对象),如果满足对象进入老年代,不满足进入3;
3 尝试在TLAB上分配,如果满足则从eden区为线程划分出一块TLAB内存,并放入对象,不满足进入4;
4 在eden区分配,之后根据gc情况对象会进入s0、s1、老年代。
2.1 对象在栈上分配
如果JAVA中的对象都在堆上进行分配,当对象没有被引用的时候,需要依靠GC进行回收内存,如果对象数量较多的时候,会给GC带来较大压力,也间接影响了应用的性能。
为了减少临时对象在堆内分配的数量,JVM通过逃逸分析确定该对象不会被外部访问。如果不会逃逸可以将该对象在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。
对象逃逸分析:就是分析对象动态作用域,是JVM编译器的优化。当一个对象在方法中被定义后,它可能被外部方法所引用,就认为该对象逃逸出去了,即对象的作用域逃逸出方法范围。
public User test1() { User user = new User(); ...... return user; } public void test2() { User user = new User(); ...... }
test1方法中的user对象被返回了,这个对象的作用域范围不确定
test2方法中的user对象我们可以确定当方法结束这个对象就可以认为是无效对象了,对于这样的对象我们其实可以将其分配在栈内存里,让其在方法结束时跟随栈内存一起被回收掉。
JVM对于这种情况可以通过开启逃逸分析参数-XX:+DoEscapeAnalysis来优化对象内存分配位置,使其通过标量替换优先栈上分配,JDK7之后默认开启逃逸分析,如果要关闭使用参数-XX:-DoEscapeAnalysis。
标量替换:通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间,这样就不会因为没有一大块连续空间导致对象内存不够分配。开启标量替换参数-XX:+EliminateAllocations,JDK7之后默认开启,如果要关闭使用参数-XX:-EliminateAllocations。
标量与聚合量:标量即不可被进一步分解的量,而JAVA的基本数据类型就是标量(如:int,long等基本数据类型以及 reference类型等),标量的对立就是可以被进一步分解的量,而这种量称之为聚合量。而在JAVA中对象就是可以被进一 步分解的聚合量。
/** * 栈上分配,标量替换 * 代码调用了1亿次alloc(),如果是分配到堆上,大概需要1GB以上堆空间,如果堆空间小于该值,必然会触发GC。 * * 使用如下参数不会发生GC * ‐Xmx15m ‐Xms15m ‐XX:+DoEscapeAnalysis ‐XX:+PrintGC ‐XX:+EliminateAllocations * 使用如下参数都会发生大量GC * ‐Xmx15m ‐Xms15m ‐XX:‐DoEscapeAnalysis ‐XX:+PrintGC ‐XX:+EliminateAllocations * ‐Xmx15m ‐Xms15m ‐XX:+DoEscapeAnalysis ‐XX:+PrintGC ‐XX:‐EliminateAllocations */ public class AllotOnStack { 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); } private static void alloc() { User user = new User(); user.setId(1); } }
结论:栈上分配依赖于逃逸分析和标量替换,前提是对象不会很大。
2.2 对象进入老年代
1.对象大小
如果是大对象,当新生代eden区无法装入时,会直接进入老年代。
JVM有个参数-XX:PretenureSizeThreshold可以设置当对象大小超过多少后,直接进入老年代。这个参数只在 Serial 和ParNew两个收集器下有效。
可以避免为大对象分配内存时的复制操作而降低效率。
2.对象年龄
一般而言,对象首次创建会被放置在新生代的eden区,如果没有GC介入,则对象不会离开eden区。如果对象的年龄达到一定大小,就会自动离开新生代进入老年代,对象年龄是由对象经历数次GC决定的,在新生代每次GC之后如果对象没有被回收则年龄加1。
虚拟机提供了一个参数-XX:MaxTenuringThreshold来控制新生代对象的最大年龄,根据设置MaxTenuringThreshold参数,可以指定新生代对象经过多少次回收后进入老年代,该参数值默认是15。。
import java.util.HashMap; import java.util.Map; public class Test05 { public static void main(String[] args) { //初始的对象在eden区 //参数:-Xmx64M -Xms64M -XX:+PrintGCDetails for(int i=0; i< 5; i++){ byte[] b = new byte[1024*1024]; } //测试进入老年代的对象 // //参数:-Xmx1024M -Xms1024M -XX:+UseSerialGC -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails //-XX:+PrintHeapAtGC Map<Integer, byte[]> m = new HashMap<Integer, byte[]>(); for(int i =0; i <5 ; i++) { byte[] b = new byte[1024*1024]; m.put(i, b); } for(int k = 0; k<20; k++) { for(int j = 0; j<300; j++){ byte[] b = new byte[1024*1024]; } } } }
执行结果:
第一次执行:
Heap PSYoungGen total 18944K, used 6759K [0x00000000feb00000, 0x0000000100000000, 0x0000000100000000) eden space 16384K, 41% used [0x00000000feb00000,0x00000000ff199d70,0x00000000ffb00000) from space 2560K, 0% used [0x00000000ffd80000,0x00000000ffd80000,0x0000000100000000) to space 2560K, 0% used [0x00000000ffb00000,0x00000000ffb00000,0x00000000ffd80000) ParOldGen total 44032K, used 0K [0x00000000fc000000, 0x00000000feb00000, 0x00000000feb00000) object space 44032K, 0% used [0x00000000fc000000,0x00000000fc000000,0x00000000feb00000) Metaspace used 2682K, capacity 4486K, committed 4864K, reserved 1056768K class space used 295K, capacity 386K, committed 512K, reserved 1048576K
第二次执行:
[GC (Allocation Failure) [DefNew: 279476K->5668K(314560K), 0.0038935 secs] 279476K->5668K(1013632K), 0.0047935 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [DefNew: 284561K->5667K(314560K), 0.0041478 secs] 284561K->5667K(1013632K), 0.0041773 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284588K->5667K(314560K), 0.0017715 secs] 284588K->5667K(1013632K), 0.0018017 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284614K->5667K(314560K), 0.0018597 secs] 284614K->5667K(1013632K), 0.0019291 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284631K->5667K(314560K), 0.0018142 secs] 284631K->5667K(1013632K), 0.0018455 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284642K->5667K(314560K), 0.0015024 secs] 284642K->5667K(1013632K), 0.0015366 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284649K->5667K(314560K), 0.0015616 secs] 284649K->5667K(1013632K), 0.0015980 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284654K->5667K(314560K), 0.0019092 secs] 284654K->5667K(1013632K), 0.0019519 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284657K->5667K(314560K), 0.0017078 secs] 284657K->5667K(1013632K), 0.0017419 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284659K->5667K(314560K), 0.0024223 secs] 284659K->5667K(1013632K), 0.0024747 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284660K->5667K(314560K), 0.0015576 secs] 284660K->5667K(1013632K), 0.0015855 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284661K->5667K(314560K), 0.0016333 secs] 284661K->5667K(1013632K), 0.0016640 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284661K->5667K(314560K), 0.0025270 secs] 284661K->5667K(1013632K), 0.0025566 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284662K->5667K(314560K), 0.0015952 secs] 284662K->5667K(1013632K), 0.0016253 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284662K->5667K(314560K), 0.0018967 secs] 284662K->5667K(1013632K), 0.0019325 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 284662K->0K(314560K), 0.0045602 secs] 284662K->5667K(1013632K), 0.0045966 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0020099 secs] 284662K->5667K(1013632K), 0.0020417 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0003294 secs] 284662K->5667K(1013632K), 0.0003715 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0002207 secs] 284662K->5667K(1013632K), 0.0002503 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0004489 secs] 284662K->5667K(1013632K), 0.0005001 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0002128 secs] 284662K->5667K(1013632K), 0.0002389 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0002099 secs] 284662K->5667K(1013632K), 0.0002355 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 314560K, used 33704K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000) eden space 279616K, 12% used [0x00000000c0000000, 0x00000000c20ea030, 0x00000000d1110000) from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000) to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000) tenured generation total 699072K, used 5667K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000) the space 699072K, 0% used [0x00000000d5550000, 0x00000000d5ad8db0, 0x00000000d5ad8e00, 0x0000000100000000) Metaspace used 2683K, capacity 4486K, committed 4864K, reserved 1056768K class space used 295K, capacity 386K, committed 512K, reserved 1048576K
结论:1.大对象直接进入老年代 2.年龄大的对象进入老年代。
2.3 TLAB
Thread Local Allocation Buffer即线程本地分配缓存,是一个线程专用的内存分配区域,即每个线程在Java堆中预先分配一小块内存。JVM使用TLAB来避免多线程内存分配冲突问题,提高了对象分配的效率。TLAB空间一般不会太大,当大对象无法在TLAB上分配时,会直接分配到堆上。
-XX:+UseTLAB 使用TLAB
-XX:+TLABSize 设置TLAB大小
-XX:TLABRefillWasteFraction 设置维护进入TLAB空间的单个对象大小,是一个比例值,默认64,即如果对象大于整个空间的1/64,则在堆创建对象。
-XX:+PrintTLAB 查看TLAB信息
-XX:ResizeTLAB 自调整阈值
public class Test07 { private static void alloc(){ byte[] b = new byte[2]; } public static void main(String[] args) { //TLAB分配 //参数:-XX:+UseTLAB -XX:+PrintTLAB -XX:+PrintGC -XX:TLABSize=102400 -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=100 -XX:-DoEscapeAnalysis -server for(int i=0; i<10000000;i++){ alloc(); } } }
2.4 对象动态年龄判断
当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了。这个规则是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年龄判断机制一般是在minor gc之后触发的。
2.5 老年代空间分配担保机制
2.6 无用的类
方法区主要回收的是无用的类,需要同时满足下面3个条件:
-
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
-
加载该类的 ClassLoader 已经被回收。
-
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。