虚拟机在java堆中对象分配、布局和访问的过程

简介: java虚拟机读书笔记 对象的分配、布局、访问

二、虚拟机在java堆中对象分配、布局和访问的过程
  • 一、 对象的创建
    • 1、从java程序,new指令开始,从虚拟机来看,则是判断类是否被加载;
    • 2、类加载通过后,有两个问题:
      • 一、内存分配,
        • 对象所需内存的大小在类加载完成后就可以完全确定,为对象分配空间的任务等于把一块确定大小的内存从Java堆中划分出来。
        • 两种方法:
          • 指针碰撞
          • 空闲列表
        • 分配方式的选择取决于:java堆是否规整;Java堆是否规整取决于:所采用的垃圾收集器是否带有压缩整理功能。
        • 例子:
          • 在使用Serial、ParNew等带有Compact过程的收集器时,系统采用分配算法为:指针碰撞;
          • 使用CMS,基于 Mark-Sweep算法的收集器时,系统采用分配算法为:空闲列表。
      • 二、对象创建的线程安全问题
        • 问题描述
          • 对象创建在虚拟机中的行为非常频繁,即使只是修改一个指针所指向的位置,在并发情况下也并不是线程安全的,有可能出现正在给对象A分配
          • 内存,指针还没有修改,对象B又同时使用了原来的指针来分配内存的情况。
        • 解决方法,有两种:
          • 1 对分配空间的动作进行同步处理;
            • 实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
            • CAS(比较与交换,Compare and swap)是一种有名的无锁算法。(ConcurrentHashMap也用到了此算法)
          • 2 本地线程分配缓冲
            • 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(Tlab)。
            • 虚拟机是否使用Tlab,可以通过-XX:+/-UseTlab参数来设定
    • 3、对象内存分配完成后,虚拟机把内存空间初始化
      • 虚拟机将分配到的内存空间都初始化为零值(不包括对象头),如果使用Tlab,初始化操作可以提前至Tlab分配时进行。
      • 目的:
        • 保证了对象的实例字段在java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的的零值。
    • 4、修饰对象
      • 虚拟机对对象进行必要的设置。其实是对象头。
      • 对象头中包括:对象的哈希码、对象的Gc分代年龄、此对象是哪个类的实例、类的元数据信息的指示等;
      • 根据虚拟机当前的运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
    • 5、对象产生
      • 从虚拟机的角度,一个新的对象已经产生。
      • 从java程序的角度,对象创建才刚刚开始,init方法还没有执行,所有的字段都为零。
        • 一般来说(由字节码中是否跟随invokespecial指令所决定),执行new 指令之后会接着执行方法,
        • 把对象按照程序员的意愿进行初始化,这样一个真正可用的对象产生。
  • 二、 对象的内存布局
    • 对象在内存中布局分配分为三个部分
      • 对象头
      • 实例数据
      • 对齐填充
    • 1 、对象头
      • 如图:
        746053c1-0024-4667-80f9-6cace6fa994e-5210562.jpg
      • 第一部分
        • 用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,
      • 第二部分
        • 类型指针,即对象指向它的类元素数据的指针,虚拟机通过该指针确定对象的实例是哪个类。
    • 2 、实例数据
      • 对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
      • 包括父类继承的,子类定义的;
      • 存儲順序: 虚拟机分配策略参数和字段在java源码中定义的顺序 ;
      • hotSpot虚拟机默认的分配策略为longs/doubles、ints、 shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),
      • 相同宽度的字段被分配到一起。在这个前提下,父类中定义的变量会出现在子类之前。
    • 3 、对齐填充
      • 不是必须存在的,只是占位符的作用。
      • 因为对象的大小必须是8字节的整数倍。对象头部分正好是8字节的倍数,当对象实例数据部分没有对齐时,通过对齐填充补全。
  • 三、 访问
    • 访问方式有2个,句柄和指针:
      • 句柄访问
        f502ca94-6fc3-4bb3-ac78-8f7c7c52d34c-5210562.jpg
        • 存储位置:java 堆中划分一块内存作为句柄池,reference中存储的就是对象的句柄地址;
        • 包含信息:对象实例数据与类型数据各自的具体地址信息。
      • 指针访问
        9a55bcb9-2a0b-4f82-8031-84036ee1b25f-5210562.jpg
        • reference 存储的直接就是对象地址。
        • 问题: 考虑java堆对象的布局中存放访问类型数据的相关信息。
    • 两种方法的优劣:
      • 句柄
        • reference中存储的是稳定的句柄地址,对象被移动时,只改变句柄中的实例数据指针,而reference本身不需要修改。(在垃圾收集时移动对象时普遍的)
      • 指针
        • 速度更快,节省了一次指针定位的时间开销。--由于对象的访问在java非常频繁。(积少成多)
    • Sun HotSpot 采用第二种方式

相关文章
|
6天前
|
存储 缓存 监控
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
18 0
|
6天前
|
存储 缓存 算法
Java面试题:给出代码优化的常见策略,如减少对象创建、使用缓存等。
Java面试题:给出代码优化的常见策略,如减少对象创建、使用缓存等。
8 0
|
6天前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
10 0
|
8天前
|
Java
Java的对象监视器
摘要: Java中的监视器(Monitor)是线程同步机制,每个对象带有一个与之关联的监视器。线程通过`synchronized`获取和释放锁。监视器包含入口集(等待锁的线程)、所有者线程(持锁线程)和等待集(调用`wait()`的线程)。线程在入口集阻塞,等待集调用`wait()`后释放锁进入等待。线程状态包括新建、可运行、阻塞、等待、超时等待和终止。示例代码展示了线程如何在不同状态间转换,如线程获取和释放监视器锁以及调用`wait()`和`notify()`方法。
|
6天前
|
Java 调度
Java线程的六种状态
Java线程有六种状态: 初始(NEW)、运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、超时等待(TIMED_WAITING)、终止(TERMINATED)。
18 1
|
6天前
|
存储 安全 Java
Java面试题:请解释Java内存模型(JMM)是什么,它如何保证线程安全?
Java面试题:请解释Java内存模型(JMM)是什么,它如何保证线程安全?
42 13
|
3天前
|
安全 Java 开发者
Java并发编程中的线程安全性与性能优化
在Java编程中,处理并发问题是至关重要的。本文探讨了Java中线程安全性的概念及其在性能优化中的重要性。通过深入分析多线程环境下的共享资源访问问题,结合常见的并发控制手段和性能优化技巧,帮助开发者更好地理解和应对Java程序中的并发挑战。 【7月更文挑战第14天】
|
3天前
|
监控 Java API
Java并发编程之线程池深度解析
【7月更文挑战第14天】在Java并发编程领域,线程池是提升性能、管理资源的关键工具。本文将深入探讨线程池的核心概念、内部工作原理以及如何有效使用线程池来处理并发任务,旨在为读者提供一套完整的线程池使用和优化策略。
|
6天前
|
缓存 安全 Java
Java中线程池如何管理?
【7月更文挑战第11天】Java中线程池如何管理?
15 2
|
6天前
|
安全 算法 Java
Java中线程安全怎么做?
【7月更文挑战第11天】Java中线程安全怎么做?
11 2