您的位置:首页 > 其它

JVM学习笔记:实例探究垃圾收集机制

2019-03-19 22:06 225 查看

在学习《深入理解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位版本的虚拟机中开启了指针压缩功能,详情可以见这篇博客:

JVM-对象的指针压缩

因此,本应是8位的指针被压缩成了4个字节。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: