Kevin's blog Kevin's blog
首页
  • AI基础
  • RAG技术
  • 提示词工程
  • Wireshark抓包
  • 常见问题
  • 数据库
  • 代码技巧
  • 浏览器
  • 手册教程
  • 技术应用
  • 流程规范
  • github技巧
  • git笔记
  • vpn笔记
  • 知识概念
  • 学习笔记
  • 环境搭建
  • linux&运维
  • 微服务
  • 经验技巧
  • 实用手册
  • arthas常用
  • spring应用
  • javaAgent技术
  • 网站
友情链接
  • 分类
  • 标签
  • 归档

Kevin

你可以迷茫,但不可以虚度
首页
  • AI基础
  • RAG技术
  • 提示词工程
  • Wireshark抓包
  • 常见问题
  • 数据库
  • 代码技巧
  • 浏览器
  • 手册教程
  • 技术应用
  • 流程规范
  • github技巧
  • git笔记
  • vpn笔记
  • 知识概念
  • 学习笔记
  • 环境搭建
  • linux&运维
  • 微服务
  • 经验技巧
  • 实用手册
  • arthas常用
  • spring应用
  • javaAgent技术
  • 网站
友情链接
  • 分类
  • 标签
  • 归档
  • 基础

  • JVM

    • 内存模型
    • 类加载
    • 虚拟机对象
      • 1 对象的创建包含哪些步骤?
        • 1.1 类加载检查
        • 1.2内存分配
        • 1.2.1 内存分配有哪些方式?
        • 1 指针碰撞
        • 2 空闲列表
        • 1.2.2 内存分配的并发问题怎么解决?
        • 1 CAS+失败重试
        • 2 TLAB
        • 1.3 初始化零值
        • 1.4 设置对象头
        • 1.5 执行init方法
      • 2 对象栈上分配
        • 2.1 对象逃逸分析
        • 2.2 标量替换
      • 3 对象的内存布局
        • 3.1 对象头
        • 1 Mark Word
        • 2 指向类的指针
        • 3 数组长度(只有数组对象有)
        • 3.2 实例数据
        • 3.3 对齐填充
      • 4 对象的访问定位
        • 4.1 句柄
        • 4.2 直接指针
        • 4.3 两种访问方式各有什么优势?
    • 垃圾回收
    • 工具命令
    • 生产配置示例
  • Spring

  • Redis

  • 消息中间件

  • 持久化

  • 算法

  • 网络

  • 系统架构

  • 知识整理
  • JVM
luoxiaofeng
2022-05-07
目录

虚拟机对象

# 1 对象的创建包含哪些步骤?

# 1.1 类加载检查

虚拟机遇到一条new指令时,首先去检查这个指令的参数能否在Class常量池中定位到一个类的符号引用,且检查这个符号引用代表的类是否已加载、解析和初始化过。如果没有,先执行相应的类加载过程。

new指令指:new关键词、对象克隆、对象序列化等。

# 1.2内存分配

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。

对象所需的内存大小在类加载完成后便可确定,为对象分配内存等同把一块确定大小的内存从java堆中划分出来。

# 1.2.1 内存分配有哪些方式?

分配内存的方式有 指针碰撞 和 空闲列表 2种。

选择哪种分配方式由java堆是否规整决定,而java堆是否规整又由所采用的垃圾收集器是否有压缩整理功能决定。

# 1 指针碰撞

适用场合:堆内存规整(即没有内存碎片)的情况下。

原理:所有用过的内存全部整合到一边,没有用过的内存放在另外一边,中间有一个分界指针,只需要向着没用过的内存方向将指针移动对象内存大小位置即可。

使用该分配方式的GC收集器:Serial,ParNew。

# 2 空闲列表

适用场合:堆内存不规整的情况下。

原理:JVM维护一个列表,该列表会记录哪些内存块是可用的,在分配内存的时候,找到一块足够大的内存块划分给对象实例,最后更新列表记录。

使用该分配方式的GC收集器:CMS。

# 1.2.2 内存分配的并发问题怎么解决?

创建对象是很频繁的问题,需保证线程安全。目前,虚拟机采用了2种方式来保证线程安全:

# 1 CAS+失败重试

CAS操作失败就重试,直到成功为止。保证更新操作的原子性。

# 2 TLAB

