1.创建对象的步骤
1.判断对象对应的类是否 加载、链接、初始化
2.为对象分配内存
- 指针碰撞
- 如果内存规整,使用指针碰撞
- 如果内存是规整的,那么虚拟机将采用的是指针碰撞法(Bump The Pointer)来为对象分配内存。意思是所有用过的内存在一边,空闲的内存在另外一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针向空闲那边挪动一段与对象大小相等的距离罢了。 如果垃圾收集器选择的是Serial、ParNew这种基于压缩算法的,虚拟机采用这种分配方式。 一般使用带有compact(整理)过程的收集器时,使用指针碰撞。
- 空闲列表
- 如果内存不规整,虚拟机需要维护一个列表,使用空闲列表分配
- 意思是虚拟机维护了一个列表,记录上哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式称为“空闲列表(Free List)”。
- 处理new对象的并发安全问题
- 保证new 对象时的线程安全,创建对象十分频繁,两种方式解决并发问题
- CAS失败重试,区域加锁
- TLAB 技术,每个线程在堆种都有都分配一块小内存。
- -XX:+/-UseTLAB
- 对象属性进行零值初始化过程。
- 设置对象的对象头,将类的元数据信息,hashcode,gc信息,锁信息存储在对象头。
- 执行init方法进行初始化。
2.对象的内存布局
3. 对象的访问定位
为什么在栈帧中的引用就能找到堆空间里面的对象实例和找到对应方法区的类元信息等呢???
通过栈上引用
3.1 句柄访问
3.2 直接指针访问
4. 执行引擎
将字节码指令 解释或者在编译为对应平台上的本地机器指令才可以。
cpu 执行 机器指令。
4.1 解释器
对字节码采用逐行解释的方式,翻译为平台对应的机器指令
4.2 jit编译器
就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言。
JIT编译器在运行时会针对那些频繁被调用的“热点代码”做出深度优化,将其直接编译为对应平台的本地机器指令,以此提升Java程序的执行性能。
对热点代码缓存,
4.3 为什么并存
当虚拟机启动的时候***,解释器可以首先发挥作用,**而不必等待即时编译器全部编译完成再执行,这样可以省去许多不必要的编译时间。并且随着程序运行时间的推移,即时编译器逐渐发挥作用,根据热点探测功能,将有价值的字节码编译为本地机器指令,*以换取更高的程序执行效率。
刚开始启动server,需要解释器来执行。接着对热点代码缓存,
4.4 热点代码和探测方式
一个被多次调用的方法,或者是一个方法体内部循环次数较多的循环体都可以被称之为“热点代码” ,因此都可以通过JIT编译器编译为本地机器指令。 由于这种编译方式发生在方法的执行过程中,因此也被称之为栈上替换,
一个方法究竟要被调用多少次,或者一个循环体究竟需要执行多少次循环才可以达到这个标准?必然需要一个明确的阈值,JIT编译器才会将这些“热点代码”编译为本地机器指令执行。这里主要依靠热点探测功能。
目前HotSpot VM所采用的热点探测方式是基于计数器的热点探测
4.4.1 方法调用计数器
- 这个计数器就用于统计方法被调用的次数,它的默认阈值在 Client 模式下是 1500 次,在 Server 模式下是 10000 次。超过这个阈值,就会触发JIT编译。
- 这个阈值可以通过虚拟机参数***-XX:CompileThreshold***来人为设定。
4.4.2 热度衰减
热度衰减
如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。
当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。