【JVM】Java堆初探

Java堆

Java堆被所有线程共享,在Java虚拟机启动时创建。是虚拟机管理最大的一块内存,也是垃圾回收的主要区域

主要采用分代回收算法堆进一步划分主要是为了更好的回收内存或更快的分配内存

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

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

堆得内存划分

堆的整体划分(堆大小 = 新生代 + 老年代)

堆的大小可通过参数 –Xms(堆的初始容量)、-Xmx(堆的最大容量) 来指定

  • 新生代被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to

    • 默认的,edem : from : to = 8 : 1 : 1

      • eden = 8/10 的新生代空间大小

      • from = to = 1/10 的新生代空间大小

    • 可以通过参数 –XX:SurvivorRatio 来设定

    • JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的

      • 新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间,有10%为空闲状态

内存的分配原则

  • 优先在 Eden 分配,如果 Eden 空间不足虚拟机则会进行一次 MinorGC

  • 大对象直接接入老年代,大对象一般指的是很长的字符串或数组

  • 长期存活的对象进入老年代,每个对象都有一个age,当age到达设定的年龄的时候就会进 入老年代,默认是15岁

内存的分配方式

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

  • 指针碰撞

    • 内存地址是连续的

    • 使用Serial 和 ParNew 收集器

  • 空闲列表

    • 内存地址不连续

    • CMS 收集器和 Mark-Sweep 收集器

内存分配安全问题

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

在JVM中有两种解决办法

  • CAS,比较和交换(Compare And Swap)

    • CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性

  • TLAB,本地线程分配缓冲(Thread Local Allocation Buffer即TLAB)

    • 为每一个线程预先分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配

创建对象的内存分配流程

Student stu = new Student();

对象的内存布局

  • 对象在内存中存储的布局可以分为三块区域 :

    • 对象头(Header)

    • 实例数据(Instance Data)

    • 对齐填充(Padding)

对象头

对象头包括两部分信息 :

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

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

实例数据

存储的是对象真正有效的信息

对齐填充

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

对象访问方式

访问方式一共有两种:句柄直接指针 句柄:稳定,对象被移动只要修改句柄中的地址 直接指针:访问速度快,节省了一次指针定位的开销

  • 通过句柄方式访问对象

  • 通过直接指针访问对象

数组的内存分析

一维数组

int[] arr1 = new int[3];
int[] arr2 = arr1;
arr2[0] = 20;

二维数组

int[][] array = new int[3][];
array[0][] = new int[1];
array[1][] = new int[2];
array[2][] = new int[3];

.

posted @ 2019-06-01 14:30  鞋破露脚尖儿  阅读(305)  评论(0编辑  收藏  举报