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代表新生代的内存

实验代码如下:

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];
	}
}

控制台输出结果如下:

[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类型数组,代码如下:

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];
	}
}

控制台输出如下:

[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类型对象大小,不妨将代码后三行注释,即变成如下代码:

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];
	}
}

控制台输出如下:

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。如果将第一行代码也注释掉,可以看到控制台输出如下:

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个字节。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本系统的研发具有重大的意义,在安全性方面,用户使用浏览器访问网站时,采用注册和密码等相关的保护措施,提高系统的可靠性,维护用户的个人信息和财产的安全。在方便性方面,促进了校园失物招领网站的信息化建设,极大的方便了相关的工作人员对校园失物招领网站信息进行管理。 本系统主要通过使用Java语言编码设计系统功能,MySQL数据库管理数据,AJAX技术设计简洁的、友好的网址页面,然后在IDEA开发平台中,编写相关的Java代码文件,接着通过连接语言完成与数据库的搭建工作,再通过平台提供的Tomcat插件完成信息的交互,最后在浏览器中打开系统网址便可使用本系统。本系统的使用角色可以被分为用户和管理员,用户具有注册、查看信息、留言信息等功能,管理员具有修改用户信息,发布寻物启事等功能。 管理员可以选择任一浏览器打开网址,输入信息无误后,以管理员的身份行使相关的管理权限。管理员可以通过选择失物招领管理,管理相关的失物招领信息记录,比如进行查看失物招领信息标题,修改失物招领信息来源等操作。管理员可以通过选择公告管理,管理相关的公告信息记录,比如进行查看公告详情,删除错误的公告信息,发布公告等操作。管理员可以通过选择公告类型管理,管理相关的公告类型信息,比如查看所有公告类型,删除无用公告类型,修改公告类型,添加公告类型等操作。寻物启事管理页面,此页面提供给管理员的功能有:新增寻物启事,修改寻物启事,删除寻物启事。物品类型管理页面,此页面提供给管理员的功能有:新增物品类型,修改物品类型,删除物品类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值