关注微信公众号:CodingTechWork,一起学习进步。

引言

  Java程序员都知道如何创建对象,不就是一个Person person = new Person()的语句就解决了么?然而,我们只知道new,却对于底层如何实现对象的创建、如何存储到内存中去、又如何被访问的知之甚少。

对象的创建

流程图

创建流程

  1. Java程序new一个对象。
  2. 虚拟机遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,且检查该符号引用代表的类是否已被加载、解析和初始化过。若没有,需先进行相应的类加载过程。
  3. 在类加载检查通过后,虚拟机将为新生对象分配内存。(对象在内存中所需要的大小在类加载完成后就确定了)
  4. 内存分配完之后,虚拟机需要将分配到的内存空间初始化为零值(不包括对象头)。保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,可以访问对应的零值。(对应准备阶段)
  5. 虚拟机对对象进行必要的设置(对象头的设置)。如这个对象是哪个类的实例、如何找到类的元数据信息、对象哈希码、对象的GC分代年龄等信息。
  6. 以上虚拟机中新对象产生,对应到Java程序还需要继续执行<init>方法,将对象在程序中进行初始化。

内存空间分配方式

  为对象分配空间就是从Java堆中划分出一块确定大小的内存给新生对象,考虑符合划分可用空间的两种方式:“指针碰撞”和“空闲列表”

  • 指针碰撞:若Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,所分配内存仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。在使用Serial、ParNew收集器时等带有Compact过程时,系统分配算法是指针碰撞。
  • 空闲列表:Java堆中内存不是规整的,已使用的内存和空闲的内存相互交错,VM需维护一个列表,记录上哪些内存是可用的,在分配时从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。使用CMS收集器时,就是采用的空闲里列表,CMS是基于Mark-Sweep算法(标记-清除)的收集器。

并发安全问题

  Java对象创建在程序中是非常常见的,所以在VM中对象创建是非常频繁,容易出现多线程并发安全问题:如程序中创建对象A和对象B,底层VM给A对象分配内存,指针没来及修改,对象B同时使用原来的指针分配内存。
  解决方案有两种:同步处理和本地线程分配缓冲

  • 同步处理:分配内存空间的动作进行同步处理(CAS操作),VM采用CAS配上失败重试的方式保证更新操作的原子性
  • 本地线程分配缓冲:Thread Local Allocation Buffer, TLAB,把内存分配的动作按照线程划分不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,即为TLAB,哪个线程要分配内存,就在哪个线程的TLAB上分配,只有用完后并分配新的TLAB,才需要同步锁定。通过-XX:+/-UseTLAB参数设定是否需要使用TLAB。

对象的内存布局

概述

  Java对象在内存存储的布局分为3块:对象头、实例数据和对齐填充

对象头

  对象头(Header)分为两部分:用于存储对象自身的运行时数据和类型指针

运行时数据

  Mark Word,用于存储对象自身的运行时数据包括:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。

存储内容 标志位 状态
对象哈希码、GC分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级的指针 10 膨胀(重量级锁定)
空,不需要记录信息 11 GC标志
偏向线程ID、偏向时间戳、对象分代年龄 01 可偏向

  Mark Word是一个非固定的数据结构,在极小的空间内存储尽量多的数据,会根据对象的状态复用自己的存储空间,如在32位HotSpot VM中,若对象处于未锁定状态,Mark Word的32bit空间中25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,即32(存储空间)=25(哈希码)+4(分代年龄)+2(锁标志位)+1(固定0)

类型指针

  即对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例,但是并非查找对象的元数据就一定要通过对象本身,也只是适用于普通对象,普通Java对象可以通过元数据信息可以确定Java对象的大小。不适用的Java对象,如Java数组对象的对象头中必须有一块能保持记录数组长度的数据,因为从数组元数据中无法确定数组的大小。

实例数据

  实例数据(Instance Data)是对象真正存储的有效信息,也是程序代码中定义的各种类型的字段内容。这部分存储顺序会受到VM分配策略参数字段在Java源码中定义顺序的影响。
VM默认分配策略
  HotSpot默认分配策略为longs/doubles、ints、shorts/chars、bytes/nooleans、oops,相同宽度的字段会被分配到一起,在父类中定义的变量会出现在子类之前。

对齐填充

  对齐填充(Padding)是非必要的,只是起着占位符的作用。VM自动内存管理系统要求对象起始地址(对象大小)必须是8字节的整数倍,对象头都是8字节的整数倍,而实例数据部分若没有8字节的整数倍,可以通过对齐填充进行补全。

对象的访问方式

概述

  Java程序通过栈上的reference数据类操作堆上的具体对象(栈中的局部变量表存储了对象名的变量,堆中存储了对象的具体地址)。主流的对象访问定位方式有两种:使用句柄和直接指针

使用句柄

  使用句柄访问对象,Java堆中会划分出一块内存作为句柄池reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。

直接指针

  使用直接指针访问,Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。(Sun HotSport VM的使用方式)

访问方式对比

  使用句柄访问优势是reference中存储的是稳定的句柄地址,对象被移动时,只会改变句柄中实例数据指针,reference本身不会变;
  使用直接指针访问优势是速度快,节省一次指针定位时间开销。(JVM默认使用)

