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

目录
打赏
0
0
0
0
40
分享
相关文章
Java 类和对象
本文介绍了Java编程中类和对象的基础知识,作为面向对象编程(OOP)的核心概念。类是对象的蓝图,定义实体类型;对象是具体实例,包含状态和行为。通过示例展示了如何创建表示汽车的类及其实例,并说明了构造函数、字段和方法的作用。同时,文章还探讨了访问修饰符的使用,强调封装的重要性,如通过getter和setter控制字段访问。最后总结了类与对象的关系及其在Java中的应用,并建议进一步学习继承等概念。
重学Java基础篇—Java对象创建的7种核心方式详解
本文全面解析了Java中对象的创建方式,涵盖基础到高级技术。包括`new关键字`直接实例化、反射机制动态创建、克隆与反序列化复用对象,以及工厂方法和建造者模式等设计模式的应用。同时探讨了Spring IOC容器等框架级创建方式,并对比各类方法的适用场景与优缺点。此外,还深入分析了动态代理、Unsafe类等扩展知识及注意事项。最后总结最佳实践,建议根据业务需求选择合适方式,在灵活性与性能间取得平衡。
85 3
|
13天前
|
理解Java引用数据类型:它们都是对象引用
本文深入探讨了Java中引用数据类型的本质及其相关特性。引用变量存储的是对象的内存地址而非对象本身,类似房子的地址而非房子本身。文章通过实例解析了引用赋值、比较(`==`与`equals()`的区别)以及包装类缓存机制等核心概念。此外,还介绍了Java引用类型的家族,包括类、接口、数组和枚举。理解这些内容有助于开发者避免常见错误,提升对Java内存模型的掌握,为高效编程奠定基础。
44 0
|
13天前
|
java中一个接口A,以及一个实现它的类B,一个A类型的引用对象作为一个方法的参数,这个参数的类型可以是B的类型吗?
本文探讨了面向对象编程中接口与实现类的关系,以及里氏替换原则(LSP)的应用。通过示例代码展示了如何利用多态性将实现类的对象传递给接口类型的参数,满足LSP的要求。LSP确保子类能无缝替换父类或接口,不改变程序行为。接口定义了行为规范,实现类遵循此规范,从而保证了多态性和代码的可维护性。总结来说,接口与实现类的关系天然符合LSP,体现了多态性的核心思想。
24 0
Java对象创建和访问
Java对象创建过程包括类加载检查、内存分配(指针碰撞或空闲列表)、内存初始化、对象头设置及初始化方法执行。访问方式有句柄和直接指针两种,前者稳定但需额外定位,后者速度快。对象创建涉及并发安全、垃圾回收等机制。
Java对象创建和访问
|
2月前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
201 60
【Java并发】【线程池】带你从0-1入门线程池
|
15天前
|
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。
51 4
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
88 23