java对象内存布局和对象定位

article/2025/9/18 1:50:18

目录

观察对象在内存中的存储布局

利用java agent

利用JOL工具

Java对象定位

直接指针寻址

间接寻址

JIT(Just In-Time Compiler)

c1、c2 编译线程

c1, c2编译器线程高CPU消耗 - 潜在的解决方案

使用字节码和汇编语言同步分析volatile,synchronized的底层实现

hsdis(Hotspot Disassembly)

JITWatch(Just In Time Compiler)

Assembly not found. Was -XX:+PrintAssembly option used?

Not JIT-compiled

CMPXCHG


观察对象在内存中的存储布局

 

利用java agent

(class文件到内存之间的代理,这个代理得自己去实现)的机制。

import java.lang.instrument.Instrumentation;/*** @author zhousong* @ClassName 打印对象内存布局类* @description:* 1.在src目录下创建* Manifest-Version: 1.0* Created-By: mashibing.com* Premain-Class: java.lang.instrument.Instrumentation.打印对象内存布局类* 2.打jar包,在需要使用该Agent Jar的项目中引入该Jar包* 3.运行时候加上虚拟机参数-javaagent:d:\toker\打印对象内存布局类.jar* 4.使用的时候-打印对象内存布局类.sizeOf(new Object()));即可* @datetime 2022年 11月 07日 15:31* @version: 1.0*/
public class 打印对象内存布局类 {private static Instrumentation inst;static void premain(String agentArgs,Instrumentation _inst){inst=_inst;}static long sizeOf(Object o){return inst.getObjectSize(o);}
}

利用JOL工具

       /***# Running 64-bit HotSpot VM.* # Using compressed oop with 3-bit shift.* # Using compressed klass with 3-bit shift.* # Objects are 8 bytes aligned.: 8字节对齐,不足就补充字节数* # Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]* # Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]*/System.out.println(VM.current().details());/***com.toker.cloud.concurrent.jvm.testcase.ObjectJVMCase1 object internals:*  OFFSET  SIZE   TYPE DESCRIPTION                               VALUE*       0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)*       4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)*       8     4        (object header)                           81 22 01 f8 (10000001 00100010 00000001 11111000) (-134143359)*      12     4        (loss due to the next object alignment)* Instance size: 16 bytes* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total** 通过第一个print的结果可以看到vm的对象是有8字节对齐的限制,在这里空对象是4+4=4=12个字节,没有向8个字节对齐* 因此最后补充了4个字节。因此可以看到Space losses空间损失了4个字节。  对象总大小为12+4=16个字节大小*<!-- JOL 工具开始:研究对象的内存结构-->
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>${jol.version}</version>
</dependency><!-- JOL 工具结束:研究对象的内存结构 -->package com.toker.cloud.concurrent.jvm;
import com.toker.cloud.concurrent.jvm.testcase.对象内存布局测试类1;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;/*** @author zhousong* @ClassName 对象内存布局 JOL: java object layout,用来观察java对象*       <!-- JOL 工具开始:研究对象的内存结构-->*             <dependency>*                 <groupId>org.openjdk.jol</groupId>*                 <artifactId>jol-core</artifactId>*                 <version>${jol.version}</version>*             </dependency>*     ClassLayout.parseInstance(t).toPrintable();* @description: 对象在内存中的基本组成* @datetime 2022年 11月 02日 17:21* @version: 1.0*/
public class 对象内存布局 {public static void main(String[] args) {*/对象内存布局测试类1 objectJVMCase1 = new 对象内存布局测试类1();System.out.println(ClassLayout.parseInstance(objectJVMCase1).toPrintable());}
}

Java对象定位

《Java虚拟机规范》中只定义了reference用来指向一个对象引用,并没有规定这个引用应该通过什么方式定位,如何去实现。reference数据存储在栈中引用堆中的对象,主流的实现方式有两种,一种是直接寻址,一种是间接寻址(句柄池).

直接指针寻址

直接寻址就是reference中直接存储的就是堆中对象的地址,如果不止访问对象本身,还需要访问对象类型Class数据就再加一次指针定位的开销,直接寻址的好处,它的速度快,相比于句柄访问(间接寻址)节省了还要在堆中通过句柄吃寻址的这一次寻址的开销,而且java程序运行过程中,访问对象是一件极其频繁的操作,省去的这一次寻址也是一项非常可观的执行成本。HotSpot虚拟机就是采用这种方式进行对象的访问。

间接寻址

java堆中划分出一块区域作为句柄池,reference中存储的就是句柄池的地址,句柄池中存储了对象的实例数据与类型数据的地址信息。使用间接定位对象的方式也有优点,堆是垃圾收集器工作的主要区域,在这里会进行频繁的垃圾收集操作,在进行垃圾收集时会涉及大量的对象移动,在频繁的移动对象时,只需要修改句柄的指向实例数据的指针即可,不用频繁的更改栈中的reference变量的值。

JIT(Just In-Time Compiler)

JVM是采用默认混合模式编译代码的,所以说java不能单纯说是解释型语言还是编译型语言

/*** @author zhousong* @ClassName JVM混合模式验证* @description: 默认混合模式,所以说java不能单纯说是解释型语言还是编译型语言* -Xmixed:默认为混合模式,启动速度较快,对热点代码实行检测和编译* -Xint:使用解释模式,通过bytecode intepreter解释器执行,启动很快,执行稍慢* -Xcomp:使用纯编译模式,通过JIT(Just In-Time Compiler)编译器,执行很快,启动很慢(很多类库的时候) * * @datetime 2022年 11月 02日 16:34* @version: 1.0*/
public class JVM混合模式验证 {/***1、什么时候会用JIT编译成本地代码呢?*      刚刚开始是用解释器执行,执行过程中有某一段代码执行的频率特别高(1s中执行几十万次),*      JVM就会把这段代码编译成本地代码(类似用C语言编译本地*.exe的文件),再执行该段代码时就不会用解释器解释来执行了,提升效率。*2、为什么不直接编译成本地代码,提高执行效率呢*      1)java解释器的执行效率其实也很高了,在某些代码的执行效率上不一定输于执行本地代码*      2)如果执行的代码引用类库特别多,在执行启动时时间会非常长*/public static void main(String[] args) {long start = System.currentTimeMillis();//这是Java7引入的新特性。分割数字增强可读性for(int i=0; i<10_0000; i++) {for (int j = 0; j <10_0000 ; j++) {long m=j%3;}}long end =System.currentTimeMillis();/***当jvm的配置是-Xint, 总计花费时间:104757(ms)*当jvm的配置是-Xcomp, 总计花费时间:2(ms)* 默认,总计花费时间:3(ms)*/System.out.println("总计花费时间:"+(end-start)+"(ms)");}
}

c1、c2 编译线程

什么是Hotspot JIT编译器?

你的应用程序可能有数百万行的代码。然而,只有一小部分代码会被反复执行。这个小的代码子集(也被称为 "热点")负责你的应用程序的性能。在运行时,JVM使用这个JIT(Just in time)编译器来优化这个热点代码。大多数时候,由应用程序开发人员编写的代码并不是最佳的。因此,JVM的JIT编译器对开发者的代码进行优化,以提高性能。为了进行这种优化,JIT编译器使用C1、C2编译器线程。

c1和c2编译器线程之间的区别是什么?

在Java的早期,有两种类型的JIT编译器。

a. 客户端

b. 服务器

根据你想使用的JIT编译器的类型,必须下载和安装适当的JDK。例如,如果你正在构建一个桌面应用程序,那么需要下载具有 "客户端 "JIT编译器的JDK。如果你要构建一个服务器应用程序,那么就需要下载具有 "服务器 "JIT编译器的JDK。

客户端 JIT 编译器会在应用程序启动后立即开始编译代码。服务器JIT编译器将观察代码的执行情况相当一段时间。基于它获得的执行知识,它将开始进行JIT编译。尽管服务器JIT编译速度很慢,但它产生的代码将比客户端JIT编译器产生的代码更优秀、更有性能。

今天,现代的JDK同时带有客户端和服务器JIT编译器。这两种编译器都试图优化应用程序的代码。在应用程序启动时,代码是用客户端JIT编译器编译的。后来,随着知识的增加,代码就用服务器JIT编译器进行编译。这被称为JVM中的分层编译。

JDK的开发者称他们为客户端和服务器JIT编译器,内部称为c1和c2编译器。因此,客户端JIT编译器使用的线程被称为c1编译器线程。服务器JIT编译器使用的线程被称为c2编译器线程。

c1、c2编译器线程的默认大小

c1、c2编译器线程的默认数量是根据运行应用程序的容器/设备上可用的CPU数量决定的。下面的表格总结了c1、c2编译器线程的默认数量。

CPUs c1 threads c2 threads

1 1 1

2 1 1

4 1 2

8 1 2

16 2 6

32 3 7

64 4 8

128 4 10

你可以通过向你的应用程序传递'-XX:CICompilerCount=N'JVM参数来改变编译器线程数。你在'-XX:CICompilerCount'中指定的数量的三分之一将被分配给c1编译器线程。剩余的线程数将被分配给c2编译器线程。假设你要用6个线程(即'-XX:CICompilerCount=6'),那么2个线程将被分配给c1编译器线程,4个线程将被分配给c2编译器线程。

c1, c2编译器线程高CPU消耗 - 潜在的解决方案

意义不大,不用讲了。无非就是几个参数稍微了解下就行了:-XX:-TieredCompilation、-XX:TieredStopAtLevel=N、-XX:ReservedCodeCacheSize=N、-XX:CICompilerCount

使用字节码和汇编语言同步分析volatile,synchronized的底层实现

要查看JIT生成的汇编代码,要先装一个反汇编器:hsdis。从名字来看,即HotSpot disassembler。

地址如下:profiling/bin at master · jkubrynski/profiling · GitHub

hsdis(Hotspot Disassembly)

C:\Users\Administrator>java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -version Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled java version "1.8.0_73" Java(TM) SE Runtime Environment (build 1.8.0_73-b02) Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

1、安装cgwin,Cygwin

2、下载binutils:简书

如果不想编译这么麻烦,直接去下个hsdis-amd64.dll放到jdk1.8.0_144\jre\bin\server,然后执行下面语句测试是否成功

C:\Users\Administrator>java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -version Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output Loaded disassembler from C:\Program Files\Java\jre1.8.0_73\bin\server\hsdis-amd64.dll Decoding compiled method 0x0000000003350a10:

JITWatch(Just In Time Compiler)

好像不能编译注释,我去掉了注释.代码想要用肉眼去看实在是太难找到自己想要看的代码最后翻译成的汇编在哪里。实在是太多了。所以需要借助一些工具的帮助。这里推荐使用的是JITWatch这个工具

  • 点击sandbox

Assembly not found. Was -XX:+PrintAssembly option used?

一般先排查日志文件,可以在该软件看到日志文件输出位置如sandbox.log:

1、我本地发现报如下错误:Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled

此时检查想到可能是hsdis-amd64.dll没有加载,发现是hsdis-amd64.dl放错了位置,copy到整正的jre环境即可

{我之前放的位置是:C:\Program Files\Java\jre1.8.0_73\bin\server,而我们的jre环境是C:\Program Files\Java\jdk1.8.0_73\jre\bin\server}

2、Sandbox Configuation中vm的参数没有加上-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

Not JIT-compiled

这个是因为没有可以重复的代码块可以优化的,所以JIT没有启作用,而只是jvm编译器起的作用,加个for循环就行了

public class JITAssembly {public volatile long sum =0;public void calcSum(int x){sum+=x;}public static void main(String[] args) {JITAssembly jitAssembly = new JITAssembly();for(int i=0;i<100000;i++){jitAssembly.calcSum(2);}       System.out.println(jitAssembly.sum);     }
}

反编译和反汇编的结果如下:

从上面可以看到volatile的反汇编后putfield之前调用lock,intel处理器的lock指令如下:

JIT启动条件(循环是100的时候没有启动JIT,但是当循环1000的时候就启动JIT)

实际测试观察synchronized的汇编的时候发现有意思的事情,但循环是100的时候没有启动JIT,但是当循环1000的时候就启动了。所以JIT判断重复代码块是有重复率判断依据的,这个依据以后再仔细说;

循环条件是100的时候

循环条件是10000的时候

可以看到synchronized的底层锁用的是CMPXCHG

CMPXCHG

含义: 比较并交换指令

用法:目的操作数和累加操作数(AH、AL、EAX)进行比较,如果相等(ZF=1),则将源操作数复制到目的操作数中,否则将目的操作数复制到累加器中。


http://chatgpt.dhexx.cn/article/KuErUsJN.shtml

相关文章

【C/C++ 集成内存调试、内存泄漏检测和性能分析的工具 Valgrind 】Linux 下 Valgrind 工具的全面使用指南

目录标题 Valgrind 工具的安装 交叉编译Valgrind Valgrind 工具的作用Memcheck 内存泄漏检测工具常规检测&#xff08;程序结束后生成报告&#xff09;重要参数长时间运行的服务使报告输出至文件报告分析示例一分析\_示例一示例二分析\_示例二 Massif堆栈检测工具Massif的基本…

C/C++开发,无可避免的多线程(篇一).跨平台并发编程姗姗来迟

目录 一、编译环境准备 1.1 编译环境配置 1.2 先睹为快的c11多线程编程示例 二、c/c并发编程支持 2.1 进程与线程 2.2 c/c多线程的并发编程 2.3 c11以前的多线程支持 2.4 c11以后的多线程支持 2.5 线程与互斥锁 2.6 跨线程安全问题 三、认识c/c的thread 3.1 std::thread类 3…

windows利器之cygwin

好久没写文章了&#xff0c;诸事烦身&#xff0c;脱不开。。。 不想开虚拟机占内存&#xff0c;同时还想在windows下使用linux命令&#xff0c;那就是cygwin了 第一部分 去官网下一个exe https://cygwin.com/install.html 直接运行&#xff0c; 基本都是默认&#xff0c; …

spi时序图怎么分析,怎么看懂spi时序图

我做产品的时候&#xff0c;最怕就是做IIC和SPI的通信。 这两种协议时序哪怕是延时时间有误差&#xff0c;都有可能导致通信不上。 出现问题的时候&#xff0c;如果没设备也很难排查问题到底出在哪里。 有时候这个产品写好的时序程序&#xff0c;换一个单片机用同一个程序移植过…

怎么看懂单片机时序图?

本人没有上过单片机相关的专业课&#xff0c;是在《计算机系统结构》里遇见的时序图。由于看不懂加之老师没有专门讲&#xff0c;因此自行查阅了相关的视频和博客。&#xff08;参考视频已放在文末&#xff09; 网上资源贫瘠&#xff0c;不过我也不需要太过深入的知识。 大家…

UML系列——时序图(顺序图)

引言 用例图、类图、活动图、时序图之间是什么关系&#xff1f; 时序图有什么作用&#xff1f; 先来模拟一下三国演义的赤壁之战的时序图&#xff0c;先知道它到底长什么样子&#xff0c;再深入介绍&#xff1a; 小伙伴惊呆了&#xff0c;这样画战略图&#xff0c;一目了然&…

UML图之五——时序图

一、什么是时序图 序列图表示了系统在参与者互动执行某一个用例期间,系统内部的一群对象的协作情况。一个用例就对应一个时序图。序列图是对系统动态行为进行描述。用于用例分析和设计阶段。 二、对比 1、序列图和协作图 序列图跟协作图相似,两种图传递的意思是一样的。不…

浅谈时序图

前序&#xff1a; 这周基本都在肝txmini项目&#xff0c;不知道是不是我菜&#xff0c;总觉得难度还是挺高的&#xff0c;所以就没太多时间整理知识点了。 项目进展了快大半个月了&#xff0c;最后因为性能问题&#xff0c;我们还是选择重构原来的代码。准备用golang去重构原本…

读懂时序图

UML建模之时序图&#xff08;Sequence Diagram&#xff09; 时序图简介&#xff08;Brief introduction&#xff09;时序图元素&#xff08;Sequence Diagram Elements&#xff09; 角色&#xff08;Actor&#xff09;对象&#xff08;Object&#xff09;生命线&#xff08;Li…

时序图的概念

一. 时序图 (Sequence Diagram) 时序图 : 显示对象之间的关系, 强调对象之间消息的时间顺序, 显示对象之间的交互; 时序图是一个二维图&#xff0c;横轴表示对象&#xff0c;纵轴表示时间&#xff0c;消息在各对象之间横向传递&#xff0c;依照时间顺序纵向排列。 1.时序图的…

UML之时序图详解

作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/17927131 动态图概念 : 从静态图中抽取瞬间值的变化描述系统随时间变化的行为, 动态图包括交互图活动图状态图, 这篇博客研究交互图 包括时序图和协作图; – 时序图 : 显示对象之间的关…

时序图简介

什么是时序图 来自百度百科的介绍&#xff1a; “又名序列图、循序图&#xff0c;是一种UML交互图。它通过描述对象之间发送消息的时间顺序显示多个对象之间的动态协作。它可以表示用例的行为顺序&#xff0c;当执行一个用例行为时&#xff0c;其中的每条消息对应一个类操作或…

时序图

一. 时序图 (Sequence Diagram) 1.时序图的概念 时序图定义 : 描述了对象之间传递消息的时间顺序, 用来表示用例中的行为顺序, 是强调消息时间顺序的交互图; 时序图描述的事物: 时序图描述系统中类和类之间的交互, 将这些交互建模成消息交换, 时序图描述了类以及类之间的交换…

时序图,程序员的保命技能

最近&#xff0c;各个大厂都在开猿节流&#xff0c;程序员们在公司里&#xff0c;靠什么技能来“保命”&#xff1f; 一方面&#xff0c;大家要有过硬的编程基础&#xff0c;另一方面&#xff0c;对UML图的掌握也很重要。今天&#xff0c;我们就来说一说其中一种重要的UML图&am…

Spring Boot异步任务、异步消息

目录 1.异步任务 1.1.概述 1.2.使用 2.异步消息 2.1.概述 2.2.使用 1.异步任务 1.1.概述 举一个例子&#xff0c;我现在有一个网上商城&#xff0c;客户在界面点击下单后&#xff0c;后台需要完成两步&#xff1a; 1.创建客户订单 2.发短信通知客户订单号 这里面第2…

python_异步

深入理解 Python 异步编程 前言 很多朋友对异步编程都处于“听说很强大”的认知状态。鲜有在生产项目中使用它。而使用它的同学&#xff0c;则大多数都停留在知道如何使用 Tornado、Twisted、Gevent 这类异步框架上&#xff0c;出现各种古怪的问题难以解决。而且使用了异步框架…

httpx 与 异步

前言 HTTPX 是新一代的 python 解析库&#xff0c;它是一个功能齐全的 HTTP 客户端&#xff0c;用于Python 3&#xff0c;较requests&#xff08;只能发送同步请求&#xff09;和 aiohttp&#xff08;只能发送异步请求&#xff09;不同的是&#xff0c;它同时提供同步和异步 AP…

一文了解异步编程基础

什么是异步编程&#xff1f; 异步编程是指并发编程的范式&#xff0c;其中除了单个主应用程序线程之外&#xff0c;工作可以委托给一个或多个并行工作线程。这被称为非阻塞系统&#xff0c;其中整体系统速度不受订单执行的影响&#xff0c;并且多个进程可以同时发生。 函数从 …

异步函数async

什么是同步异步 在最新的ES7&#xff08;ES2017&#xff09;中提出的前端异步特性&#xff1a;async、await。 在了解async和await之前得先明白什么是同步函数&#xff0c;什么是异步函数。 同步函数&#xff1a;当一个函数是同步执行时&#xff0c;那么当该函数被调用时不会…

异步(async、await)

同步与异步 同步&#xff1a;如果一个程序调用某个方法&#xff0c;等待其执行所有处理后才继续执行&#xff0c;我们就称这样的方法是同步的&#xff0c;这是默认的形式异步&#xff1a;异步的方法在处理完成之前就返回到调用方法&#xff0c;C#的async/await特性可以创建并使…