专栏首页猿人谷【JVM从小白学成大佬】6.创建对象及对象的访问定位

【JVM从小白学成大佬】6.创建对象及对象的访问定位

《JVM从小白学成大佬》系列推出到现在,收到了很多小伙伴的好评,也收到了一些小伙伴的建议,在此表示感谢。

有几个小伙伴提出了希望出一篇介绍对象的创建及访问,猿人谷向来是没有原则的,小伙们要求啥,咱就尽力满足,毕竟文章就是对自己学习的一个总结及和各位小伙伴交流学习的机会。话不多说,直接开撸!

1 创建对象

在Java程序运行过程中无时无刻都有对象被创建出来,java中对象可以采用new或反射或clone或反序列化的方法创建。接下来我们我们介绍在虚拟机中,对象(限于普通Java对象,不包括数组和Class对象等)的创建过程。

字节码new表示创建对象,虚拟机遇到该指令时,从栈顶取得目标对象在常量池中的索引,接着定位到目标对象的类型。接下来,虚拟机将根据该类的状态,采取相应的内存分配技术,在内存中分配实例空间,并完成实例数据和对象头的初始化。这样,一个对象就在JVM中创建好了。

实例的创建过程,首先根据从类常量池中获取对象类型信息并验证类是否已被解析过,若确保该类已被加载和正确解析,使用快速分配(fast allocation)技术为该类分配对象空间;若该类尚未解析过,则只能通过慢速分配(slow allocation)方式分配实例对象。实例的创建流程如下图所示。

对象创建的基本流程:

  1. 验证类已被解析。
  2. 获取instanceKlass,确保Klass已完全初始化。
  3. 若满足快速分配条件,则进入快速分配流程。
  4. 若不满足快速分配条件,或者快速分配失败,则进入慢速分配流程。

1.1 快速分配

如果在实例分配之前已经完成了类型的解析,那么分配操作仅仅是在内存空间中划分可用内存,因此能以较高效率实现内存分配,这就是快速分配

根据分配空间是来自于线程私有区域还是共享的堆空间,快速分配可以分为两种空间选择策略。HotSpot通过线程局部分配缓存技术(Thread-Local Allocation Buffers,即TLABs)可以在线程私有区域实现空间的分配。

可以通过VM选项UseTLAB来开启或关闭TLAB功能。

根据是否使用TLAB,快速分配方式有两种选择策略:

  • 选择TLAB:首先尝试在TLAB中分配,因为TLAB是线程私有区域,故不需要加锁便能够确保线程安全。在分配一个新的对象空间时,将首先尝试在TLAB空间中分配对象空间,若分配空间的请求失败,则再尝试使用加锁机制在Eden区分配对象。
  • 选择Eden空间:若失败,则尝试在共享的Eden区进行分配,Eden区是所有线程共享区域,需要保证线程安全,故采用原子操作进行分配。若分配失败,则再次尝试该操作,直到分配成功为止。

实例空间分配成功以后,将对实例进行初始化。待完成对象的空间分配和初始化后,就可以设置栈顶对象引用。当然,对象的空间分配和初始化操作都是基于从类常量池中获取对象类型并确保该类已被加载和正确解析的前提下进行的,如果类未被解析,则需要进行慢速分配。

1.2 慢速分配

之所以成为慢速分配,正是因为在分配实例前需要对类进行解析,确保类及依赖类已得到正确的解析和初始化。慢速分配是调用InterpreterRuntime模块_new()进行的,实现代码如下。

// 确保要初始化的类不是抽象类型
klass->check_valid_for_instantiation(true, CHECK);
// 确保类已初始化
klass->initialize(CHECK);
// 分配实例
oop obj = klass->allocate_instance(CHECK);
// 在线程栈中设置对象引用
thread->set_vm_result(obj);

2 对象的访问定位

建立对象是为了使用对象,Java程序需要通过栈上的reference数据来操作上的具体对象。由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。

目前主流的访问方式有使用句柄直接指针两种:

  • 如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息,如下图所示。
  • 如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。即使用直接指针访问在对象被移动时reference本身需要被修改,reference存储的就是对象地址。如下图所示。

这两种对象访问方式各有优势:

  • 使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象时非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
  • 使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。

HotSpot就是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java的内存回收机制

     在Java中,它的内存管理包括两方面:内存分配(创建Java对象的时候)和内存回收,这两方面工作都是由JVM自动完成的,降低了Java程序员的学习难度,避免了...

    猿人谷
  • 【JVM从小白学成大佬】4.Java虚拟机何谓垃圾及垃圾回收算法

    在Java中内存是由虚拟机自动管理的,虚拟机在内存中划出一片区域,作为满足程序内存分配请求的空间。内存的创建仍然是由程序猿来显示指定的,但是对象的释放却对程序猿...

    猿人谷
  • 在windows下使用eclipes连接linux下的Hadoop集群

    hadoop 是工作在linux下的分布式系统,做为一个开发者,对于手里资源有限,不得不使用只有终端的虚拟机来运行hadoop集群。但是,在这种环境下,开发,调...

    猿人谷
  • JAVA 反射

    动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的JavaScript就是动态语言,除此之外Ruby,P...

    cherishspring
  • Objective-C的内省(Introspection)

    内省(Introspection)是面向对象语言和环境的一个强大特性,Objective-C和Cocoa在这个方面尤其的丰富。内省是对象揭示自...

    roc
  • Objective-C的内省(Introspection)

    内省(Introspection)是面向对象语言和环境的一个强大特性,Objective-C和Cocoa在这个方面尤其的丰富。内省是对象揭示自...

    roc
  • how to get bsp view page by generated class name

    版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons)

    Jerry Wang
  • 通用的进程监控脚本process_monitor.sh使用方法

    通过需要将process_monitor.sh放到crontab上,以便系统重启自动生效,也可避免process_monitor.sh被意外终止导致失效,cro...

    一见
  • __main__.MyClass instance at XXX的含义

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    于小勇
  • FunDA(4)- 数据流内容控制:Stream data element control

        上节我们探讨了通过scalaz-stream-fs2来驱动一套数据处理流程,用fs2的Pipe类型来实现对数据流的逐行操作。本篇讨论准备在上节讨论的基础...

    用户1150956

扫码关注云+社区

领取腾讯云代金券