参考
《深入理解Java虚拟机》

JVM——Java对象是如何创建、存储和访问的?相关推荐

  1. java对象是如何创建的

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

  2. JVM —— Java 对象占用空间大小计算

    零. 为什么要知道 Java 对象占用空间大小 缓存的实现: 在设计 JVM 内缓存时(不是借助 Memcached. Redis 等), 需要知道缓存的对象是否会超过 JVM 最大堆限制, 如果会超 ...

  3. JVM(java 虚拟机)

    JVM(java 虚拟机) 一.JVM简介 1.JVM:Java Virtual Machine (java 虚拟机) 通过软件来模拟出来的具有完整的硬件系统功能.运行在完全隔离的环境中的完整的计算机 ...

  4. gateway 内存溢出问题_带你学习jvm java虚拟机 arthas/性能调优/故障排除/gc回收/内存溢出等...

    学完本课程,您将掌握: 内存溢出问题实战 CPU飙升问题实战 阿里巴巴Arthas在线诊断 Class字节详细拆解 手写类加载器.四种类加载器.双亲委托模型 对象创建.存储.访问.加载解析 性能调优. ...

  5. 浅析Java中对象的创建与对象的数据类型转换

    这篇文章主要介绍了Java中对象的创建与对象的数据类型转换,是Java入门学习中的基础知识,需要的朋友可以参考下 Java:对象创建和初始化过程 1.Java中的数据类型     Java中有3个数据 ...

  6. java内存区_基于jvm java内存区域的介绍

    jvm虚拟机在运行时需要用到的内存区域.广泛一点就是堆和栈,其实不然,堆和栈只是相对比较笼统的说法,真正区分有如下几个 先上图一: 总的就是 java的内存模型 内存模型又分堆内存(heap)和方法区 ...

  7. Java设计模式之五大创建型模式

    Java设计模式之五大创建型模式 设计模式(23种) 单例模式(Singleton Pattern) 参考链接 概念 使用场景 实现思路 实现方式 饿汉式(静态常量) 饿汉式(静态代码块) 懒汉式(线 ...

  8. tomcat中request对象是被创建的_常用开源框架中设计模式使用分析(全)

    一.前言 说起来设计模式,大家应该都耳熟能详,设计模式代表了软件设计的最佳实践,是经过不断总结提炼出来的代码设计经验的分类总结,这些模式或者可以简化代码,或者可以是代码逻辑开起来清晰,或者对功能扩展很 ...

  9. executor线程池框架_如何使用Java 5 Executor框架创建线程池

    executor线程池框架 Java 5以Executor框架的形式在Java中引入了线程池,它允许Java程序员将任务提交与任务执行分离. 如果要使用Java进行服务器端编程,则线程池是维护系统可伸 ...

最新文章

  1. jstl标签的用法 fn标签
  2. 无法读取内存属于错误吗_深入了解 JavaScript 内存泄露
  3. 飞思卡尔烧写工具mfgtools的使用
  4. oracle供需平衡,OracleR12_MRP_功能介绍_V2.ppt
  5. Eclipse导入Tomcat源码
  6. 2017云栖大会·杭州峰会:《在线用户行为分析:基于流式计算的数据处理及应用》之《数据可视化:构建实时动态运营数据分析大屏》篇...
  7. 使用SpringWebFlux的反应式Web应用程序
  8. 【OpenCV 例程200篇】93. 噪声模型的直方图
  9. Spring 配置多个数据源,并实现动态切换
  10. nlp基础—5.SkipGram, CBOW, Glove, MF,Gaussian Embedding, 语言模型以及各类Smooting技术
  11. TCP/IP学习 1.2 IP包头(2)
  12. 未能加载文件或程序集“AjaxControlToolkit”或它的某一个依赖项
  13. 手机音频拼接软件_技能帖 | 专业又好上手的音频剪辑攻略
  14. sql服务器的响应时间,如何解决:[Sql Server]超时时间已到。在操作完成之前超时时间已过或服务器未响应。...
  15. 进程调度:时间片轮转调度算法
  16. DIY 一个 JSON解析器。
  17. 荣耀轻薄本MagicBook 14使用体验分享 性能拉满续航无敌
  18. 使用百度API实现热点(WIFI)、GPS、基站定位
  19. CSDN目录有什么用,怎么使用csdn的目录,csdn目录怎么生成?
  20. 【直播】美团点评技术沙龙Online003: 美团点评数据库运维自动化实践与发展

热门文章

  1. 【Pytorch】model.train()和model.eval()用法和区别,以及model.eval()和torch.no_grad()的区别
  2. 操作系统中的一些基本概念
  3. tomcat-servlet-AJAX最基础例子
  4. dw连接mysql数据库原理_Dreamweaver数据库路径是什么
  5. python 上传excel_简历批量合并Python+VBA小工具
  6. 代码注释(图案:女孩儿)
  7. 应用软件更新提醒单页HTML网站源码
  8. 轻量小巧的Knife4j v2.0.8源码
  9. 简单易用的开源ORM框架SqlSugar v5.0.0.19源码
  10. TimeJot – Last Time 改名,新增中文界面、数字属性,还是那个时间线管理神器[Android]