JVM深入学习(九)-运行时数据区之对象的布局和定位

简介: JVM深入学习(九)-运行时数据区之对象的布局和定位

6.1 对象创建的方式

  1. new
  1. 单例也算new的方式
  2. 建造者模式和工厂模式产生的对象都是new
  1. StringBuilder
  2. BeanFactory
  1. Class.newInstance
  2. Constructor.newInstance
  3. clone() 需实现clonable接口
  4. 反序列化,可以从二进制流中反序列化出对象
  5. 第三方库Objenesis

测试代码:

package com.zy.study11;


import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

import com.zy.study10.MethodAreaTest;

import org.objenesis.ObjenesisStd;


import java.io.*;

import java.lang.reflect.Constructor;

import java.lang.reflect.InvocationTargetException;


/**

* @Author: Zy

* @Date: 2021/9/1 14:55

* 测试对象创建的几种方法

* 1.new

* 1.1单例也算new的方式

* 1.2建造者模式和工厂模式产生的对象都是new

* 1.2.1StringBuilder

* 1.2.2BeanFactory

* 2.Class.newInstance

* 3.Constructor.newInstance

* 4.clone() 需实现clonable接口

* 5.反序列化,可以从二进制流中反序列化出对象

* 6.第三方库Objenesis 引包

*/

public class ObjectCreateTest {


   static class ObjectCreate implements Cloneable, Serializable {

       private int x;

       private int y;


       public ObjectCreate(int x, int y) {

           this.x = x;

           this.y = y;

       }


       public ObjectCreate() {

       }


       @Override

       protected Object clone() throws CloneNotSupportedException {

           return super.clone();

       }

   }


   public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, CloneNotSupportedException, IOException, ClassNotFoundException {

       //1. new

       ObjectCreate objectCreate = new ObjectCreate();

       System.out.println("new对象的内存地址: "+objectCreate);


       //2. Class.newInstance

       ObjectCreate objectCreate1 = ObjectCreate.class.newInstance();

       System.out.println("Class.newInstance对象的内存地址: "+objectCreate1);


       //3.Constructor.newInstance

       ObjectCreate objectCreate2 = ObjectCreate.class.getConstructor().newInstance();

       System.out.println("Constructor.newInstance对象的内存地址: "+objectCreate2);


       // 4.clone

       ObjectCreate clone = (ObjectCreate) objectCreate.clone();

       System.out.println("clone对象的内存地址: "+clone);


       // 5.反序列化

       ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

       new ObjectOutputStream(byteArrayOutputStream).writeObject(objectCreate);

       ObjectCreate unSerializable = (ObjectCreate) new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())).readObject();

       System.out.println("反序列化对象的内存地址: "+unSerializable);


       // 6.第三方库 Objenesis

       ObjenesisStd objenesisStd = new ObjenesisStd();

       ObjectCreate objectCreate3 = objenesisStd.getInstantiatorOf(ObjectCreate.class).newInstance();

       System.out.println("Objenesis生成的内存地址: "+objectCreate3);


   }

}


运行结果:


6.2 对象创建的过程

6.2.1 从字节码中看

一行简单的代码

Object obj = new Object();

查看字节码

Classfile /E:/��Ң/idea��Ŀ/jvm/target/classes/com/zy/study11/ObjectCreateProcessTest.class

 Last modified 2021-9-1; size 491 bytes

 MD5 checksum 26134ce9ee2f969bfbee2050f921671d

 Compiled from "ObjectCreateProcessTest.java"

public class com.zy.study11.ObjectCreateProcessTest

 minor version: 0

 major version: 52

 flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

  #1 = Methodref          #2.#19         // java/lang/Object."<init>":()V

  #2 = Class              #20            // java/lang/Object

  #3 = Class              #21            // com/zy/study11/ObjectCreateProcessTest

  #4 = Utf8               <init>

  #5 = Utf8               ()V

  #6 = Utf8               Code

  #7 = Utf8               LineNumberTable

  #8 = Utf8               LocalVariableTable

  #9 = Utf8               this

 #10 = Utf8               Lcom/zy/study11/ObjectCreateProcessTest;

 #11 = Utf8               main

 #12 = Utf8               ([Ljava/lang/String;)V

 #13 = Utf8               args

 #14 = Utf8               [Ljava/lang/String;

 #15 = Utf8               obj

 #16 = Utf8               Ljava/lang/Object;

 #17 = Utf8               SourceFile

 #18 = Utf8               ObjectCreateProcessTest.java

 #19 = NameAndType        #4:#5          // "<init>":()V

 #20 = Utf8               java/lang/Object

 #21 = Utf8               com/zy/study11/ObjectCreateProcessTest

