六,JVM之对象的实例化,直接内存和执行引擎

JVM之对象的实例化,直接内存和执行引擎

一,对像的实例化内存布局与访问定位

一,对象的实例化:

对象的创建方式:
  1. new :最常见的方式:变形:(1)Xxx的静态方法(2)XxxBuilder/XxxFactory的静态方法
  2. Class的newInstance():反射方式,只能调用空参的构造器,权限必须是public
  3. Constructor的newInstance(xxx):反射方式,可以调用空参,带参的构造器,权限没有要求
  4. 使用clone:不调用任何构造器,当前类需要实现Clonable接口,实现clone()
  5. 使用反序列化:从文件中,网络中获取一个对象的二进制流
  6. 第三方库Objenesis
对象创建步骤:
  1. 判断对象对应的类是否加载,链接,初始化(加载类元信息)

    (虚拟机遇到一条new指令,首先检查这个指令的参数能否在Meta space的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化(即判断类元信息是否存在),如果没有,那么在双亲委派机制模式下,使用当前类加载器以ClassLoader+包名+类名为Key进行查找对应的.class文件。如果没有找到文件,则抛出ClassNotFoundEzception异常,如果找到则进行类加载,并生成对应得Class类对象。)

  2. 为对象分配内存:

    首先计算对象占用空间大小,接着在堆空间中划分一块内存给新对象,如果实例成员是引用变量,仅分配引用变量空间即可,4个字节

    1. 如果内存规整–指针碰撞(所有用过的内存在一边,空闲的内存在另一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针向空闲那边挪动一段与队像大小相等的距离罢了。如果垃圾收集器选择是Serial,ParNew这种基于压缩算法的,虚拟机采用这种分配方式一般使用带有compat(整理)过程的收集器,使用指针碰撞)
    2. 内存不规整–虚拟机需要维护一个列表,空闲列表分配,(内存不是规整的,已使用的内存和未使用的内存相互交错,虚拟机会维护一个列表,记录哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容,这种分配方式就是“空闲分配列表”)
  3. 处理并发安全问题:

    采用CAS配上失败重试保证更新的原子性,每个线程预先分配一块TLAB

  4. 初始化分配到的空间:

    所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用

  5. 设置对象的对象头:

    将对象的所属类(即类的元数据信息),对象的HashCode和对象的GC信息,锁信息等数据存储在对象的对象头中,这个 过程的具体设置方式取决于JVM实现

  6. 执行init方法进行初始化:

    初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量,这步才是真正按照程序员的意愿进行。

    对象属性赋值操作:

    属性的默认初始化;显示初始化/代码块中初始化;构造器中初始化

二,对象的内存布局:

在这里插入图片描述
1.对象头:
包含两部分:
运行时元数据(Mark Word):哈希值,GC分代年龄;锁状态标志;线程持有的锁;偏向线程ID;偏向时间戳

类型指针:指向类元数据instanceClass,确定该对象所属的类型

如果时数组,还需记录数组的长度

2.实例数据(InstanceData)

它是对象真正存储的有效信息,包括程序代码中每个定义的各种类型的字段(包括父类型和本身)

规则:相同宽度的字段总是分配在一起;父类中定义的变量会出现在子类之前;如果CompactFields参数为true(默认为true):子类的变量可能插入到父类变量的空隙

3.对齐填充

不是必须,也没特别含义,仅仅起占位符的作用
在这里插入图片描述

三,对象的访问定位:

两种方式:
在这里插入图片描述
在这里插入图片描述

JVM是如何通过栈帧中的对象引用访问到其他内部的对象实例的呢?

定位。通过栈上的reference访问

对象访问的两种方式的优缺点比较:

(1)句柄访问:对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中的实例数据指针即可,reference本身不需要被改变

(2)直接对象访问:访问速度块,而且不需要开辟一块空间来存储句柄池,Hotspot采用这种方式

二,直接内存

  • 直接内存是Java堆外的,直接向系统申请的内存空间,不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域
  • 来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存
  • 通常,访问直接内存的速度会优于Java堆,即读写性能高,因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存;Java的NIO库允许Java程序使用直接内存,用于数据缓冲区
  • 直接内存分配回收成本较高。不受JVM内存回收管理
  • 直接内存大小可以通过MaxDirectMemorySize设置,如果不指定,默认与堆的最大值-Xmx参数值一致。
  • 也会出现OOM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v5JutWc8-1615982756759)(D:\markDown\images\n5.png)]

三,执行引擎

执行引擎是Java 虚拟机核心组成部分之一,虚拟机的执行引擎是由软件自行实现的,可以不受物理条件制约地定制指令集与执行引擎的结构体系,能够执行那些不被硬件直接支持的指令集格式。

执行引擎的任务:将字节码指令解释/编译为对应平台上的本地机器指令才可以

执行引擎的工作过程:
在这里插入图片描述

Java代码编译和执行的过程:

在这里插入图片描述

解释器:Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行。

JIT编译器:虚拟机将源代码直接编译成本地机器平台相关的机器语言
在这里插入图片描述

HotSpot JVM的执行方式:

当虚拟机启动的时候,解释器可以首先发挥作用,而不必等代即时编译器全部编写完成后再执行,可以省去很多不必要的编译时间,并且随着程序运行时间的推移,即时编译器逐渐发挥作用,根据热点探测功能,将有价值的字节码编译为本地机器指令,以换取更高的程序执行效率

热点代码及探测方式:

一个被多次调用的方法,或者一个方法体内部循环次数较多的循环体都可以被称之为“热点代码”

目前HotSpot VM采用的热点探测方式是基于计数器的热点探测

采用基于计数器的热点探测,会为每一个方法建立两个不同类型的计数器,分别为方法调用计数器(统计方法的调用次数)和回边计数器(统计循环体执行的循环次数)

当超过这个阈值,就会触发JIT编译器,阈值可以通过虚拟机参数 -XX:CompileThreshold来设定,默认阈值在Client模式是1500次,Server模式下是1000次

方法调用计数器
在这里插入图片描述

HotSpot VM设置程序执行方式:

-Xint:完全采用解释器模式执行程序

-Xcomp:完全采用即时编译器模式执行程序,如果即时编译出现问题,解释器会介入

-Xmixed:两种模式混合执行

JIT分类:

Client Compiler和ServerCompiler 简称为C1(对字节码进行简单可靠的优化,耗时短)和C2(耗时较长的优化,但优化的代码执行效率更高)

C1和C2编译器不同的优化策略:

C1编译器上主要有方法内联,去虚拟化,冗余消除

​ 方法内联:将引用的函数代码编译到引用点处,减少栈帧的生成,减少参数传递以及跳转过程

​ 去虚拟化:对唯一的实现类进行内联

​ 冗余消除:在运行期间把一些不会执行的代码折叠掉

C2优化主要是在全局层面,逃逸分析是优化的基础,优化:

​ 标量替换:用标量值代替聚合对象的属性值

​ 栈上分配:对未逃逸的对象分配对象在栈而不是堆

​ 同步消除:清除同步操作。

AOT编译器(了解)

jdk9引入,在程序运行前将字节码转换为机器码的过程,好处:Java虚拟机加载已经预编译成二进制库,可以直接运行,不必等待即时编译器的预热,减少Java应用给人带来“第一次运行慢”的不良体验。

缺点:破坏了Java的跨平台性;降低了Java链接过程的动态性,(加载的代码在编译器就必须全部已知)
JVM之String

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值