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分代年龄等信息,这些信息就存放在对象头中。对象头就是我们下节所要讨论的内容。

相关文章
|
9月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
614 6
|
11月前
|
存储 安全 Java
java: 无法访问org.springframework.ldap.core.LdapTemplate
java: 无法访问org.springframework.ldap.core.LdapTemplate
276 9
|
缓存 Java 数据库
Java 访问修饰符使用方法与组件封装方法详细说明
本文详细介绍了Java中访问修饰符(`public`、`private`、`protected`、默认)的使用方法,并结合代码示例讲解了组件封装的核心思想与实现技巧。内容涵盖数据封装、继承扩展、模块化设计与接口隔离等关键技术点,帮助开发者提升代码的可维护性与安全性,适用于Java初学者及进阶开发者学习参考。
260 1
|
监控 测试技术 数据库
详解Hyper-V虚拟机CPU分配方法
在Hyper-V环境中,合理分配虚拟机的CPU资源至关重要。vCPU是物理CPU的虚拟化表示,管理员可通过指定处理器数量、核心数、设置兼容性和亲和性、启用动态分配等方法优化性能。使用性能监视工具监控并调整CPU资源,避免过度分配,确保虚拟机稳定运行。定期评估和优化资源分配策略,以适应业务变化,保持最佳性能。
|
NoSQL JavaScript 前端开发
Java访问MongoDB
Java访问MongoDB
265 21
|
存储 算法 安全
Java对象创建和访问
Java对象创建过程包括类加载检查、内存分配(指针碰撞或空闲列表)、内存初始化、对象头设置及初始化方法执行。访问方式有句柄和直接指针两种,前者稳定但需额外定位,后者速度快。对象创建涉及并发安全、垃圾回收等机制。
191 0
Java对象创建和访问
|
SQL Java 数据库连接
【潜意识Java】深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
3102 1
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
399 3
|
SQL Java 数据库连接
打破瓶颈:利用Java连接池技术提升数据库访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,避免了频繁的连接建立和断开,显著提升了数据库访问效率。常见的连接池库包括HikariCP、C3P0和DBCP,它们提供了丰富的配置选项和强大的功能,帮助优化应用性能。
390 2

热门文章

最新文章