JIT

JIT(Just-In-Time)编译器是一种能够在程序运行时动态编译代码的编译器,它可以将字节码或其他中间表示形式编译为本地机器指令,从而提高程序的执行速度。JIT 编译器的发展可以追溯到上世纪 80 年代,但是直到近年来,随着计算机硬件性能的提高和虚拟化技术的普及,JIT 编译器才开始得到广泛应用。
JIT 编译器的作用是通过实时编译字节码或其他中间表示形式,将程序的执行速度提高到接近本地代码的水平。它可以优化循环、内联函数、消除不必要的计算等,从而提高程序的效率。JIT 编译器还可以动态生成代码,从而实现动态语言的高效执行。
在 Java 虚拟机(JVM)中,JIT 编译器是一个非常重要的组件。JVM 中的 JIT 编译器可以将 Java 代码编译为本地机器指令,从而提高程序的执行速度。以下是 JIT 在 JVM 中的应用:

  1. 即时编译 Java 代码:JIT 编译器可以将 Java 代码编译为本地机器指令,从而提高程序的执行速度。
  2. 动态编译:JIT 编译器可以根据程序的运行情况动态生成代码,从而优化程序的执行效率。
  3. 热点代码优化:JIT 编译器可以根据程序的运行情况,对热点代码进行优化,从而进一步提高程序的执行速度。
  4. 垃圾回收:JIT 编译器可以与 JVM 的垃圾回收器配合使用,从而减少垃圾回收的开销,提高程序的性能。

总之,JIT 编译器在 JVM 中扮演着非常重要的角色,它可以大大提高 Java 程序的执行速度和性能。】

TLAB(Thread Local Allocation Buffer)

是Java虚拟机中的一种内存分配技术。它为每个线程分配了一个私有的内存缓冲区,用于在对象创建时进行快速的内存分配,从而避免了多线程竞争的开销。
TLAB的作用是优化Java虚拟机中的对象分配过程。在没有TLAB的情况下,每次对象分配都需要进行同步操作,从而导致了较大的性能开销。而使用TLAB可以避免这种同步操作,从而提高了对象分配的效率。
TLAB的大小可以通过JVM参数进行调整,以适应不同程序的内存需求。需要注意的是,如果TLAB过小,就会导致频繁的同步操作,从而降低程序的性能。反之,如果TLAB过大,就会浪费大量的内存空间。

对象的创建

Java 对象的创建过程我建议最好是能默写出来,并且要掌握每一步在做什么。

# Step1:类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

# Step2:分配内存

类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式“指针碰撞”“空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定
内存分配的两种方式 (补充内容,需要掌握):

  • 指针碰撞:
    • 适用场合:堆内存规整(即没有内存碎片)的情况下。
    • 原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。
    • 使用该分配方式的 GC 收集器:Serial, ParNew
  • 空闲列表:
    • 适用场合:堆内存不规整的情况下。
    • 原理:虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。
    • 使用该分配方式的 GC 收集器:CMS

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是”标记-清除”,还是”标记-整理”(也称作”标记-压缩”),值得注意的是,复制算法内存也是规整的。
内存分配并发问题(补充内容,需要掌握)
在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:

  • CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配

    TLAB(Thread Local Allocation Buffer)

    是Java虚拟机中的一种内存分配技术。它为每个线程分配了一个私有的内存缓冲区,用于在对象创建时进行快速的内存分配,从而避免了多线程竞争的开销。 TLAB的作用是优化Java虚拟机中的对象分配过程。在没有TLAB的情况下,每次对象分配都需要进行同步操作,从而导致了较大的性能开销。而使用TLAB可以避免这种同步操作,从而提高了对象分配的效率。 TLAB的大小可以通过JVM参数进行调整,以适应不同程序的内存需求。需要注意的是,如果TLAB过小,就会导致频繁的同步操作,从而降低程序的性能。反之,如果TLAB过大,就会浪费大量的内存空间。

# Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
在Java中,当创建一个新的对象时,如果该对象的成员变量没有被显式地赋值,那么它们会被自动初始化为默认值,即“零值”。
例如,假设我们有一个如下所示的类:

  1. public class Person {
  2. private String name;
  3. private int age;
  4. private boolean married;
  5. // 其他成员变量
  6. // 构造方法和其他方法
  7. 当我们创建一个Person对象时,如果没有对其成员变量进行初始化,那么它们会被自动初始化为以下默认值:
  8. // ● name:null
  9. // ● age:0
  10. // ● married:false
  11. }


Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

# Step5:执行 init 方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

在Java虚拟机中,句柄池(Handle Pool)是一种用于管理对象引用的数据结构。句柄池的英文原义是“把手池”,也可以称为“句柄表”(Handle Table)。

句柄池的作用是将对象引用分为两部分,一部分是句柄(Handle),另一部分是实例数据(Instance Data)。句柄是一个固定大小的数据结构,包含了指向实例数据的指针和指向类元数据的指针。实例数据则是对象的成员变量和实例方法等信息。

通过使用句柄池,Java虚拟机可以将对象的引用从直接指针变成间接指针,从而提高了内存管理的灵活性和效率。具体来说,句柄池可以实现以下几个功能:

  1. 可以将对象在内存中的位置随时改变,而不需要修改对象引用中存储的指针值。
  2. 可以将对象引用分为全局句柄和本地句柄两种类型,从而实现不同级别的内存管理和垃圾回收策略。
  3. 可以减少对象引用在内存中占用的空间,从而提高内存利用率和垃圾回收效率。

需要注意的是,Java虚拟机中是否使用句柄池是由具体实现决定的,不同的虚拟机实现可能采用不同的内存管理方式。