{

 public com.zy.study11.ObjectCreateProcessTest();

   descriptor: ()V

   flags: ACC_PUBLIC

   Code:

     stack=1, locals=1, args_size=1

        0: aload_0

        1: invokespecial #1                  // Method java/lang/Object."<init>":()V

        4: return

     LineNumberTable:

       line 8: 0

     LocalVariableTable:

       Start  Length  Slot  Name   Signature

           0       5     0  this   Lcom/zy/study11/ObjectCreateProcessTest;


 public static void main(java.lang.String[]);

   descriptor: ([Ljava/lang/String;)V

   flags: ACC_PUBLIC, ACC_STATIC

   Code:

     stack=2, locals=2, args_size=1

     ##########重点##################

        0: new           #2                  // class java/lang/Object

        3: dup

        4: invokespecial #1                  // Method java/lang/Object."<init>":()V

     #####################################

        7: astore_1

        8: return

     LineNumberTable:

       line 11: 0

       line 12: 8

     LocalVariableTable:

       Start  Length  Slot  Name   Signature

           0       9     0  args   [Ljava/lang/String;

           8       1     1   obj   Ljava/lang/Object;

}

SourceFile: "ObjectCreateProcessTest.java"


可以看到执行的过程:

  1. new命令,用到了#2也就是object的class对象,主要作用是:
  1. 判断这个类是否已经加载到方法区了,如果没有,类加载器进行加载
  2. 在堆中为这个对象开辟内存空间
  1. dup 在栈中创建两个引用指向堆中的内存地址
  2. invokespecial 调用Object类的构造器,#1也可以从常量池中看到就是Object的构造器方法

6.2.2 对象的创建步骤

  1. 对象类型信息的加载,链接,初始化
  1. 查看元空间是否存在该类的类元信息,如果没有,使用类加载器对此类进行加载(双亲委派机制),并生成Class对象,如果加载过程中找不到对应的.class文件,就会报ClassNotFoundException.
  1. 为对象分配内存空间,根据内存空间是否规整来判断,而内存空间是否规整又跟垃圾回收器有关系,有些垃圾回收器有压缩算法,在回收后就导致内存空间不规整.
  1. 内存空间规整
  1. 指针碰撞法分配内存, 内存规整的空间,将已使用内存和未使用内存分开,用指针标记,指针碰撞法就是在分配内存空间之后将指针移动到新分配的内存空间处
  1. 内存空间不规整
  1. 虚拟机维护一个空闲列表,通过空闲列表寻找可以放下此对象的空间
  1. 处理堆内存并发问题
  1. TLAB
  2. CAS失败重试,加锁保证原子性
  1. 对象属性赋默认值
  2. 设置对象头信息
  3. 调用对象构造器,进行对象的显式初始化.

6.2.3 对象的内存布局

  1. 对象头
  1. 运行时元数据
  1. 哈希值
  2. GC分代年龄
  3. 锁状态标志
  4. 线程持有的锁
  5. 偏向线程ID
  6. 偏向时间戳
  1. 类型信息 指向该对象对应的Class对象,其实就是指向方法区中存储的类型信息
  2. 如果是数组对象,还要记录一下数组的长度
  1. 实例数据,存储对象的实际信息
  1. 存储对象的各个字段,包括父类的字段,并且父类的字段在前
  2. 如果CompareFields为true(默认true),子类的窄变量可能存储到父类变量的缝隙
  3. 相同宽度的字段总是被分配到一起
  1. 对齐填充,无实际意义

图解:


6.3 对象的定位

对象创建出来就是用来访问的

对象访问:

  1. 句柄访问
  1. 堆中维护了句柄池,句柄池中有对象实例数据地址和对象类型数据地址,栈中的引用通过句柄池中的可以访问对象实例数据和对象类型数据
  2. 好处: 当堆中的对象实例地址发生了变化时,不必修改栈中引用的地址,修改句柄池中的对象实例地址即可
  3. 坏处: 增加了访问环节
  1. 直接访问 (HotSpot使用)
  1. 如上图内存布局所示,通过栈帧中的引用直接访问对象实例地址,然后再通过对象中的类型指针访问方法区中的对象类型信息
  2. 好处: 直接访问,减少了中间环节,并且堆中不需要额外开辟句柄池
  3. 坏处: 当堆中的对象实例地址发生了变化时,栈帧中局部变量表的变量引用地址也要随之变化
目录
打赏
0
0
0
0
18
分享
相关文章
JVM实战—8.如何分析jstat统计来定位GC
本文详细介绍了使用jstat、jmap和jhat等工具分析JVM运行状况的方法,以及如何合理优化JVM性能。内容涵盖新生代与老年代对象增长速率、Young GC和Full GC的触发频率及耗时等关键指标的分析。通过模拟BI系统和计算系统的案例,展示了如何根据实际场景调整JVM参数以减少FGC频率,提升系统性能。最后汇总了常见问题及其解决方案,帮助开发者更好地理解和优化JVM运行状态。
JVM实战—8.如何分析jstat统计来定位GC
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
676 166
JVM实战—2.JVM内存设置与对象分配流转
本文详细介绍了JVM内存管理的相关知识,包括:JVM内存划分原理、对象分配与流转、线上系统JVM内存设置、JVM参数优化、问题汇总。
JVM实战—2.JVM内存设置与对象分配流转
JVM实战—12.OOM的定位和解决
本文详细探讨了JVM内存管理中的常见问题及其解决方案,包括如何监控和报警系统的OOM异常、在内存溢出时自动Dump内存快照、解决Metaspace区域内存溢出、栈内存溢出(StackOverflowError)以及堆内存溢出(OutOfMemoryError: Java heap space)。针对每种情况,文章提供了具体的解决思路、示例代码、GC日志分析及内存快照分析方法。通过搭建系统监控体系、调整JVM参数和使用工具如MAT,可以有效定位和解决各类内存问题,优化系统性能并避免崩溃风险。
JVM实战—12.OOM的定位和解决
|
4月前
|
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
84 1
散列表的数据结构以及对象在JVM堆中的存储过程
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
JVM 运行时数据区
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这 些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区 域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解 析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳 转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成; 为什么要线程计数器?因为线程是不具备记忆功能 Java 虚拟机
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
|
3月前
|
JVM运行时数据区
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一
46 2
JVM对象引用
本次课程聚焦JVM对象引用,涵盖强引用、软引用、弱引用和虚引用。强引用是最常见的引用类型,确保对象不会被垃圾回收器回收,适用于需要确保对象存活的场景;软引用在内存不足时会被优先回收,常用于缓存;弱引用的对象随时可能被回收,适合临时对象;虚引用最弱,主要用于接收对象回收通知,进行资源清理。通过合理选择引用类型,可优化内存管理,避免内存泄露。