深入了解 JVM - 热点虚拟机对象
1. HotSpot 虚拟机对象
如何创建、如何布局、如何访问。
2. 对象创建
Class加载 --> 内存分配 --> 内存初始化 --> 对象初始化.
2. 类加载
当VM遇到字节码 new 指令,检查这个指令的参数在常量池能否定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过,没有则需要先执行对应的类加载过程。
3. 内存分配
类加载检查通过后,则为新生对象分配内存,对象所需大小在类加载后便可完全确定。指针碰撞(Bump The Pointer)
Java堆内存绝对规整,已使用过的内存放在一边,未使用过的内存在一边,中间使用指针作为指示器,则内存分配仅需要移动指针。
空闲链表(Free List)
java堆内存不规整,需要使用链表维护哪些内存是空闲的,哪些已使用过,分配时从空闲链表找到足够大内存划分对象实例,并更新链表。
选择哪种方式 ---> Java堆是否规整 ---> 所采用的的垃圾收集器是否带有空间压缩整理(Compact)。
Serial、ParNew 等带有空间压缩整理,使用指针碰撞,简单高效。
CMS 采用清除算法(Sweep)实现,使用空闲链表。
实际上即便不规整也会两种均使用,空闲链表管理大块内存,然后在大块内存中采用指针碰撞分配。
CMS 实现中使用了 Linear Allocation Buffer 的分配缓冲器区,先通过空闲链表获取大块分配缓冲区,在缓冲区中使用指针碰撞分配。
内存分配的线程安全
一种是 CAS + 失败重试进行分配。
另一种是每个线程预先在 Java 堆中分配线程私有的分配缓冲区,Thread Local Allocation Buffer,TLAB,线程分配时先在TLAB中分配,没有足够内存再使用 CAS + 失败重试从堆中分配 TLAB。
-XX:+/-UseTLAB
启用或关闭 TLAB
4. 内存初始化
这好像也是 Java 内存模型的一部分,主要是保证字段即便未显式初始化也可直接使用。
可提前到 TLAB 分配时期就处理。
把内存清零即可。
5. 对象初始化
对象头的初始化,如是哪个类的实例、如何找到类的元数据信息、对象哈希码(Object#hashCode调用时才计算)、GC分代年龄等。
构造函数调用 <init>()
,由 new 指令后是否有 invokespecial 指令决定,java编译器会在new指令后同时生成invokespecial,通过其他方式产生的则不一定。
3. 对象内存布局
三部分:对象头
(Header)、实例数据
(InstanceData)、对齐填充
(Padding)。
1. 对象头
两部分信息
- 对象自身运行时数据 Mark Word
:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分在32位和64位VM(未开启指针压缩)中分别32位和64位。
- 类型指针:实例的类型元数据指针,VM通过这个指针确定该对象是哪个类的实例。不一定所有实现都需要在对象头保留类型指针。
- 数组长度:如果实例是一个数组的话
Mark Word
动态数据结构,由于运行时数据较多,不同情况下存储的数据不一样。
实例数据
所有字段内容,无论父子类的都需要记录下来(因为最终是一个实例),顺序受VM分配策略(-XX:FieldsAllocationStyle)和源码定义顺序影响。
默认:longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPs)。
默认相同宽度字段一起,这个前提下,父类字段在子类字段前。
若 HotSpot 虚拟机的+XX:CompactFields参数值为true(默认),那子类之中较窄的变量也允许插入父类变量的空隙之中,以节省出一点点空间。
对其填充
非必然存在,占位符作用,主要是 HotSpot 的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也即要求任何对象的大小都必须是8字节的整数倍。
对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍)。
4. 对象访问定位
常用方法:句柄、直接指针。句柄
堆内存划出句柄池,reference 存储的是对象的句柄地址,句柄中包含了对象实例数据和类型数据各自地址信息。
reference 存储稳定句柄地址,若对象移动,仅需要修改句柄池中的数据。
直接指针
reference 存储的直接是对象实例的地址,对象头需要使用类型指针存储对象所属类型的信息。
若只是访问实例,则与句柄相比少了一次间接访问的开销。
Hotspot 使用直接指针的访问方法。
5. 扩展
- 启动JVM至少有哪些线程? new 的过程哪些是线程安全的?是用户调用线程执行的吗
- 对象大小计算
原文地址:https://www.cnblogs.com/chenxingyang/p/16142430.html
推荐阅读
-
深入了解 JVM - 热点虚拟机对象
-
Java 从初学者到失业者》,第 4 章:类和对象 (4.3):完整示例带你深入了解类和对象
-
深入jvm的对象如何步入老年
-
Java 类加载器的作用 - 简介:类加载器是 Java™ 中一个非常重要的概念。类加载器负责将 Java 类的字节码加载到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模型、加载类的具体过程和线程上下文类加载器等。然后介绍了如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。 类加载器是 Java 语言的一项创新,也是 Java 语言广受欢迎的重要原因之一。它允许将 Java 类动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 开始出现,最初是为了满足 Java Applets 的需求而开发的,Java Applets 需要从远程位置下载 Java 类文件并在浏览器中执行。现在,类加载器已广泛应用于网络容器和 OSGi。一般来说,Java 应用程序的开发人员不需要直接与类加载器交互;Java 虚拟机的默认行为足以应对大多数情况。但是,如果遇到需要与类加载器交互的情况,而您又不太了解类加载器的机制,就很容易花费大量时间调试异常,如 ClassNotFoundException 和 NoClassDefFoundError。本文将详细介绍 Java 的类加载器,帮助读者深入理解 Java 语言中的这一重要概念。下面先介绍一些基本概念。 类加载器的基本概念 顾名思义,类加载器用于将 Java 类加载到 Java 虚拟机中。一般来说,Java 虚拟机以如下方式使用 Java 类:Java 源程序(.java 文件)经 Java 编译器编译后转换为 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码并将其转换为 java.lang 实例。每个实例都用来表示一个 Java 类。通过该实例的 newInstance 方法创建该类的对象。实际情况可能更加复杂,例如,Java 字节代码可能是由工具动态生成或通过网络下载的。 基本上,所有类加载器都是 java.lang.ClassLoader 类的实例。下面将详细介绍这个 Java 类。 java.lang.ClassLoader 类简介 java.lang.ClassLoader 类的基本职责是根据给定类的名称为其查找或生成相应的字节码,然后根据这些字节码定义一个 Java 类,即 java.lang.Class 类的实例。除此之外,ClassLoader 还负责加载 Java 应用程序所需的资源,如图像文件和配置文件。不过,本文只讨论它加载类的功能。为了履行加载类的职责,ClassLoader 提供了许多方法,其中比较重要的方法如表 1 所示。下文将详细介绍这些方法。 表 1.与加载类相关的 ClassLoader 方法
-
深入 JVM(III)的 HotSpot 虚拟机类加载机制
-
实用的命令行工具指南:深入理解jps、jstack、jmap、jhat、jstat与hprof JVM 性能优化与监控方法" - jps: Java虚拟机进程状态工具 - 全面解读 - jstack: 一步步掌握堆栈跟踪工具 - 实战解析 - jmap & jhat: 内存映射与Java堆分析利器 - 详解篇 - jstat: JVM性能统计与监测工具 - 使用详解 - hprof: 堆/CPU性能剖析工具 - 深入学习指南
-
实战技巧:优化JVM性能 - 通过jmap与jhat深入探究线上系统中的对象分布情况
-
深入探究对象数组的去重方法,你真的了解吗?
-
Vue.js新手指南:创建多个Vue实例对象,深入了解Vue组件
-
JavaScript Number 对象:深入了解数字的操作和特性