JVM对象创建与内存分配
前言
在我们创建对象时的一个流程是怎样的,创建的对象又应该在哪里分配给他内存,下面让我们一起来看一下吧。
对象创建的流程
在我们创建一个对象时,主要经历以下几个阶段:
- 类加载检查:在分配内存之前,类必须要先加载,因此进行类加载检查。类加载就是将字节码文件读入到JVM。
- 分配内存:一般对象都是在堆中分配内存。那么内存是如何分配的,有两种分配内存的方法:
+ 指针碰撞:适用于内存是规整的,也就是说已分配与未分配是分开的,在分配内存时,顺序分配下去即可。
+ 空闲列表:适用于内存是不规整的,内存布局比较分散,空闲内存会有一个列表进行维护,分配内存时在列表中找出合适的内存分配即可。以上分配方式在并发环境下,会出现一些问题,试想一下,在同一时间恰有多个对象指向了同一块内存,这块内存到底改分配给谁?针对上述问题,我们也有解决方案:
+ CAS(compare and swap):CAS会保证分配内存的原子性,给对象分配内存之前先看看这个内存跟以前是否一样,一样就分配,不一样就说明被分配出去了。
+ 本地线程缓冲栈:给每个线程分配一小块区域,每个线程只能在自己的区域里面分配内存。
- 赋默认值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。
- 设置对象头:一个对象主要由对象头(Header)、 实例数据(Instance Data)和对齐填充(Padding)组成,对象头主要结构如下:
32位对象头
- 执行init方法:为属性赋予真正的值。
对象内存分配
说完对象创建的流程,下面我们一起看一下对象是如何进行内存分配的。
- 逃逸分析与栈中分配:一般来说,对象通常是分配在堆中的,但是也有例外的情况,那就是产生了逃逸分析,什么是逃逸分析呢,一个对象在方法中被定义后,永远不会被其他外部方法所引用就是产生了逃逸,对于产生逃逸的对象我们没有必要在堆中给他分配内存,直接在栈中只为它的成员变量分配内存即可,也就是栈中分配,因为不需要被引用。例如下面这个对象:
1 | csharp复制代码public void test() { |
User就产生了逃逸,因为在这个方法中没有返回任何对象,所以User不会被其他外部对象引用。
- 对象直接进入老年代:让对象直接进入老年代只有一个目的,为了避免为大对象分配内存时的复制操作而降低效率。对象在以下几种情况会直接进入老年代。
+ 大对象直接进入老年代
+ 长期存活的对象将进入老年代
+ 对象动态年龄判断,一批对象的总大小大于这块Survivor区域内存大小的50%,那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了。
- 对象在Eden区分配:大多数情况下,对象在新生代中 Eden 区分配。
本文转载自: 掘金