jvm对象探究

hostpot虚拟机对象探究

jvm虚拟机创建对象的流程

ava虚拟机(JVM)创建对象的过程包括以下步骤:

  1. 类加载: 首先,JVM会检查对象的类是否已经被加载。如果该类还没有被加载,JVM会通过类加载器加载该类的字节码文件。
  2. 内存分配: 一旦类加载完成,JVM会在堆内存中为对象分配内存空间。Java的对象通常存储在堆中,堆是Java虚拟机管理的一块内存区域。
  3. 初始化零值: 在分配内存后,JVM会对对象进行初始化。这包括将对象的实例变量设置为默认的零值(数值类型为0,布尔类型为false,引用类型为null)。
  4. 构造方法调用: 接着,JVM会调用对象的构造方法(如果有的话)。构造方法负责完成对象的初始化工作,可以对实例变量进行赋值,执行其他必要的初始化操作。
  5. 对象引用: 一旦对象成功初始化,JVM将返回一个指向该对象的引用。这个引用可以被赋给类变量、实例变量、局部变量等,使得程序可以通过引用访问和操作这个对象。

需要注意的是,对象的内存分配和初始化是原子操作的,这意味着在对象初始化完成之前,其他线程无法看到或访问到这个对象。这有助于确保多线程环境

此外,Java虚拟机可能会对对象的内存分配和回收采取一些优化措施,如对象池、分代垃圾收集等,以提高内存利用率和程序性能。

java对象的内存分布

  • Java 对象头包括两个部分:Mark Word 和 Class Pointer,对于数组对象对象头还包括数组长度(Length),下面具体看一下每个部分:

对象头

Mark Word

用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,这部分数据的长度在 32 位和 64 位的虚拟机(未开启压缩指针)中分别为 32 个比特和 64 个比特。

包含两部分类信息:

  1. 存储对象自身运行时数据,哈希码,Gc分代年龄,锁状标志,线程持有的锁,偏向线程ID,偏向时间戳
  • 实例数据(Instance Data)
  • 对齐填充
Class Pointer

这部分是一个类型指针,即对象指向它的类型元数据的指针,Java 虚拟机通过这个指针来确定该对象是哪个类的实例。

并不是所有的虚拟机实现对象头都具有类型指针,这和对象的访问定位方式有关,主流的访问方式主要有使用句柄直接指针两种:

使用句柄的方式:Java 堆中将可能会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息

直接指针的方式:Java 堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference 中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问 的开销(HotSpot 虚拟机采用该方式,所以对象头中有类型指针用于存放对象结构的引用)

length

如果对象是一个 Java 数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通 Java 对象的元数据信息确定 Java 对象的大小,但是如果数组的长度是不确定的,将无法通过元数据中的信息推断出数组的大小。

实例数据

实例数据部分是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。这部分的存储顺序会受到虚拟机分配策略参数(-XX:FieldsAllocationStyle参数)和字段在 Java 源码中定义顺序的影响。 HotSpot 虚拟机默认的分配顺序为 longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPs),从以上默认的分配策略中可以看到,相同宽度的字段总是被分配到一起存放,在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果 HotSpot 虚拟机的 +XX:CompactFields 参数值为 true(默认就为 true),那子类之中较窄的变量也允许插入父类变量的空隙之中,以节省出一点点空间。

这里指的是子类在继承父类的字段后,把原本填充的空间放上小的字段类型数据

对齐填充

