(三)对象的内存布局

Java对象内存模型 8点正式开始 讲师:严镇涛

> 一个Java对象在内存中包括3个部分:对象头、实例数据和对齐填充
>
>

image.png

数据 内存 – CPU 寄存器 -127 补码 10000001 - 11111111 32位的处理器

一次能够去处理32个二进制位 4字节的数据 64位操作系统 8字节 2的64次方的寻址空间

指针压缩技术 JDK1.6出现的 开启了指针压缩 什么时候指针压缩会无效 ??

超过32G指针压缩无效

16499221260943015043ffy

小端存储 :便于数据之间的类型转换,例如:long类型转换为int类型时,高地址部分的数据可以直接截掉。

大端存储 :便于数据类型的符号判断,因为最低地址位数据即为符号位,可以直接判断数据的正负号。

> java中使用的是大端存储。

内存模型设计之–Class Pointer

句柄池访问:

image.png

直接指针访问对象图解:

image.png

区别:

句柄池:

使用句柄访问对象,会在堆中开辟一块内存作为句柄池,句柄中储存了对象实例数据(属性值结构体) 的内存地址,访问类型数据的内存地址(类信息,方法类型信息),对象实例数据一般也在heap中开 辟,类型数据一般储存在方法区中。

优点 :reference存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为) 时只会改变句柄中的实例数据指针,而reference本身不需要改变。

缺点 :增加了一次指针定位的时间开销。

直接访问:

直接指针访问方式指reference中直接储存对象在heap中的内存地址,但对应的类型数据访问地址需要 在实例中存储。

优点 :节省了一次指针定位的开销。

缺点 :在对象被移动时(如进行GC后的内存重新排列),reference本身需要被修改

内存模型设计之–指针压缩

> 指针压缩的目的:
>
> 1. 为了保证CPU普通对象指针(oop)缓存
> 2. 为了减少GC的发生,因为指针不压缩是8字节,这样在64位操作系统的堆上其他资源空间就少了。
>
> 64位操作系统中 内存 > 4G 默认开启指针压缩技术,内存**< 4G**,默认是32位系统默认不开启。内存 > 32G 指针压缩失效。所以我们通常在部署服务时,JVM内存不要超过32G,因为超过32G就无法开启 指针压缩了。
>
> 内存 > 32G指针压缩失效的原因是:4G8 = 32G
>
> 32位系统的CPU 最大支持2^32 = 4G ,如果是64位系统,最大支持 2^64, 但是对其填充是按照8字节进行填充,指针压缩可以理解为在32位系统在64位上面使用,因为32位系统的CPU寻址空间最大支持4G,对其填充
8 = 32G,这就是内存>32G指针压缩失效的原因。
>
> 关闭指针压缩 : -XX:-UseCompressedOops

2的32次方 4294967296字节 4G 如果现在老项目 32位操作系统 支持 4G以上的

PAE的特殊内核

我进行了指针压缩 4G 8字节对齐

内存模型设计之–对齐填充

对齐填充的意义是 提高CPU访问数据的效率 ,主要针对会存在该实例对象数据跨内存地址区域存储的情况。

例如:在没有对齐填充的情况下,内存地址存放情况如下:

image.png

因为处理器只能0x00-0x07,0x08-0x0F这样读取数据,所以当我们想获取这个long型的数据时,处理 器必须要读两次内存,第一次(0x00-0x07),第二次(0x08-0x0F),然后将两次的结果才能获得真正的数值。

那么在有对齐填充的情况下,内存地址存放情况是这样的:

image.png

现在处理器只需要直接一次读取(0x08-0x0F)的内存地址就可以获得我们想要的数据了。

当我们的策略为0时,这个时候我们的排序是 基本类型>填充字段>引用类型

当我们策略为1时,引用类型>基本类型>填充字段

策略为2时,父类中的引用类型跟子类中的引用类型放在一起 父类采用策略0 子类采用策略1,

这样操作可以降低空间的开销 ,

2.4 JVM内存模型

2.4.1 运行时数据区

上面对运行时数据区描述了很多,其实重点存储数据的是堆和方法区(非堆),所以内存的设计也着重从这两方面展开(注意这两块区域都是线程共享的)。

对于虚拟机栈,本地方法栈,程序计数器都是线程私有的。

可以这样理解,JVM运行时数据区是一种规范,而JVM内存模式是对该规范的实现

2.4.2 图形展示

一块是非堆区,一块是堆区
堆区分为两大块,一个是Old区,一个是Young区
Young区分为两大块,一个是Survivor区(S0+S1),一块是Eden区
S0和S1一样大,也可以叫From和To

image.png
1.现在有一整块内存,如果要进行垃圾回收,要在整个内存中进行。
在这里插入图片描述
2.第一种效率不高,于是把内存划分成了新生代、老年代(Old区、young区),那么新的对象只要在young区创建、回收。
在这里插入图片描述
3.对象每经历一次垃圾回收没有被回收的话,它的分代年龄age+1,到age=15时,移动到老年代。
98%的对象活不过一次GC,“朝生暮死”,只有2%能存活,多次GC后,能存活的基本也不超过10%。
gc的悲观策略,在某些情况下,young区对象不达到分代年龄,直接进入老年代。
在这里插入图片描述
4.那为何young区还要继续分,分成eden和survivor区呢?

在这里插入图片描述

如图所示,假设“”邓老师对象占满了”young区,明明young区还有空间,但是其他对象已经存不下了,这叫做空间碎片。所以这时候又设计了Eden区和Survivor区,“邓老师”对象移动到S区。
在这里插入图片描述

在这里插入图片描述
5.这时经过一次young gc,“严老师”依然存活,它要进入S区,但此时S区放不下,此时明显还是有内存碎片产生。
6.所以S区又改进 分成了两个内存大小相等的区域S0和S1.

7.S0和S1的图形
在这里插入图片描述
在这里插入图片描述
S0和S1实际生产可以会不一样大,jvm可能会自动扩容其中一个区。不一样大正常

如果Eden区的对象要进入S区,要进入的对象占S0或S1区的一半大小,年龄大于等于这个年龄的此对象,可以植入老年代。
在这里插入图片描述

![在这里插入图片描述](https://img-blog.csdnimg.cn/ae077f1a1c0c442dbe050d8d242fddb1.png16499221260943012495ffy

2.4.3 对象创建过程

一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到Old区。

我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kfF6VzC0-1677160191828)(file://E:\桌面\yzt\笔记课件\JVM\3天JVM训练营\资料+笔记\images\23.png?lastModify=1649927948)]image.png

什么时候会触发Full GC?

1.之前每次晋升的对象的平均大小 > 老年代的剩余空间 基于历史平均水平

2.young GC之后 存活对象超过了老年代的剩余空间 基于下一次可能的剩余空间

3.Meta Space区域空间不足

4.System.gc();

方法区 类信息 静态变量 常量 即时编译过后的代码 运行时常量池

JDK1.7之前 Perm space 永久代 持久代 JVM自己的内存 线性整理 会增加垃圾回收的时间

JDK1.8 Meta Space 元空间 元数据区 直接内存 减少内存碎片 节省压缩时间

类的总数 常量池的大小 方法的数量 设置JVM内存 2G

16499221260943019786ffy

1.动态扩容

分配内存 1.7之前 线性分配

2.4.4 常见问题

  • 如何理解Minor/Major/Full GC
Minor GC:新生代
Major GC:老年代
Full GC:新生代+老年代
  • 为什么需要Survivor区?只有Eden不行吗?
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。
这样一来,老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了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空间通常会比较小,如果对象比较大,那么还是在共享区域分配。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值