Java对象内存分配原理与布局
当一个对象被创建了,那在JVM中是如何的从一个对象不存在到存到,然后将对象存放在什么地方呢?这次主要来探讨一下Java对象创建的过程。
new关键字创建对象的3个步骤:
1、在堆内存中创建出对象的实例。
当我们用new关键字来创建对象的实例时,JVM首先会检查new这个指令的参数是不是能造常量池中定位成一个类的符号引用,然后再检查该符号引用所对应的类是不是被正常的加载、连接、初始了,如果木有则必须要完成类的加载过程,当事先的准备阶段都结束之后,接着JVM则为该对象分配内存,当对像加载完之后该对象要分配多少内存其实是已经确定的一件事情了。而在Java堆中内存整体来说是分成2部分的,第一部分内存是已经被使用或者说已经被占用的,而第二部分内存则是空闲的可以被使用的,而已经被占用的空间和未被使用的空间又分为两种情况:
第一种情况:在堆内存中已经截然有序的将已使用和未使用的内存空间给分离开了,比如说左侧是已经占用的空间,而右侧是未被占用的空间。中间可以通过一个指针来指向,这种情况下如果新创建的对象则会存在于未被占用的空间中,然后指针发生了一个移动指向了下一个可以被使用的内存空间,对于这种case我们可以称之为指针碰撞(前提是堆中的空间通过一个指针进行分割,一侧是已经被占用的空间,另一侧是未被占用的空间)。
第二种情况:这种Java堆内存并未像第一种情况说得这么理想,而是不归整交织在一起了,这种情况下肯定不能去移动指针这么简单来进行指向了,这时需要记录一个列表用来标识哪些地方是内存已经被使用了的,哪些是未被使用了的,并且还要记录未被使用的大小是多少,这种情况下当要给对象分配内存时,则需要从列表中选出来可以容纳新创建对象大小的空间,然后把新的对象放置在可以容纳的内存当中,并且要修改列表的记录,这种做法则称之为空闲列表(前提是堆内存空间中已被使用与未被使用的空间是交织在一起的,这时,虚拟机就需要通过一个列表来记录哪些空间是可以使用的,哪些空间是已被使用的,接下来找出可以容纳下新创建对象的且未被使用的空间,在此空间存放该对象,同时还要修改列表上的记录)。
FAQ:为啥会有这两种情况呢?其实是跟垃圾收集【未来会专门学到它】器息息相关的,有一些垃圾收集器是带压缩过程,所谓压缩过程是指垃圾收集器在执行一次垃圾回收的时候,除了把真正垃圾的对象给清除掉之外,此时已使用和未使用的内存一定是不连续的,那么它们在做完清除工作之后还要做一次对象的移动操作,也就是将已被使用的和未被使用的分文别类的给排开,此时就可以用指针碰撞的方式来解决对象存放的问题;而有些垃圾收集器在垃圾回收之后就立马结束了,不会对对象进行一个移动操作,从而导致已使用和未被使用的内存交织在一起的,此时就只能用空闲列表的方式来解决对象存放的问题啦。
2、为对象的实例成员变量【而非静态成员变量】赋初值。
这个不多解释了,在之前的类加载中详细说过。
3、将对象的引用返回。
对象在内存中的布局【了解既可】:
对象的内存布局其实就是指一个对象它存放的信息有啥, 总共分为三部分:
1、对象头。
它会存放对象自身的一些运行时的数据信息,比如说一个对象有一个hash码、还有分代的一个信息等,把这些信息都放置在对象头里面。
2、实例数据 (既我们在一个类中所声明的各项信息)。如成员变量。
3、对齐填充(可选),其实就是起到一些点位符的作用,比如说要求8的倍数,如果不够8的话被0等。
引用访问对象的方式:
这个在之前【https://www.cnblogs.com/webor2006/p/9876493.html】已经详细学习过了,其实就是两种形态,回顾一下:
1、使用句柄的方式。
2、使用直接指针的方式。
这两种有啥区别,这里也再贴出来回顾一下,纯之前学的东东:
好,接下来回到咱们熟悉的代码上来,上面一大堆的理论还得由实践将其进行验证,这里编写一个可以在堆空间出现内存溢出异常的代码,具体做法如下:
也就是写一个死循环,不断的往堆中新建MyTest1对象,最终肯定会撑爆JVM的堆空间从而来模拟出堆内存溢出,我们知道JVM是可以有参数来调整截内存空间的,为了让这个程序更快的出现,我们可以手动来修改JVM的参数,如下:
其中还设置了一个当发生内存溢出时来将内存的信息给dump出来,其实就类似于Android中来分析内存也是需要dump内存信息一样,如下:
下面来运行一下,看是不是很快就报内存溢出异常了:
立竿见影嘛,其中内存异常的原因也可以清楚的看到是由于java的堆空间:
其中可以看到dump文件已经创建了:
那咱们在工程中刷新一下,发现貌似木有看到dump文件呀,其实在IntelliJ IDEA中是没有将其显示出来而已,咱们得在目录文件中来查看,瞅下:
那dump文件生成了怎么查看呢,莫要急,下次再继续学~~