详解jvm对象的创建和分配

对象的创建

创建方式

1、 new 关键字直接创建。 new ObjectName()。

2、通过 Class 反射对象的 newInstance() 方法。ObjectName  obj  =  ObjectName.class.newInstance()。

3、通过 Class 反射对象获取 Constructor 类,再调用其 newInstance() 方法。 ObjectName obj = ObjectName.class.getConstructor.newInstance()。

4、在类实现 Cloneable 接口的前提下,使用对象的 clone() 方法。ObjectName obj = obj.clone()。(如果内部有自定义类属性,并且想要实现深克隆(新创建的对象和原有的对象不是同一个),那么就需要让该属性类也实现 Cloneable 接口。

5、使用反序列化。(为了避免属性丢失,需要让类实现 Serializable 接口)

public static void main(String[] args){
  try {
   ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FilePath))
   ObjectName obj = ois.readObject();
  } catch (IOException e) {
   e.printStackTrace();
  } catch (ClassNotFoundException e) {
   e.printStackTrace();
  }
 }

对象的内存布局

在对象身上,存储了关于这个对象的所有信息。

创建过程

1、根据创建对象的信息去内存中存放类信息的常量池中寻找是否存在要加载的类信息,如果存在直接创建对象;如果不存在就先进行该类的加载。

2、为对象分配空间。这里涉及到线程位置分配的安全和效率,比较复杂,会在下面详细来说。

3、初始化分配到对应的位置。

4、设置对象的对象头。

5、执行 init 方法(执行非静态代理块和实例属性的初始化以及执行实例构造方法)

对象的内存分配

分配方式

1、指针碰撞:如果 Java 堆内存是规整的,也就是对象的创建位置都是紧挨着的,这样的话直接将指针指示器向空闲方向移动要创建对象大小的距离就可以了。

2、空闲列表:如果 Java 堆内存是不规整的,那么就需要维护一个空闲列表来记录哪些位置是空闲的以及多大。在分配时就在列表上查询,找到合适的位置分配。

并发安全

由于在堆的线程共享的,所以对象的创建分配的空间可能同时也是另外一个线程对象创建的分配位置,这就导致了并发问题,所以为了保证对象创建的并发安全,可以有下面两种方式:

1、在分配空间时进行同步处理(采用 CAS +回旋锁的方式来保证)

2、TLAB:新的线程创建时会在堆中划分一块区域给该线程,后面该线程创建的对象都会在该位置存放,当空间不足时才使用第一种方式。(HotSpot 使用)。

代码优化

1、栈上分配。通过逃逸分析判断创建的对象是否逃逸出方法(也就是这个对象是否在当前方法的外部被调用),如果没有逃逸出方法,那么就有可能直接在栈上分类空间来保存。

2、同步省略。JIT 在编译时会判断同步块所使用的锁对象是否只能被一个线程访问而没有被发布到其他的线程。如果没有,那么 JIT 编译器在编译这段代码时就会取消这段代码的同步。

3、分离对象(标量替换)。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在栈中。

标量:无法再被分解的数据。如一个类的基本数据类型属性。

聚合量:还可以被分解的数据。如一个类的自定义属性。

逃逸分析的不成熟性

关于逃逸分析目前还是处于不稳定的阶段,因为无法保证逃逸分析的性能消耗一定高于其节省的性能。简单来说就是可能执行了逃逸分析,结果发现都是逃逸出方法的对象,这样逃逸分析并没有提高性能,同时执行逃逸分析也消耗了一定的性能,造成得不偿失。所以,逃逸分析在 JVM 中没有实现 栈上分配的功能的,但是其还是在 JIT 中起到了优化作用。所以可以说对象都是创建在堆上的。而我们一般所说的对象创建在栈上,实际情况是因为标量替换的作用。

实际的对象空间分配过程

首先会判断是否可以进行标量替换,如果可以直接使用标量替换,然后结束。不可以的话再尝试在当前线程划分的区域创建,如果区域不够再尝试使用 CAS+ 自旋锁在其他位置划分,失败就再次尝试,直到成功。

对象的访问

Java 程序通过栈上的引用访问堆中的对象。对象的访问方式取决于 JVM 虚拟机上的实现,目前主流的访问方式是句柄和直接指针。

句柄

句柄相当于一个中间表,存储着对应实例对象的地址以及实例数据所对应类信息的地址。

优势:比较稳定,当对象被移动后(垃圾回收时移动对象是非常常见的事)时只需要改变句柄中的指针就可以了。句柄本身不需要改变。

直接指针

引用直接指向实例对象,在对象上保存对应的类信息所在的地址。

