对象创建过程、内存分配详解

2021/4/16 7:25:27

本文主要是介绍对象创建过程、内存分配详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、对象创建流程分析
 在这里插入图片描述

1、类加载检查
jvm遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
new指令对应到语言层面上讲是,new关键词、对象克隆、对象序列化等 
2 分配内存
 类加载完成后,所占用Eden内存大小基本确定(类的属性所占大小等)
 分配内存方法:

- 指针碰撞(默认指针碰撞)
   如果堆中的内存是规整的(即所有用过的内存在一边,没用过的内存在另一边,中间有一个指正分割。分配内存只需移动指正即可)

- 空闲列表
如果堆中的内存不是规整的,即用过的内存和没用过的内存是打乱交叉,此时没法用指正碰撞。此时jvm会维护一个空闲列表,记录那些内存是可用的,分配内存时,会在空闲列表找到一个足够大的内存为这个对象分配,然后更新空闲列表。

		上述分配内存单线程没问题,当并发多线程来争抢指针/空闲列表就会存在问题
jvm提供内存分配解决并发问题
  • Cas(Compare and sweep)
    jvm采用CAS配上失败重试的方式保证更新操作的原子性来对分配内存空间的动作进行同步处理

  • 本地线程分配缓冲(Thread local allocation buffer/TLAB)
    jvm为每个线程在堆中分配一部分内存,这个线程产生对象都分配在这块内存上
    JVM会默认开启­XX:+UseTLAB,­XX:TLABSize 指定TLAB大小

3、初始化
  内存分配完成后,对对象初始化零值
4设置对象头
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data)和对齐填充(Padding)。 HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时 间戳等。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
在这里插入图片描述
 下面具体看下对象头数据
 pom文件需引入

 <dependency>
 	 <groupId>org.openjdk.jol</groupId>
	  <artifactId>jol‐core</artifactId>
 	 <version>0.9</version>
 </dependency>
public class JolTest {
	
	public static void main(String[] args) {
		 
		 ClassLayout layout = ClassLayout.parseInstance(new Object());
		 System.out.println(layout.toPrintable());
		 
		 System.out.println();
		 ClassLayout layout1 = ClassLayout.parseInstance(new String[] {});
		 System.out.println(layout1.toPrintable());
	}

}

在这里插入图片描述
  注意上图klass pointer类型指针都是4个字节,正常是8个字节,都存在指针压缩。
  指针压缩(jvm默认都是开启的)
  优点:
    在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据
  注意事项:
   (1)堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
   (2)堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,所以堆内存不要大于32G为好(占用较大宽带,同时GC也会承受较大压力)

5、初始化
  对对象赋值(真正意义的值)、执行构造方法

二、对象内存分配

1、对象在年轻代Eden区分配
   Eden与Survivor区默认8:1:1
 大量的对象被分配在eden区,eden区满了后会触发minor gc,大部分对象成为垃圾被回收掉,剩余存活的对象会被挪到为空的那块survivor区,下一次eden区满了后又会触发minor gc,把eden区和survivor区垃圾对象回收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,因为新生代的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适的,让eden区尽量的大,survivor区够用即可
2、对象分配在栈上
  正常对象都分配在堆上,jvm默认开启逃逸分析,方法内部没有逃逸的对象会分配在栈上
  没有逃逸的对象:
    就是方法内部创建的对象没有被外部引用,如下。

 public void test() {
   byte[] b = new byte[10];
}
  	优点:
  		 因为分配在栈上,方法执行完就会释放内存,负责分配在堆上得等到gc时才能收集,占用内存,要是大对象可能直接分配到老年代,可能会触发full gc
  		

3、大对象直接分配到老年代
  JVM参数 -XX:PretenureSizeThreshold 可以设置大
对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下有效。避免大对象在gc在年轻带复制来复制去,占用带宽
4、对象动态年龄判断
   当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会
把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年龄判断机制一般是在minor gc之后触发的



这篇关于对象创建过程、内存分配详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程