Java对象创建、分配、布局、访问小析(HotSpot虚拟机)(一)

简介: 本文内容总结自周志明先生所编著的《深入理解Java虚拟机-JVM高级特性与最佳实践》此书的经典不必多说。本节内容是对象的创建.、分配的内容。 对象的创建 java对象的创建有几种方式呢(这里所说的java对象仅限于普通java对象不包含数据和Class对象)?大致有以下四种方式: new关键字。这应该是我们最常见和最常用最简单的创建对象的方式。使用newInstance方法

本文内容总结自周志明先生所编著的《深入理解Java虚拟机-JVM高级特性与最佳实践》此书的经典不必多说。本节内容是对象的创建.、分配的内容。

对象的创建

java对象的创建有几种方式呢(这里所说的java对象仅限于普通java对象不包含数据和Class对象)?大致有以下四种方式:
  1. new关键字。这应该是我们最常见和最常用最简单的创建对象的方式。
  2. 使用newInstance方法。这里包括Class的newInstance方法和Constructor的newInstance方法(Class的newInstance方法最终调用的也是Constructor的newInstance方法)。
  3. 使用clone方法。要使用clone方法我们必须实现实现Cloneable接口,用clone方法创建对象并不会调用任何构造函数。即我们所说的浅copy
  4. 反序列化。要实现反序列化我们需要让我们的类实现Serializable接口。当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象,在反序列化时,JVM创建对象并不会调用任何构造函数。即我们所说的深Copy
上面的四种创建对象的方法除了第一种使用new指令之外,其他三种都是使用invokespecial(构造函数的直接调用)。这里我们只说new创建对象的方式,关于invokespecial心急的同学可以看一下 http://wensiqun.iteye.com/blog/1125503。下面我们来看看当虚拟机遇到new指令的时候发生了什么事。
java虚拟机规范中规定了几种类初始化的几种条件,其中就有遇到new指令的时候(在虚拟机的生命周期中,一个类只会在一个类加载器初始化一次)。所以当虚拟机遇到一条new指令的时候首先会检查这个类有没有被初始化过,如果没有则首先进行类的初始化的操作。这个检查是怎么进行的呢?虚拟机会去检查这个指令的参数是否能在常量池中定位到一个相应的符号引用(关于符号引用的内容可以看一下R大的回答 https://www.zhihu.com/question/30300585?sort=created ) ,然后检查这个符号引用代表的类是否已被加载、解析、和初始化过。

分配内存

当上面的检查通过之后,虚拟机就会为新生对象分配内存了。这里需要注意的是: 对象所需内存的大小在类加载完成后便可以完全确定了。既然对象所需的内存大小可以确定了,那为对象分配内存空间就相当于从java堆中找一份相应大小的内存空间了。由于不同的虚拟机所采用的垃圾收集算法不同,或者相同的虚拟机根据不同的配置所采用不同的垃圾收集算法,所以会导致jvm中的内存可能是规整(有一块连续的内存空间)或者不规整(内存一个碎块一个碎块的)(使用Serial、ParNew等带Compact过程的收集器时,java内存是相对规整的,使用CMS这种基于Mark-Sweep算法的收集器时,java内存是相对不规整的。这一部分的内容参考垃圾收集器)。对于内存规整的,对象内存分配方式为“指针碰撞”,对于不规整的内存,对象内存分配方式为空闲列表方法。

指针碰撞

由于java堆中的内存是绝对规整(具体的参看标记压缩的垃圾回收机制)的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配的内存就仅仅是把那个指针指向空闲空间那边,然后挪动一段与对象大小相等的距离。

空闲列表

由于java堆中的内存是不规整的(具体的参看标记清除的垃圾回收机制)的,正在使用的内存和空闲的内存是交织在一起的,这个时候虚拟机会维护一个列表,在这个列表中会记录那些内存块是可以使用的,所在在分配内存的时候,只需要从列表中找到一块足够大的空间划分给对象就行了。
上面说的是两种为对象分配内存的方式,你以为有这两种内存分配方式就行了吗?图样图森破。只要做过项目的人都知道,对象的创建时多么频繁的一件事,所以这么频繁的创建对象就会产生线程安全的问题。有可能我现在正在给A对象分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来进行内存分配。所以这个时候就需要考虑线程安全的问题了。jvm解决这个问题有两种方式,一种方式是使用CAS算法,另一种是使用 TLAB(Thread Local Allocation Buffer 本地线程分配缓冲)。即,把内存的分配动作按照线程划分在不同的空间之中进行,也就是每个线程在Java堆中预先分配一小块内存。哪个线程需要分配内存,就在哪个线程的TLAB上分配。

在内存分配完成之后,需要做的一件事是为属性赋初始值。然后虚拟机会对对象进行一些必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息就存放在对象头中。对象头就是我们下节所要讨论的内容。

相关文章
|
5天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
9天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
33 17
|
8天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
8天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
7 0
|
Ubuntu Java Unix
《HotSpot实战》—— 1.2 动手编译虚拟机
由于开发环境各不相同,每个人遇到的问题可能都不尽相同;即使遇到相同的问题,在不同的平台上解决的方式可能也有所不同。当然,对于相同的问题,也会有多种办法解决。限于篇幅,在这里不能对所有错误信息和解决办法都列举出来。
5245 0
|
18天前
|
安全 虚拟化 数据中心
Xshell 连接 VMware虚拟机操作 截图和使用
Xshell 连接 VMware虚拟机操作 截图和使用
39 4
|
26天前
|
Linux 虚拟化
vmware虚拟机安装2024(超详细)
vmware虚拟机安装2024(超详细)
199 6
|
5月前
|
Unix Linux 虚拟化
虚拟机VMware知识积累
虚拟机VMware知识积累
|
1月前
|
虚拟化 网络虚拟化 网络架构
虚拟机 VMware Workstation 16 PRO 的网络配置
虚拟机 VMware Workstation 16 PRO 的网络配置
|
2月前
|
存储 SQL 数据挖掘
虚拟化数据恢复—VMware虚拟机vmdk文件被误删除的数据恢复案例
虚拟化数据恢复环境: 某品牌服务器(部署VMware EXSI虚拟机)+同品牌存储(存放虚拟机文件)。 虚拟化故障: 意外断电导致服务器上某台虚拟机无法正常启动。查看虚拟机配置文件发现这台故障虚拟机除了磁盘文件以外其他配置文件全部丢失,xxx-flat.vmdk磁盘文件和xxx-000001-delta.vmdk快照文件还在。管理员联系VMware工程师寻求帮助。VMware工程师尝试新建一个虚拟机来解决故障,但发现ESXi存储空间不足。于是将故障虚拟机下的xxx-flat.vmdk磁盘文件删除,然后重建一个虚拟机并且分配固定大小的虚拟磁盘。