优势:查找快,在栈上的引用可以很快找到对应的对象。这也是 HotSpot 默认的访问方式。

以上就是详解jvm对象的创建和分配的详细内容,更多关于jvm对象的创建和分配的资料请关注我们其它相关文章!

(0)

相关推荐

  • JVM对象创建和内存分配原理解析

    创建对象 当 JVM 收到一个 new 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被加载过了,如果没有的话则要进行一次类加载. 接着就是分配内存了,通常有两种方式: 指针碰撞 空闲列表 使用指针碰撞的前提是堆内存是完全工整的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可. 当堆中已经使用的内存和未使用的内存互相交错时,指针碰撞的方式就行不通了,这时就需要采用空闲列表的方式.虚拟机会维护一个空闲的列表,用于记

  • Java对象在JVM中的生命周期详解

    概念 在Java中,对象的生命周期包括以下几个阶段: 创建阶段(Created) 应用阶段(In Use) 不可见阶段(Invisible) 不可达阶段(Unreachable) 收集阶段(Collected) 终结阶段(Finalized) 对象空间重分配阶段(De-allocated) Java对象在JVM中的生命周期 当你通过new语句创建一个java对象时,JVM就会为这个对象分配一块内存空间,只要这个对象被引用变量引用了,那么这个对象就会一直驻留在内存中,否则,它就会结束生命周期,JV

  • 解析Java的JVM以及类与对象的概念

    Java虚拟机(JVM)以及跨平台原理 相信大家已经了解到Java具有跨平台的特性,可以"一次编译,到处运行",在Windows下编写的程序,无需任何修改就可以在Linux下运行,这是C和C++很难做到的. 那么,跨平台是怎样实现的呢?这就要谈及Java虚拟机(Java Virtual Machine,简称 JVM). JVM也是一个软件,不同的平台有不同的版本.我们编写的Java源码,编译后会生成一种 .class 文件,称为字节码文件.Java虚拟机就是负责将字节码文件翻译成特定平

  • 详解JVM的内存对象介绍[创建和访问]

    作为java程序员对应Object应该是非常熟悉的,但是对于对象在JVM中的一些情况并不是很清楚,所以本文就来记录下对象在JVM中的一些内容 对象的创建 java程序中创建对象的常用方式是: Object obj = new Object(); 该行代码的执行过程如下: 从图中我们可以发现对象创建的步骤如下 执行new执行 检查这个指令参数是否能够在常量池中定位到一个类的符号引用,并且检查这个符号引用所代表的类是否已经被加载,解析和初始化. 如果该类没有被加载则先执行类的加载操作 如果该类已经被

  • 深入理解JVM之Java对象的创建、内存布局、访问定位详解

    本文实例讲述了深入理解JVM之Java对象的创建.内存布局.访问定位.分享给大家供大家参考,具体如下: 对象的创建 一个简单的创建对象语句Clazz instance = new Clazz();包含的主要过程包括了类加载检查.对象分配内存.并发处理.内存空间初始化.对象设置.执行ini方法等. 主要流程如下: 1. 类加载检查 JVM遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化过.如果没有,那必须先执

  • JVM创建对象及访问定位过程详解

    1.对象的创建 虚拟机接收到new指令时,检查这个指令能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化.如果都没有,先执行类加载过程. 在类加载通过后,虚拟机为新对象分配内存(把一块确定大小的内存从Java堆中划分出来),内存大小在类加载完成后即可完全确定. 两种分配方式: (1):指针碰撞:假设Java堆中内存是绝对规整的,即使用过的内存在一边,空闲的内存在另外一边,中间放着一个指针作为指示器,通过移动指针实现内存分配. (2):空闲列表:如果Jav

  • 详解jvm对象的创建和分配

    对象的创建 创建方式 1. new 关键字直接创建. new ObjectName(). 2.通过 Class 反射对象的 newInstance() 方法.ObjectName  obj  =  ObjectName.class.newInstance(). 3.通过 Class 反射对象获取 Constructor 类,再调用其 newInstance() 方法. ObjectName obj = ObjectName.class.getConstructor.newInstance().

  • 详解JVM系列之对象的锁状态和同步

    java对象头 Java的锁状态其实可以分为三种,分别是偏向锁,轻量级锁和重量级锁. 在Java HotSpot VM中,每个对象前面都有一个class指针和一个Mark Word. Mark Word存储了哈希值以及分代年龄和标记位等,通过这些值的变化,JVM可以实现对java对象的不同程度的锁定. 还记得我们之前分享java对象的那张图吗? javaObject对象的对象头大小根据你使用的是32位还是64位的虚拟机的不同,稍有变化.这里我们使用的是64位的虚拟机为例. Object的对象头,

  • 详解Java对象创建的过程及内存布局

    一.对象的内存布局 对象头 对象头主要保存对象自身的运行时数据和用于指定该对象属于哪个类的类型指针. 实例数据 保存对象的有效数据,例如对象的字段信息,其中包括从父类继承下来的. 对齐填充 对齐填充不是必须存在的,没有特别的含义,只起到一个占位符的作用. 二.对象的创建过程 实例化一个类的对象的过程是一个典型的递归过程. 在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类. 此时,首先实例化Object类

  • 详解 javascript对象创建模式

    创建模式 在javascript中,主要有以下几种创建模式: 工厂模式 构造函数模式 原型模式 组合模式 动态原型模式 寄生构造函数模式 稳妥构造模式 工厂模式 工厂模式是软件工程领域一种广为人知的设计模式.javascript实现方式: function createPerson(name, obj, job) { var o = new Object(); o.name = name; o.obj = obj; o.job = job; o.sayName = function() { al

  • 详解jvm中的标量替换

    概述 通常在java中创建一个对象,大家都认为是在堆中创建. 在jdk6开始有逃逸分析,标量替换等技术,关于在堆中创建对象不再绝对. 关于标量替换,通过以下几点进行概述: 逃逸分析 标量替换是什么 测试标量替换 逃逸分析 逃逸分析是一种分析技术,分析对象的动态作用域,供其他优化措施提供依据.比如分析一个对象不会逃逸到方法之外或线程之外,其它优化措施(栈上分配,标量替换等)根据逃逸程度进行优化. 逃逸分析示例 public class EscapeAnalysis { public Person

  • 详解JVM的分代模型

    前言 上篇文章我们一起对jvm的内存模型有了比较清晰的认识,小伙伴们可以参考JVM内存模型不再是秘密这篇文章做一个复习. 本篇文章我们将针对jvm堆内存的分代模型做一个详细的解析,和大家一起轻松理解jvm的分代模型. 相信看过其他文章的小伙伴们可能都知道,jvm的分代模型包括:年轻代.老年代.永久代. 那么它们分别代表着什么角色呢?我们先来看一段代码 public class Main { public static void main(String[] args) { while (true)

  • 详解Java对象的内存布局

    前言 今天来讲些抽象的东西 -- 对象头,因为我在学习的过程中发现很多地方都关联到了对象头的知识点,例如JDK中的 synchronized锁优化 和 JVM 中对象年龄升级等等.要深入理解这些知识的原理,了解对象头的概念很有必要,而且可以为后面分享 synchronized 原理和 JVM 知识的时候做准备. 对象内存构成 Java 中通过 new 关键字创建一个类的实例对象,对象存于内存的堆中并给其分配一个内存地址,那么是否想过如下这些问题: 这个实例对象是以怎样的形态存在内存中的? 一个O

  • js对象实例详解(JavaScript对象深度剖析,深度理解js对象)

    这算是酝酿很久的一篇文章了. JavaScript作为一个基于对象(没有类的概念)的语言,从入门到精通到放弃一直会被对象这个问题围绕. 平时发的文章基本都是开发中遇到的问题和对最佳解决方案的探讨,终于忍不住要写一篇基础概念类的文章了. 本文探讨以下问题,在座的朋友各取所需,欢迎批评指正: 1.创建对象 2.__proto__与prototype 3.继承与原型链 4.对象的深度克隆 5.一些Object的方法与需要注意的点 6.ES6新增特性 下面反复提到实例对象和原型对象,通过构造函数 new

  • 详解TS对象扩展运算符和rest运算符

    概述 TypeScript 2.1 增加了对 对象扩展运算和 rest 属性提案的支持,该提案在 ES2018 中标准化.可以以类型安全的方式使用 rest 和 spread 属性. 对象 rest 属性 假设已经定义了一个具有三个属性的简单字面量对象 const marius = { name: "Marius Schulz", website: "https://mariusschulz.com/", twitterHandle: "@mariussc

  • 详解JVM之运行时常量池

    class文件中的常量池 之前我们在讲class文件的结构时,提到了每个class文件都有一个常量池,常量池中存了些什么东西呢? 字符串常量,类和接口名字,字段名,和其他一些在class中引用的常量. 运行时常量池 但是只有class文件中的常量池肯定是不够的,因为我们需要在JVM中运行起来. 这时候就需要一个运行时常量池,为JVM的运行服务. 运行时常量池和class文件的常量池是一一对应的,它就是class文件的常量池来构建的. 运行时常量池中有两种类型,分别是symbolic refere

随机推荐