对象的第三部分是对齐填充,这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于 HotSpot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是任何对象的大小都必须是 8 字节的整数倍。对象头部分已经被精心设计成正好是 8 字节的倍数(1 倍或者 2 倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

垃圾收集(GC)器和内存分配策略

  • 什么是GC?

    GC 是 garbage collection 的缩写,意思是垃圾回收------把内存(特别是堆内存)中不再使用的空间释放掉;清理不再使用的对象。

  • 为什么要GC?

    堆内存是各个线程共享的空间,不能无节制的使用。服务器运行的时间通常都很长。累积的对象也会非常多。这些对象如果不做任何清理,任由它们数量不断累加,内存很快就会耗尽。所以GC就是要把不使用的对象都清理掉,把内存空间空出来,让项目可以持续运行下去。

  • 什么样的对象是垃圾对象?

    不再使用或获取不到的对象是垃圾对象。

  • 如何把垃圾对象找出来?

    办法1:引用计数法(不采用,不能解决循环引用问题)

    办法2:可达性分析(从GC Roots对象出发,不可达的对象就是要清理的对象)

  • 找到垃圾对象如何执行清理?

    具体的GC算法

对象已死:

在回收对象的时候 要确认对象不可能再被任何途径使用的对象(以下方法)

1.程序计数法

  • 引用计数法是在对象每一次被引用时,都给这个对象专属的『引用计数器』+1。
  • 当前引用被取消时,就给这个『引用计数器』-1。
  • 当前『引用计数器』为零时,表示这个对象不再被引用了,需要让GC回收。
  • 可是当对象之间存在交叉引用的时候,对象即使处于应该被回收的状态,也没法让『引用计数器』归零。

2.可达性分析法

核心原理:判断一个对象,是否存在从『堆外』到『堆内』的引用。

为了解决引用计数法的循环引用问题,Java 采用了可达性分析的方法。其实现原理是,将一系列"GCroot"对象作为搜索起点。如果在"GCroot"和一个对象之间没有可达的路径,则该对象被认为是不可访问的。

要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

引用

jdk1.2之前:如何reference类型的数据中存储的数值代表的是另一块内存的其实地址,就称该reference数据是 代表某块内存,某个对象的引用

jdk1.2后:

  • 强引用:最传统的引用定义,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象
  • 软引用:用来描述还有用,但是非必须的对象,只被弱引用关联的对象,在系统将要内存溢出前,会把这些对象列入回收范围之中,进行二次回收.如果这次回收还没有呢足够的内存,才会抛出内存溢出异常
  • 弱引用:用来描述那些非必要对象,比软引用更弱一点,被弱引用关联的对象只能生存到下一次垃圾收集发生为止,当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉被弱引用关联的对象
  • 虚引用:也称"幽灵引用"或者"幻影引用",它是最弱的一种引用关系作用是:在这个对象被回收掉 的时候收到一个系统通知

生存还是死亡

在可达性分析算法中判定为不可达的对象,也不是'非死不可'在清理之前会进行两次标记,如过没有发现可达链,则会被标记一次

随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法

回收方法区

主要是对常量池的回收和对类的卸载。

在大量使用反射、动态代理、CGLib 、动态生成 JSP 以及 OSGi 场景都需要虚拟机具备类卸载功能,以保证不会出现内存溢出

类的卸载条件:

  • 堆中不含有该类的任何实例
  • 该类的classloader被回收
  • 该类对应的class对象没有被任何地方所引用,即没有通过反射访问该类方法

不过满足也不一定被卸载,可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。

垃圾回收算法

  1. 引用计数式垃圾收集
  2. 追踪式垃圾收集
1.标记-清除

将存活的对象标记,然后清除未标记的对象。

缺点:标记、清除效率不高,且会产生不连续的内存碎片,导致无法给大对象分配内存

2.标记-整理

所有存活对象都向一端移动,然后直接清理掉端边界外的对象。实现了内存连续

3.复制

将内存划分为两块,每次只使用其中一块,内存用完将存活的对象复制到另外上,然后进行清理。

HotSpot虚拟机中Eden和S区比例是:8:1:1,内存的利用率达到90%,如果每次回收有多于10%的存活对象,此时要借用老年代来存储放不下的对象

4分代收集

根据对象存活周期将内存划分为几块,不同区域采用不同算法。

一般堆分为新生代和老年代:新生代使用复制算法;老年代使用标记-清除或标记-整理算法。

相关推荐
isyangli_blog6 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008116 小时前
FastAPI APIRouter
开发语言·python
Benszen6 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆6 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木6 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充7 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~7 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6167 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
春生野草7 小时前
反射、Tomcat执行
java·开发语言
雪的季节8 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt