浅谈JVM中的JOL

JOL简介

JOL的全称是Java Object Layout。是一个用来分析JVM中Object布局的小工具。包括Object在内存中的占用情况,实例对象的引用情况等等。

JOL可以在代码中使用,也可以独立的以命令行中运行。命令行的我这里就不具体介绍了,今天主要讲解怎么在代码中使用JOL。

使用JOL需要添加maven依赖:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

添加完依赖,我们就可以使用了。

使用JOL分析VM信息

首先我们看下怎么使用JOL来分析JVM的信息,代码非常非常简单:

log.info("{}", VM.current().details());

输出结果:

# Running 64-bit HotSpot VM.

# Using compressed oop with 3-bit shift.

# Using compressed klass with 3-bit shift.

# WARNING | Compressed references base/shifts are guessed by the experiment!

# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.

# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.

# Objects are 8 bytes aligned.

# 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]

上面的输出中,我们可以看到:Objects are 8 bytes aligned,这意味着所有的对象分配的字节都是8的整数倍。

使用JOL分析String

上面的都不是重点,重点是怎么使用JOL来分成class和Instance信息。

其实java中的对象,除了数组,其他对象的大小应该都是固定的。我们先举一个最最常用的字符串来看一下:

log.info("{}",ClassLayout.parseClass(String.class).toPrintable());

上面的例子中,我们使用ClassLayout来解析一个String类,先看下输出:

[main] INFO com.flydean.JolUsage - java.lang.String object internals:

 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE

      0    12           (object header)                           N/A

     12     4    byte[] String.value                              N/A

     16     4       int String.hash                               N/A

     20     1      byte String.coder                              N/A

     21     1   boolean String.hashIsZero                         N/A

     22     2           (loss due to the next object alignment)

Instance size: 24 bytes

Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

先解释下各个字段的含义,OFFSET是偏移量,也就是到这个字段位置所占用的byte数,SIZE是后面类型的大小,TYPE是Class中定义的类型,DESCRIPTION是类型的描述,VALUE是TYPE在内存中的值。

分析下上面的输出,我们可以得出,String类中占用空间的有5部分,第一部分是对象头,占12个字节,第二部分是byte数组,占用4个字节,第三部分是int表示的hash值,占4个字节,第四部分是byte表示的coder,占1个字节,最后一个是boolean表示的hashIsZero,占1个字节,总共22个字节。但是JVM中对象内存的分配必须是8字节的整数倍,所以要补全2字节,最后String类的总大小是24字节。

如果字符串里面存了很多很多数据,那么对象的大小还是24字节吗?

这个问题问得非常有水平,下面我们就来看看怎么使用JOL来解析String对象的信息:

log.info("{}",ClassLayout.parseInstance("www.flydean.com").toPrintable());

上面的例子,我们使用了parseInstance而不是parseClass来解析String实例的信息。

输出结果:

[main] INFO com.flydean.JolUsage - java.lang.String object internals:

 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE

      0     4           (object header)                           01 c2 63 a2 (00000001 11000010 01100011 10100010) (-1570520575)

      4     4           (object header)                           0c 00 00 00 (00001100 00000000 00000000 00000000) (12)

      8     4           (object header)                           77 1a 06 00 (01110111 00011010 00000110 00000000) (399991)

     12     4    byte[] String.value                              [119, 119, 119, 46, 102, 108, 121, 100, 101, 97, 110, 46, 99, 111, 109]

     16     4       int String.hash                               0

     20     1      byte String.coder                              0

     21     1   boolean String.hashIsZero                         false

     22     2           (loss due to the next object alignment)

Instance size: 24 bytes

Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

先看结论,和String Class一样,这个String对象确实只占24字节。

实例的解析和Class解析的结果差不多,因为是实例对象,所以多了VALUE的值。

我们知道在JDK9之后,String的底层存储从Char[] 变成了Byte[]用于节约String的存储空间。上面的输出中,我们可以看到String.value值确实很长,但是保存在String中的只是Byte数组的引用地址,所以4字节就够了。

使用JOL分析数组

虽然String的大小是不变的,但是其底层数组的大小是可变的。我们再举个例子:

log.info("{}",ClassLayout.parseClass(byte[].class).toPrintable());

输出结果:

[main] INFO com.flydean.JolUsage - [B object internals:

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0    16        (object header)                           N/A

     16     0   byte [B.<elements>                             N/A

Instance size: 16 bytes

Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

类的解析结果,可以看到Byte数组占16个字节。

再看实例的情况:

log.info("{}",ClassLayout.parseInstance("www.flydean.com".getBytes()).toPrintable());

输出结果:

[main] INFO com.flydean.JolUsage - [B 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)                           22 13 07 00 (00100010 00010011 00000111 00000000) (463650)

     12     4        (object header)                           0f 00 00 00 (00001111 00000000 00000000 00000000) (15)

     16    15   byte [B.<elements>                             N/A

     31     1        (loss due to the next object alignment)

Instance size: 32 bytes

Space losses: 0 bytes internal + 1 bytes external = 1 bytes total

可以看到数组的大小真的变化了,这次变成了32字节。

使用JOL分析自动装箱

我们知道,java中的基本类型都有一个和它对于的Object类型,比如long和Long,下面我们来分析下他们两个在JVM中的内存区别:

log.info("{}",ClassLayout.parseClass(Long.class).toPrintable());

输出结果:

[main] INFO com.flydean.JolUsage - java.lang.Long object internals:

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0    12        (object header)                           N/A

     12     4        (alignment/padding gap)                  

     16     8   long Long.value                                N/A

Instance size: 24 bytes

Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

可以看到1个Long对象是占24个字节的,但是其中真正存储long的value只占8个字节。

看一个实例:

log.info("{}",ClassLayout.parseInstance(1234567890111112L).toPrintable());

输出结果:

[main] INFO com.flydean.JolUsage - java.lang.Long object internals:

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           9a 15 00 00 (10011010 00010101 00000000 00000000) (5530)

     12     4        (alignment/padding gap)                  

     16     8   long Long.value                                1234567890111112

Instance size: 24 bytes

Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

使用JOL分析引用关系

上面我们使用JOL分析的是class内部的空间使用情况,那么如果有外部引用可不可以分析呢?

HashMap hashMap= new HashMap();
hashMap.put("flydean","www.flydean.com");
log.info("{}", GraphLayout.parseInstance(hashMap).toPrintable());

上面我们使用一个不同的layout:GraphLayout,它可以用来分析外部引用情况。

输出结果:

[main] INFO com.flydean.JolUsage - java.util.HashMap@57d5872cd object externals:

          ADDRESS       SIZE TYPE                      PATH                           VALUE

        7875f9028         48 java.util.HashMap                                        (object)

        7875f9058         24 java.lang.String          .table[14].key                 (object)

        7875f9070         24 [B                        .table[14].key.value           [102, 108, 121, 100, 101, 97, 110]

        7875f9088         24 java.lang.String          .table[14].value               (object)

        7875f90a0         32 [B                        .table[14].value.value         [119, 119, 119, 46, 102, 108, 121, 100, 101, 97, 110, 46, 99, 111, 109]

        7875f90c0         80 [Ljava.util.HashMap$Node; .table                         [null, null, null, null, null, null, null, null, null, null, null, null, null, null, (object), null]

        7875f9110         32 java.util.HashMap$Node    .table[14]                     (object)

从结果我们可以看到HashMap本身是占用48字节的,它里面又引用了占用24字节的key和value。

总结

使用JOL可以分析java类和对象,这个对于我们对JVM和java源代码的理解和实现都是非常有帮助的。

以上就是浅谈JVM中的JOL的详细内容,更多关于JVM中的JOL的资料请关注我们其它相关文章!

(0)

相关推荐

  • docker 查看jvm内存占用方式

    一.进入docker容器的宿主机,查看运行指定镜像的容器id(结果的第一列): docker ps | grep myImageName(或docker ps | grep java) 二.进入容器内部: docker exec -it containerId sh 三.直接输入top命令: top 可看到基本的容器占用的信息:pid.vsz.cpu.command等.(ctrl+c 或 q,退出top) 四.查看更具体的jvm内存占用: top -m 其中,vsz:Virtual Memory

  • java虚拟机之JVM调优详解

    JVM常用命令行参数 1. 查看参数列表 虚拟机参数分为基本和扩展两类,在命令行中输入 JAVA_HOME\bin\java就可得到基本参数列表. 在命令行输入 JAVA_HOME\bin\java –X就可得到扩展参数列表. 2. 基本参数说明: -client,-server: 两种Java虚拟机启动方式,client模式启动比较快,但是性能和内存管理相对较差,server模式启动比较慢,但是运行性能比较高,windos上采用的是client模式,Linux采用server模式 -class

  • JVM常量池的深入讲解

    提示:这里咱们要说的常量池,常量池就是咱们面试中所说的常量池,谈谈你对常量池的认识?面试官一问咱们就懵逼了,你要记得你脑子中有一张图!!! 剩下的就好办了 提示:请各位大佬批评指正!! 前言 提示:学习的时候会有点头疼哦 一.Class常量池与运行时常量池 Class常量池可以理解为是Class文件中的资源仓库. Class文件中除了包含类的版本.字段.方法.接口等描述信息外,还有一项信息就是 常量池(constant pool table) ,用于存放编译期生成的各种 字面量(Literal)

  • jvm运行原理以及类加载器实例详解

    JVM运行原理 首先从".java"代码文件,编译成".class"字节码文件,然后类加载器将".class"字节码文件中的类给加载带JVM中,最后就是JVM执行写好的代码.执行过程如下图 类加载器 类加载过程 加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载 加载 一旦JVM进程启动之后,一定会先把类加载到内存中,然后从main()方法的入口代码开始执行 public class

  • 详解JVM 中的StringTable

    是什么 字符串常量池是 JVM中的一个重要结构,用于存储JVM运行时产生的字符串.在JDK7之前在方法区中,存储的是字符串常量.而字符串常量池在 JDK7开始移入堆中,随之而来的是除了存储字符串常量外,还可以存储字符串引用(因为在堆中,引用堆中的字符串常量很方便,所以可以存储引用).这使得很多字符串的操作在 JDK7中和在之前的版本中执行是不同的结果.这也是为什么字符串相关的问题是如此具有迷惑性的原因之一. 底层 String:在 JDK9之前,String底层是使用 char数组来存储字符串数

  • Java 汇编JVM编写jasmin程序的操作方法

    Jasmin是Java汇编语言,以文本方式来描述JVM的指令集以及Java Class的结构,Jasmin编译器可以把汇编语言转换成二进制的字节码,使JVM可以调入执行. Jasmin最初是由Jon Meyer和Troy Downing编纂<Java Virtual Machine>时设计的范例,虽然该书不再出版,但是Jasmin成为了事实上的Java汇编语言标准,并作为开源项目得到发展:http://jasmin.sourceforge.net/. Jasmin在Java class方面的处

  • 详解jvm对象的创建和分配

    对象的创建 创建方式 1. new 关键字直接创建. new ObjectName(). 2.通过 Class 反射对象的 newInstance() 方法.ObjectName  obj  =  ObjectName.class.newInstance(). 3.通过 Class 反射对象获取 Constructor 类,再调用其 newInstance() 方法. ObjectName obj = ObjectName.class.getConstructor.newInstance().

  • 浅谈JVM垃圾回收之哪些对象可以被回收

    1.背景 Java语言相比于C和C++,一个最大的特点就是不需要程序员自己手动去申请和释放内存,这一切交由JVM来完成.在Java中,运行时的数据区域分为程序计数器.Java虚拟机栈.本地方法栈.方法区和堆.其中,程序计数器.虚拟机栈和本地方法栈是线程私有的,线程销毁后自动释放.垃圾回收的行为发生在堆和方法区,主要是堆,而堆中存储的主要是对象.那么自然而然地就会有这么几个问题,哪些对象可以被回收?通过什么方式回收?本文主要探讨第一个问题,以及JVM对Java中几种引用的回收策略. 2.如何判断一

  • java之jvm加载器例举

    在java的学习中,对于jvm模块我们会不断补充一些知识点,毕竟jvm是比较重要的一个组成部分.本篇围绕jvm加载器展开介绍,在加载类的时候,我们的加载器会逐个进行工作,在具体的加载器类型上,想必大家还不是很清楚.下面我们就java之jvm加载器的4中类型带来介绍. 1.Bootstrap ClassLoader (引导类加载器) 负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现的核心库,也就是JVM调用每个系统的API实现系统功能,实现跨平台的主要模块

  • 浅谈JVM中的JOL

    JOL简介 JOL的全称是Java Object Layout.是一个用来分析JVM中Object布局的小工具.包括Object在内存中的占用情况,实例对象的引用情况等等. JOL可以在代码中使用,也可以独立的以命令行中运行.命令行的我这里就不具体介绍了,今天主要讲解怎么在代码中使用JOL. 使用JOL需要添加maven依赖: <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-co

  • 浅谈jvm中的垃圾回收策略

    java和C#中的内存的分配和释放都是由虚拟机自动管理的,此前我已经介绍了CLR中GC的对象回收方式,是基于代的内存回收策略,其实在java中,JVM的对象回收策略也是基于分代的思想.这样做的目的就是为了提高垃圾 回收的性能,避免对堆中的所有对象进行检查时所带来的程序的响应的延迟,因为jvm执行GC时,会stop the word,即终止其它线程的运行,等回收完毕,才恢复其它线程的操作.基于分代的思想是:jvm在每一次执行垃圾收集器时,只是对一小部分内存 对象引用进行检查,这一小部分对象的生命周

  • 浅谈JVM系列之JIT中的Virtual Call

    Virtual Call和它的本质 有用过PrintAssembly的朋友,可能会在反编译的汇编代码中发现有些方法调用的说明是invokevirtual,实际上这个invokevirtual就是Virtual Call. Virtual Call是什么呢? 面向对象的编程语言基本上都支持方法的重写,我们考虑下面的情况: private static class CustObj { public void methodCall() { if(System.currentTimeMillis()==

  • 浅谈Java中的四种引用方式的区别

    强引用.软引用.弱引用.虚引用的概念 强引用(StrongReference) 强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用: Object object = new Object(); String str = "hello"; 只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象. 比如下面这段代码: public class Main { publi

  • 浅谈hibernate中对象的3种状态_瞬时态、持久态、脱管态

    Hibernate的对象有3种状态,分别为:瞬时态(Transient). 持久态(Persistent).脱管态(Detached).处于持久态的对象也称为PO(Persistence Object),瞬时对象和脱管对象也称为VO(Value Object). • 瞬时态 由new命令开辟内存空间的java对象, eg. Person person = new Person("amigo", "女"); 如果没有变量对该对象进行引用,它将被java虚拟机回收. 瞬

  • 浅谈java中类名.class, class.forName(), getClass()的区别

    Class对象的生成方式如下: 1.类名.class    说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作.返回Class的对象 2.Class.forName("类名字符串")  (注:类名字符串是包名+类名) 说明:装入类,并做类的静态初始化,返回Class的对象 3.实例对象.getClass()  说明:对类进行静态初始化.非静态初始化:返回引用o运行时真正所指的对象(因为:子对象的引用可能会赋给父对象的引用变量中)所属的类的Cla

  • 浅谈Java中的class类

    Class 类是在Java语言中定义一个特定类的实现.一个类的定义包含成员变量,成员方法,还有这个类实现的接口,以及这个类的父类.Class类的对象用于表示当前运行的 Java 应用程序中的类和接口. 比如:每个数组均属于一个 Class 类对象,所有具有相同元素类型和维数的数组共享一个Class 对象.基本的 Java 类型(boolean, byte, char, short,int, long, float 和 double) 和 void 类型也可表示为 Class 对象. 以下示例使用

  • 浅谈Java中是否直接可以使用enum进行传输

    背景 我们在进行传输的时候 会有一些状态值,如Status为1代表删除,为0代表失败或者怎么样的.只传输一个)0或者1过去给第三方(此处不包括给前端),如果没有契约第三方会不认识你这个是什么意思,那我们在平时写业务逻辑的时候使用枚举很轻易就知道了什么状态什么值.所以我们在构建DTO对象的时候里面放一个枚举来表示. 首先在阿里的规范里是这样说的: [强制]二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象. 那到底为啥不能用呢? 枚举

  • 浅谈Java中File文件的创建以及读写

    1.创建一个文件 @Test public void test6() throws IOException { File file1 = new File("C:\\IDEA\\h1.txt"); if(!file1.exists()){//文件不存在 file1.createNewFile(); System.out.println("创建成功"); }else{//文件存在 file1.delete(); System.out.println("删除成

  • 浅谈JVM之java class文件的密码本

    简介 机器可以读,人为什么不能读?只要我们掌握java class文件的密码表,我们可以把二进制转成十六进制,将十六进制和我们的密码表进行对比,就可以轻松的解密了. 下面,让我们开始这个激动人心的过程吧. 一个简单的class 为了深入理解java class的含义,我们首先需要定义一个class类: public class JavaClassUsage { private int age=18; public void inc(int number){ this.age=this.age+

随机推荐