java-内存布局

对象在堆中的存储

在这里插入图片描述

对象头

mark word:
	1、一系列的标记位:
		hashCode:对象初次调用hashCode方法时才会存储
		GC分代年龄
		锁的状态标识
	2、64位系统上大小8字节
class pointer:
	指向对象元数据的内存地址(方法区)
	64位系统上大小8字节,//但默认开启指针压缩(大小为4字节)
	引用--->对象(class pointer)--->方法区中的类信息
如果是数组:
	还会用四个字节存储数组长度

为什么使用指针压缩

减少GC的发生:
	减少内存开销:如果不压缩就是8字节,在jvm大小相同的情况下,那么剩余的空间相对就会更少,会GC更频繁
	内存 > 32G 指针压缩失效。所以我们通常在部署服务时,JVM内存不要超过32G:因为32位系统的
	CPU寻址空间最大支持2的32次方4G,对其填充乘以8 = 32G,这就是内存>32G指针压缩失效的原因
	关闭指针压缩 : -XX:-UseCompressedOops

实例数据

实例数据:
	基本数据类型大小就是对应字节大小
	引用数据类型64位系统上8字节(默认开启指针压缩,大小变为4字节):对象中有String变量String a="xxxxxxx";
	对象中存储的a其实还是4字节,a只是一个引用并不会存储具体的对象,对象存储在堆中(池化技术)

string池化技术

比如很多对象中都有string类型的变量:String a="xxxx";如果对象存储时存储的是具体数据而不是
引用,这样如果字符串很长的话非常浪费空间;存引用的话:只需要在字符串常量池中存一份具体
数据,对象中存储引用就行;而且string是final修饰的;final修饰的在编译时就会赋值,放入常量池
类似于缓存技术,需要直接去常量池中拿就行

对齐填充

保证对象大小为8字节的整数倍:提高cpu访问率(空间换时间)

为什么要是8字节的整数倍
64位jvm下是按照8字节读取数据的,例如以下每个格子代表一个字节:每次读取8个字节
这是没有进行对齐填充的
在这里插入图片描述
没有对齐的话,想要读取long型的这个完整数据:需要先读取0x00-0x07这8字节,再读取0x08-0x0f这8字节,整合两次读取的结果才能得到真正的数据
如果是对齐填充的
在这里插入图片描述
只需要读取一次0x08-0x0f这八字节就能获取到数据

java内存模型

主要针对堆:以下的非堆指的是方法区
在这里插入图片描述
堆被分为old区和young区

young区

young区又被分为一个eden区和两个survivor区

一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到Old区
默认eden和两个survivor大小比值是8:1:1(可修改)
通常new对象优先会分配在eden区:如果eden区剩余内存无法分配就会触发youngGC,如果youngGC后eden区剩余
	大小能存放新对象就直接分配在eden区;如果不够再看survivor区剩余空间是否足够,如果够:先将eden部分活跃对
	象转移到survivor区,再为新对象分配内存;如果不够再看old区剩余空间是否足够,如果够:先将survivor部分活跃对
	象放入old区,再将eden区部分活跃对象放入survivor区,最后再为新对象分配内存,如果old区剩余空间不够:触发
	fullGC;fullGC后如果还不够跑oom异常,如果够先将survivor部分活跃对象放入old区,再将eden区部分活跃对象放
	入survivor区,最后再为新对象分配内存

youngGC:eden区为new 对象分配内存不够时会触发,将eden区gc后存活的对象移入survivor区,如果一个survivor不是空的,把eden要转移到s区的和s区已存在的一起移到另一个s区中,减少内存碎片
流程图
在这里插入图片描述

old区

什么样的对象会放入old区?

1、一般经历过15次youngGC都没有回收的对象,为什么是15次?
	在对象内存中,对象头中的markWord使用4个二进制位来存储分代年龄,最大取值就是15
	为什么取15,尽可能保证old区存放的是生命周期更长的对象
2、一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到Old区(内存担保机制)。
3、相同年龄的所有对象大小总和大于任意一个s区大小的一半,年龄大于等于这个年龄的对象直接进入old区

fullGC = oldGc + youngGC + metaSpaceGC

什么时候会触发fullGC

1、之前每次晋升的对象的平均大小 > 老年代的剩余空间 基于历史平均水平
2、young GC之后 存活对象超过了老年代的剩余空间 基于下一次可能的剩余空间
3、Meta Space区域空间不足(jdk1.8及之后)
4、System.gc();

oldGC:将old区中能回收的对象清理掉

永久代和元空间在jvmH中==中的区别
	永久代中的所有信息都会保存在jvm中,元空间中只有静态变量和字符串常量池会保存在jvm中,
	其他信息(类信息、即时编译过后的代码、运行时常量池)会存在直接内存中(也就是服务器内存)

永久代:
在这里插入图片描述
元空间
在这里插入图片描述

常见问题

为什么需要Survivor区?只有Eden不行吗?

如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代,这样一来,老年代很快被填
满,触发oldGC(因为oldGC一般伴随着youngGC,也可以看做触发了Full GC)。老年代的内存空间远大于新生
代,进行一次Full GC消耗的时间比Minor GC长得多。执行时间长有什么坏处?频发的Full GC消耗的时间很长,
会影响大型程序的执行和响应速度。

可能你会说,那就对老年代的空间进行增加或者较少咯。
假如增加老年代空间,更多存活对象才能填满老年代。虽然降低Full GC频率,但是随着老年代空间加大
,一旦发生Full GC,执行所需要的时间更长。
假如减少老年代空间,虽然Full GC所需时间减少,但是老年代很快被存活对象填满,Full GC频率增加。

所以Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,
只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。

为什么需要两个Survivor区?

最大的好处就是解决了碎片化。也就是说为什么一个Survivor区不行?第一部分中,我们知道了必须设置
Survivor区。假设现在只有一个Survivor区,我们来模拟一下流程:
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。
这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,
如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了
内存碎片化。永远有一个Survivor space是空的,另一个非空的Survivor space无碎片(垃圾回收)。

新生代中Eden:S1:S2为什么是8:1:1?

新生代中的可用内存:复制算法用来担保的内存为9:1
可用内存中Eden:S1区为8:1
即新生代中Eden:S1:S2 = 8:1:1
现代的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中的对象大概98%是“朝生夕死”的

堆内存中都是线程共享的区域吗?

JVM默认为每个线程在Eden上开辟一个buffer区域,用来加速对象的分配,称之为TLAB,全称:Thread Local Allocation Buffer。
对象优先会在TLAB上分配,但是TLAB空间通常会比较小,如果对象比较大,那么还是在共享区域分配。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值