jvm-运行时数据区(java堆)

Java堆

概念

Java堆被所有线程共享,在Java虚拟机启动时创建。是虚拟机管理最大的一块内存。

Java堆是垃圾回收的主要区域,而且主要采用分代回收算法。堆进一步划分主要是为了更好的回收内存或更快的分配内存。

存储内容

Java虚拟机规范的描是:所有的对象实例以及数组都要在堆上分配。

不过随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。

存储方式

堆内存空间在物理上可以不连续,逻辑上连续即可。

堆内存划分
  • 新生代
    • Eden空间
    • From Survivor空间
    • To Survivor空间
  • 老年代

堆大小 = 新生代 + 老年代。
堆的大小可通过参数 –Xms(堆的初始容量)、-Xmx(堆的最大容量) 来指定

  • 其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分
    别被命名为 from 和 to,以示区分。
    默认的,Edem : from : to = 8 : 1 : 1 。(可以通过参数 –XX:SurvivorRatio 来设定 。
    即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
  • JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
  • 新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

在这里插入图片描述
在这里插入图片描述

对象创建
Student stu = new Student();

在这里插入图片描述

内存的分配原则
  • 1:优先在 Eden 分配,如果 Eden 空间不足虚拟机则会进行一次 MinorGC
  • 2:大对象直接接入老年代,大对象一般指的是很长的字符串或数组
  • 3:长期存活的对象进入老年代,每个对象都有一个age,当age到达设定的年龄的时候就会进入老年代,默认是15岁。
内存分配方式

内存分配的方法有两种:指针碰撞(Bump the Pointer)和空闲列表(Free List)

分配方法说明收集器
指针碰撞内存地址是连续的Serial 和 ParNew 收集器
空闲列表内存地址不连续CMS 收集器和 Mark-Sweep 收集器

在这里插入图片描述

内存分配安全问题

在分配内存的同时,存在线程安全的问题,即虚拟机给A线程分配内存过程中,指针未修改,B线程可能同时使用了同样一块内存。

在JVM中有两种解决办法:

  • 1:CAS,比较和交换(Compare And Swap): CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟 机采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • TLAB,本地线程分配缓冲(Thread Local Allocation Buffer即TLAB): 为每一个线程预先分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配。
对象的内存布局

对象在内存中存储的布局可以分为三块区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。

  • 1:对象头
    对象头包括两部分信息:
    一部分是用于存储对象自身的运行数据,如 哈希码(HashCode),GC分代年龄,锁状态标志,线程持有的 锁,偏向线程ID,偏向时间戳 等。

    另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪一个类的实例。当对象是一个java数组的时候,那么对象头还必须有一块用于记录数组长度的数据,因此虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是从数组的元数据中无法确定数组的大小。

  • 2:实例数据
    存储的是对象真正有效的信息。

  • 3:对齐填充
    这部分并不是必须要存在的,没有特别的含义,在jvm中对象的大小必须是8字节的整数倍,而对象头也是8字节的倍数,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

对象访问方式
方式优点
句柄稳定,对象被移动只要修改句柄中的地址
直接指针访问速度快,节省了一次指针定位的开销
  • 句柄访问对象
    在这里插入图片描述

  • 指针访问对象在这里插入图片描述

数组的内存分析
  • 一维数组
int[] arr1 = new int[3];
//先把 arr1 压进栈,然后在堆空间中开辟一个空间,并把值初始化为0(arr1为引用变量,但是
//内部数据是int类型,默认值为 0),最后把 开辟的堆空间地址 赋值给arr1
int[] arr2 = arr1;
// 把 arr1 中的 地址 赋值给 arr2,此时 arr2 和 arr1 指向同一块空间。
arr2[0] = 20;
//此时,arr1[0] 值为 20。
  • 二维数组
int[][] array = new int[3][];
//这条语句会先把 array 压栈,然后在堆中开辟一个空间,初始值为 null(array为引用变量,
//第一维同样是引用类型),最后把开辟的堆空间地址赋值给 array。
array[0][] = new int[1]
//这条语句会在堆空间中开辟一个 只有一个 int 类型大小的空间,并初始化为 0 ,
//然后把自己的地址赋值给array[0][]
array[1][] = new int[2];
array[2][] = new int[3];
这两条语句和上一条意义一样,就不再做解释
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值