JVM学习笔记:实例探究垃圾收集机制
在学习《深入理解JAVA虚拟机》这本书的过程中,记录一些理解与实验过程,欢迎交流讨论。
实验中使用的虚拟机为Java HotSpot(TM) 64-Bit Server VM版本 25.202-b08,垃圾收集器为PS MarkSweep + PS Scavenge
1.1 对象优先在Eden区分配
对象在新生代Eden区中分配,当Eden区没有空间进行分配时,虚拟机则发起一次Minor GC。
为了明显地看到堆内存变化过程,将虚拟机参数设置如下:
-Xms20M -Xmx20M -Xmn10M -verbose:gc -XX:+PrintGCDetails
其中-Xms代表初始堆大小,-Xmx代表堆的最大内存,-Xmn代表新生代的内存
实验代码如下:
[code]public class Main { public static void main(String[] args) { final int _1MB = 1024 * 1024; byte[] b1 = new byte[2 * _1MB]; byte[] b2 = new byte[2 * _1MB]; byte[] b3 = new byte[2 * _1MB]; byte[] b4 = new byte[2 * _1MB]; } }
控制台输出结果如下:
[code][GC (Allocation Failure) [PSYoungGen: 7294K->712K(9216K)] 7294K->6864K(19456K), 0.0029718 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Ergonomics) [PSYoungGen: 712K->0K(9216K)] [ParOldGen: 6152K->6691K(10240K)] 6864K->6691K(19456K), [Metaspace: 2673K->2673K(1056768K)], 0.0042844 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap PSYoungGen total 9216K, used 2130K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 26% used [0x00000000ff600000,0x00000000ff814930,0x00000000ffe00000) from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 10240K, used 6691K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 65% used [0x00000000fec00000,0x00000000ff288ea8,0x00000000ff600000) Metaspace used 2679K, capacity 4486K, committed 4864K, reserved 1056768K class space used 295K, capacity 386K, committed 512K, reserved 1048576K
可以看到,新建的byte型数组优先在Eden区中分配内存,每个数组元素分配一个字节的内存,因此新建b3数组以后,Eden区中已有6MB的数组数据以及其他初始数据,所以在新建b4数组时,系统判定新生代已没有足够空间可用,便发起了一次Minor GC,将6MB数组元素移动到了老年代当中,而b4数组则被分配在了Eden区中。
1.2 大对象直接进入老年代
接下去把byte数组全部改为Byte类型数组,代码如下:
[code]public class Main { public static void main(String[] args) { final int _1MB = 1024 * 1024; Byte[] b1 = new Byte[2 * _1MB]; Byte[] b2 = new Byte[2 * _1MB]; Byte[] b3 = new Byte[2 * _1MB]; Byte[] b4 = new Byte[2 * _1MB]; } }
控制台输出如下:
[code][GC (Allocation Failure) [PSYoungGen: 1150K->728K(9216K)] 9342K->8928K(19456K), 0.0125107 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 728K->648K(9216K)] 8928K->8848K(19456K), 0.0024487 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 648K->0K(9216K)] [ParOldGen: 8200K->8739K(10240K)] 8848K->8739K(19456K), [Metaspace: 2673K->2673K(1056768K)], 0.0058229 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(9216K)] 8739K->8739K(19456K), 0.0006323 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(9216K)] [ParOldGen: 8739K->8727K(10240K)] 8739K->8727K(19456K), [Metaspace: 2673K->2673K(1056768K)], 0.0057499 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at Main.main(Main.java:9) Heap PSYoungGen total 9216K, used 246K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 3% used [0x00000000ff600000,0x00000000ff63d890,0x00000000ffe00000) from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 10240K, used 8727K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 85% used [0x00000000fec00000,0x00000000ff485eb8,0x00000000ff600000) Metaspace used 2705K, capacity 4486K, committed 4864K, reserved 1056768K class space used 299K, capacity 386K, committed 512K, reserved 1048576K
可以看到,内存直接发生了溢出,证明Byte类型对象的占用内存远远大于byte类型的数据,为了探究Byte类型对象大小,不妨将代码后三行注释,即变成如下代码:
[code]public class Main { public static void main(String[] args) { final int _1MB = 1024 * 1024; Byte[] b1 = new Byte[2 * _1MB]; // Byte[] b2 = new Byte[2 * _1MB]; // Byte[] b3 = new Byte[2 * _1MB]; // Byte[] b4 = new Byte[2 * _1MB]; } }
控制台输出如下:
[code]Heap PSYoungGen total 9216K, used 1315K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 16% used [0x00000000ff600000,0x00000000ff748c10,0x00000000ffe00000) from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) ParOldGen total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400010,0x00000000ff600000) Metaspace used 2676K, capacity 4486K, committed 4864K, reserved 1056768K class space used 296K, capacity 386K, committed 512K, reserved 1048576K
可以看到,新生代中的空间只被使用了1MB左右,而老年代中的内存被占用了8MB。如果将第一行代码也注释掉,可以看到控制台输出如下:
[code]Heap PSYoungGen total 9216K, used 1151K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 14% used [0x00000000ff600000,0x00000000ff71fc50,0x00000000ffe00000) from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) ParOldGen total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000) Metaspace used 2679K, capacity 4486K, committed 4864K, reserved 1056768K class space used 295K, capacity 386K, committed 512K, reserved 1048576K
因此,新生代中1MB左右的内存是被系统初始的对象所占用的,而后面新建立的对象并未进入新生代,而是直接进入了老年代中。
发生这一现象的原因,是虚拟机为了避免对一些占用内存较大的“短命”对象进行频繁的Minor GC 而影响性能,因而对较大的对象,虚拟机不会将其放入新生代,而是直接在老年代中分配其内存。对于Serial和ParNew两款垃圾收集器,可以直接设置虚拟机中的PretenureSizeThreshold参数来设置其阈值,超过这一阈值的对象将被放入老年代;而对于 PS Scavenge收集器,则无需配置这一参数。
由这一段代码的结果可以推测,Byte型数组的元素(类型为引用)大小为byte类型数组元素的4倍。一个byte类型数据占用内存为1字节,因此一个Byte对象的引用类型占用4字节的空间。但是在64位虚拟机中,引用类型对象的大小应该为8字节,为什么会只占用4字节呢?这是因为虚拟机为了节省内存,在64位版本的虚拟机中开启了指针压缩功能,详情可以见这篇博客:
因此,本应是8位的指针被压缩成了4个字节。
- JVM学习笔记--垃圾收集机制(1)
- JVM学习三.Java垃圾收集机制
- jvm学习笔记2:jvm垃圾收集器与垃圾收集算法
- JVM学习笔记二 :垃圾收集的过程分析Eden->Survivor->Tenured
- jvm学习笔记(5)垃圾收集器介绍
- JVM深入学习笔记四:JVM垃圾收集和内存分配
- JVM垃圾回收机制学习笔记
- java学习笔记15:垃圾回收机制(Garbage Collection)、垃圾回收原理和算法、通用的分代垃圾回收机制、JVM调优和Full GC、开发中容易造成内存泄露的操作
- JVM学习四.垃圾收集机制
- 深入理解JVM虚拟机学习笔记(二)垃圾收集器与垃圾收集算法
- JVM垃圾收集算法学习笔记
- 深入理解JVM学习笔记:第3章 垃圾收集器与内存分配策略
- JAVA程序员养成计划之JVM学习笔记(2)-垃圾收集管理
- JVM学习笔记2 垃圾收集器与内存分配策略
- JVM学习笔记3-垃圾收集算法
- JVM学习笔记三:垃圾收集器与内存分配策略
- JavaWeb学习笔记—JVM的垃圾回收机制(转载)
- 深入理解JVM学习笔记-垃圾收集器和内存分配策略
- JVM阅读笔记之垃圾收集机制
- Java学习笔记之垃圾收集机制和原理