缓冲区(Thread Local Allocation Buffer)

JVM为每一个线程预先在Eden区分配一块内存,给线程中的对象分配内存时,先在TLAB上分配,当对象大于TLAB的剩余内存或TLAB的内存用尽时,采用 CAS+失败重试 的方式分配内存。

# 1.3 初始化零值

内存分配完成后,JVM将分配到的内存空间都初始化为零值。

这一步保证了对象的实例字段可以不赋初始值就直接使用。

# 1.4 设置对象头

初始化零值完成后,JVM开始设置对象头。

对象头主要包括2部分信息(如果是数组对象,还有一个数组长度):

1.哈希码、GC分代年龄、锁状态标识等。

2.指向类元数据的指针,JVM通过这个指针来确定这个对象是哪个类的实例。

# 1.5 执行init方法

执行innit方法,对应到语言层面上讲,就是为属性赋值(与上面的赋零值不同),和执行构造方法。

# 2 对象栈上分配

通过创建对象时的内存分配可以知道JAVA中的对象都是在堆上进行分配,需要依靠GC进行内存回收,如果对象数量较多的时候,会给GC带来较大压力,间接影响应用的性能。

JVM通过逃逸分析来确定方法内的对象会不会被外部访问(即会不会“逃逸”),如果不会逃逸就可以在栈上给该对象分配内存,这样该对象所占用的内存空间就可以随着栈帧的出栈而销毁。

栈上分配一般同时依赖逃逸分析和标量替换。

# 2.1 对象逃逸分析

就是分析方法内对象是否会被外部引用(是否逃逸),未逃逸的对象在栈上分配内存。

JDK7之后默认开启逃逸分析。

# 2.2 标量替换

标量和聚合量:

1.基础类型和对象的引用可以理解为标量,它们不能被进一步分解。

2.能被进一步分解的就是聚合量,比如:对象。

标量替换:将对象成员变量分解成分散的变量,这些分散的变量在栈帧或寄存器上分配空间,这样就不会因为没有一大块连续空间导致对象内存不够分配。

# 3 对象的内存布局

对象在内存中的布局可以分为3块区域:对象头、实例数据和对齐填充。

# 3.1 对象头

对象头主要由3部分组成:Mark Word、指向类的指针、数组长度(只有数组对象有)

# 1 Mark Word

Mark Word记录了哈希码、GC分代年龄、锁标识等信息。

在32位的JVM中长度是32bit,在64位的JVM中长度是64bit。

# 2 指向类的指针

JAVA对象的类数据保存在方法区。

该指针在32位的JVM中长度是32bit,在64位的JVM中长度是64bit。

# 3 数组长度(只有数组对象有)

该对象在32位和64位的JVM中长度都是32bit。

# 3.2 实例数据

对象真正存储的有效信息,也就是程序中定义的各种类型的字段内容。

# 3.3 对齐填充

对齐填充不是必然存在的,只是因为Hotspot要求对象大小必须是8字节的整数倍。

而对象头部分是8字节的倍数(1倍或2倍),所以实例数据部分没有对齐时,需要对齐填充来补全。

# 4 对象的访问定位

JAVA程序通过栈上的reference数据来操作堆上的具体对象。由于reference类型在JVM规范中只定义了一个指向对象的引用,没有说明这个引用通过何种方式去定位、访问堆中对象的具体位置,所以访问的方式取决于JVM的实现。目前主要的访问方式由2种:句柄和直接指针。

# 4.1 句柄

JAVA堆中会划分出一块内存来作为句柄池,reference存放的是对象的句柄地址,而句柄中包括对象实例的地址和对象Class的地址。

# 4.2 直接指针

reference存放的是对象实例地址。对象实例数据的对象头里包含Class的地址。

# 4.3 两种访问方式各有什么优势?

1.句柄访问最大的好处是reference中存储的是稳定的句柄地址,对象移动时只会改变句柄中的实例数据指针,reference不需要修改。

2.直接指针访问的好处是速度快,减少了一次指针定位的时间开销。

上次更新: 2022/06/02, 11:20:10
类加载
垃圾回收

← 类加载 垃圾回收→

最近更新
01
AI是如何学习的
06-05
02
chatGpt提示原则
06-05
03
提示词工程实践指南
06-05
更多文章>
| Copyright © 2022-2025 Kevin | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式