JVM-对象分配

划分对象两种方式
1.指针碰撞: Java堆内存规整的情况下使用
2.空闲列表:Java堆内存不规整的情况下使用
在这里插入图片描述
JVM中分配对象
本地线程分配缓冲
Thread Local Allocation Buffer, TLAB (Eden 1%)
栈----堆中预先分配一块很小私有区域。
CAS比较和交换,确保原子性问题。

对象内存布局

在HostSpot虚拟机中,对象在内存中存储布局可以分为3个部分:对象头(Header)、实例数据(Instance Data)和对其填充(Padding)。
对象头:包括两部分信息,
第一部分存储本身运行时数据,如哈希码(hashcode)、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等。
另一部分是类类型指针,及对象指向类元素的指针,虚拟机通过这个指针确定这个类是哪个对象的实例。
对其填充:对象的大小必须是8个字节。实例数据7个字节 填充1个,1个填充7个

对象访问方式

1.句柄(特殊的指针),移动方便,访问效率低
2.直接指针(HotSpot中使用)
在这里插入图片描述

堆内存分配策略

新生代Eden区
Survivor(from)区,设置Survivor是为了减少送到老年代的对象
Survivor(to)区
设置两个Survivor区是为了解决碎片化问题(复制回收算法)

1.对象优先在Eden区分配

-Xms:20m堆空间20m
-Xmx:20m 堆最大空间20m
-Xmn:10m 新生代(Eden区)10m
-XX:+PrintGCDetails 打印GC日志
-XX:+UseSeriolGC 一个垃圾回收器种类
-XX:PretenureSizeThreshold=2m 超过2M的对象可以直接进入老年代
在这里插入图片描述
注:大多数情况对象是在Eden区分配,当Eden区空间不足,虚拟机将会发起一次Minor GC。

2.大对象直接进入老年代

目的:1.避免大量内存复制2.避免提前进行垃圾回收,明明内存有空间分配

3.长期存活对象进入老年代

Eden区8m占满,再分配1m对象,Eden区会发生MinGC.
存活对象进入from区,年龄+1,再来垃圾回收则进入to区,年龄再+1。
再次进行垃圾回收,对象返回from区,年龄再+1, from和to区反复,因为from和to区采用复制回收算法的原因。
年龄达到15岁(默认),属于长期存活对象,进入老年代。

4.动态年龄判断

from和to区年龄所有对象大小加起来大于from年龄的一半,年龄大于等于该年龄的对象就可以直接进入老年代。

5.空间分配担保

HandlePromotionFailure, 不用考虑老年代空间不够,不用考虑发生FullGC,如果担保失败或内存不够也会进行一次FullGC
FullGC:当老年代空间不足时候,有from或to区升级进入老年代的时候,将会执行FullGC

在进行Minor GC之前,JVM首先会检查【老年代最大连续空闲空间】是否大于【当前新生代所有对象占用的总空间】
如果是,那么说明此次的Minor GC是安全的,可以放心的进行Minor GC
如果不是,则JVM会去查看HandlePromotionFailure参数的值是否为true(表示是否允许担保失败)
如果不允许担保失败,则此时就会进行一次Full GC 以腾出老年代更多的空间

实战分析

在这里插入图片描述
在这里插入图片描述
通过上面这些内容介绍,就是尽可能让对象都在新生代里分配和回收,尽量别 让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃 圾回收。

1. 什么是java对象的指针压缩?

  1. jdk1.6 update14开始,在64bit操作系统中,JVM支持指针压缩
  2. jvm配置参数:UseCompressedOops,compressed­­压缩、oop(ordinary object pointer)­­对象指针
  3. 启用指针压缩:­XX:+UseCompressedOops(默认开启),禁止指针压缩:­XX:­UseCompressedOops

为什么要进行指针压缩?

  1. 在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据, 占用较大宽带,同时GC也会承受较大压力
  2. 为了减少64位平台下内存的消耗,启用指针压缩功能
  3. 在jvm中,32位地址最大支持4G内存(2的32次方),可以通过对对象指针的压缩编码、解码方式进行优化,使得jvm 只用32位地址就可以支持更大的内存配置(小于等于32G)
  4. 堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
  5. 堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内 存不要大于32G为好

2. 逃逸分析、标量替换

对象逃逸分析: 就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参 数传递到其他地方中。

public User test1() { 
	User user = new User(); 
	user.setId(1);
	user.setName("andy"); 
	//TODO 保存到数据库 
	return user; 
} 

// 没有逃出本方法外部,即为逃逸
public void test2() { 
	User user = new User(); 
	user.setId(1); 
	user.setName("andy"); 
	//TODO 保存到数据库 
}

很显然test1方法中的user对象被返回了,这个对象的作用域范围不确定,test2方法中的user对象我们可以确定当方法结 束这个对象就可以认为是无效对象了,对于这样的对象我们其实可以将其分配在栈内存里,让其在方法结束时跟随栈内 存一起被回收掉。
JVM对于这种情况可以通过开启逃逸分析参数(-XX:+DoEscapeAnalysis)来优化对象内存分配位置,使其通过标量替换优 先分配在栈上(栈上分配),JDK7之后默认开启 逃逸分析,如果要关闭使用参数(-XX:-DoEscapeAnalysis)

标量替换: 通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而是将该 对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间,这样就 不会因为没有一大块连续空间导致对象内存不够分配。开启标量替换参数(-XX:+EliminateAllocations),JDK7之后默认 开启

栈上分配示例:

/** 
* 栈上分配,标量替换
* 代码调用了1亿次alloc(),如果是分配到堆上,大概需要1GB以上堆空间,如果堆空间小于该值,必然会触发GC。 
* 
* 使用如下参数不会发生GC 
* ‐Xmx15m ‐Xms15m ‐XX:+DoEscapeAnalysis ‐XX:+PrintGC ‐XX:+EliminateAllocations 
* 使用如下参数都会发生大量GC 
* ‐Xmx15m ‐Xms15m ‐XX:‐DoEscapeAnalysis ‐XX:+PrintGC ‐XX:+EliminateAllocations 
* ‐Xmx15m ‐Xms15m ‐XX:+DoEscapeAnalysis ‐XX:+PrintGC ‐XX:‐EliminateAllocations 
*/ 
public class AllotOnStack { 
	public static void main(String[] args) { 
		long start = System.currentTimeMillis(); 
		for (int i = 0; i < 100000000; i++) { 
			alloc(); 
		} 
		long end = System.currentTimeMillis(); 
		System.out.println(end ‐ start); 
	} 
	private static void alloc() { 
		User user = new User(); 
		user.setId(1); 
		user.setName("andy");
    } 
}

栈上分配依赖于逃逸分析和标量替换

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值