JVM对象管理!

对象的生命周期

对象的创建流程:

开始 new一个对象

进行常量池检查

  • 看能否在常量池中定位到这个类的符号引用,定位不到则加载类
  • 看是否加载过这个类,没加载过则加载类

分配内存空间

  • 指针碰撞: GC不带压缩功能,SerialParNew
  • 空闲列表: GC带压缩功能,CMS

内存空间初始化为零值:

  • 保证了对象的实例字段在不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值

必要信息设置

  • 对象类的元数据
  • 对象的哈希码
  • GC分代年龄 -> 对象头

init()

image-20231021164000947

一个对象产生到灭亡的过程

新产生的对象优先分配在Eden区。

  • 当Eden区满了或放不下了进行GC,这时候其中存活的对象会复制到From区。

  • 如果From区放不下则会全部进入老年代,然后Eden内存全部清除。

  • 之后产生的对象继续分配在Eden区,当Eden区又满了或放不下了

    • 这时候将会把Eden区和From区存活下来的对象复制到To区。
  • 同理,如果存活下来的对象To区都放不下,则这些存活下来的对象全部进入年老代

    • 之后回收掉Eden区和From区的所有内存。

如上这样,会有很多对象会被复制很多次(每复制一次,对象的年龄就+1)。

默认情况下:

  • 当对象被复制了15次(这个次数可以通过:-XX:MaxTenuringThreshold来配置),就会进入年老代了。

当年老代满了或者存放不下将要进入年老代的存活对象的时候。

  • 就会发生一次Full GC(这个是最需要减少的,因为耗时很严重)。

对象的内存分配方式

指针碰撞:

假设堆中的内存是绝对规整的,所有用过的内存放一边,未使用内存放另一边,中间边界线就可以类比为指针。

  • 内存分配就是把指针向未分配的区域挪一段与对象大小相等的距离,这就是指针碰撞

空闲列表:

如果堆中的内存不是很规整的,已使用和未使用的内存就会相互交错。

这个时候就要维护一个列表来记录所有已使用和未使用的内存块

  • 在分配内存时从列表找到一块足够大的空间划分给对象实例, 并更新内存列表。
分配方法 说明 收集器
指针碰撞 内存地址是连续的(新生代) Serial和 ParNew收集器
空闲列表 内存地址不连续(老年代) CMS收集器和 Mark-Sweep收集器

对象的内存布局

在堆内存中,一个对象的的存储布局可以分为三个区域:

对象头:

  • 存储对象自身的运行时数据(哈希吗+GC分代年龄+锁状态标准)

  • 类型指针: 类元素的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

实例数据:

  • 存储对象真正的有效信息,例如: 非静态变量也会存入堆空间

对齐填充:

  • JVM内对象都采用8byte对齐,不够8byte整数倍的就需要通过对齐填充来补全

image-20231026171802543

如何访问一个对象

对象的访问方式由虚拟机决定,目前主流的访问方式有以下两种:

句柄:

  • 使用句柄的话,堆中会专门划分出一块内存来作为句柄池,reference中存储对象句柄的地址
    • 句柄中包含了对象实例数据与对象类型数据各自的具体地址信息。

直接指针:

  • 访问速度快,节省了一次指针定位的开销
  • 直接访问,reference中存储的就是对象的地址,节省了一次指针定位的开销

强、软、弱、虚引用

四种引用的目的是让程序自己决定对象的生命周期。

  • 通过垃圾回收器对这四种引用做不同的处理,来实现对象生命周期的改变。

强引用:

如果一个对象具有强引用,那垃圾收器绝不会回收它。

当内存空间不足,宁愿抛出OutOfMemoryError错误,使程序异常终止。

  • 也不会靠随意回收具有强引用对象来解决内存不足的问题。

如:Object obj = new Object();这种就是强引用。

软引用:

在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。

如果这次回收还没有足够的内存,才会抛出内存溢出异常。

  • 使用软引用的方式是SoftReference

软引用通常用在对内存敏感的程序中。

  • 比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收。

弱引用:

在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。

  • 只要垃圾回收,不管内存够不够用,弱引用都会被回收。

使用弱引用的方式是类WeakReference

虚引用:

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

  • 它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(RefenenceQueue)联合使用。

  • 主要作用是跟踪对象垃圾回收的状态。

  • 提供了一种确保对象被 Finalize 以后,做某些事情的机制。

设置虚引用的唯一目的:

  • 就是在这个对象被回收器回收的时候收到一个系统通知或者后续添加进一步的处理。

空对象占多大内存

开启压缩指针:

  • 默认会占用12个字节,但为了避免伪共享问题,JVM会按照8个字节的倍数进行填充。
    • 所以会填充4个字节变成16个字节长度。
  • 所以:Markword占8个字节、类型指针占4个字节, 对齐填充占4个字节。

关闭压缩指针:

  • 默认会占用16个字节,16个字节正好是8的整数倍,因此不需要填充。

常用参数

-Xms:

  • 最小堆大小(初始化堆),如-Xms256m

-Xmx:

  • 最大堆大小,如-Xmx512m

-Xmn:

  • 新生代大小,如-Xmn是64m

-Xss:

  • 每个线程的堆栈大小

-XX:NewRatio=4:

  • 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)
  • 设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5

-XX:SurvivorRatio=8:

  • 设置年轻代中Eden区与Survivor区的大小比值
  • 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10

-XX:PermSize=100m:

  • 初始化永久代大小为100MB

-XX:MaxTenuringThreshold=15:

  • 设置垃圾对象最大年龄