第一知识点,java字节码是如何在虚拟机里运行的(以下都已hotSpot虚拟机为例)
1.虚拟机视角看
执行 java代码首先需要将它编译成class文件加载到java虚拟机中,加载后的java类会被存放在方法区中,实际运行时,虚拟机会执行方法区内的代码。
如果你熟悉X86,你会发现这个段式内存管理中的代码段类似,而且,java虚拟机同样也在内存汇总划分出堆和栈来存储运行时的数据,不同的是,java虚拟机会将栈细分为面向java方法的java方法栈,面向用C++写的native方法的本地方法栈,以及存放各个线程执行位置的pc寄存器。
在运行过程中,每当调用进入一个java方法,java虚拟机会在当前线程的java方法栈中生成一个栈帧,用以存放局部变量以及字节码操作数。这个栈帧的大小是提前计算好额,而且java虚拟机不要求栈帧在内存空间里连续分布。
当退出当前执行的方法时,不管是正常返回,还是异常返回,java虚拟机均会弹出当前线程的当前栈帧,并将值舍弃。
2.从硬件视角看
java字节码无法直接执行,因此,java虚拟机需要将字节码翻译成机器码。
在hotSpot中,上述翻译过程有两种形式:第一种是执行翻译,相当于同声传译,即每解析一条字节码,便翻译成机器码并执行,第二种是及时翻译,则相当于线下翻译,即将整个方法中所包含的字节码统一翻译成机器码后在执行。
前者的优势在于无需等待编译,而后者的优势在于实际运行速度更快,hotSpot采用的是混合模式,综合了解释执行和及时编译两者的优点,它会先解释执行字节码,而后将其中返回执行的热点代码,以方法为单位进行及时编译。
第二大知识点,java虚拟机如何加载java类的
java虚拟机加载java类的过程可分为加载、链接以及初始化三步骤。
加载是指查找字节流,并且据此创建累的过程,加载乣借助类加载器,java虚拟机中,类加载器使用了双亲委派的 模型,即接收到加载请求事,会想将请求转发给父类加载器。
链接,是指将创建成的类合并至java虚拟机中,使之能够执行的过程,链接还分为验证、准备和解析三个阶段,分别完成"验证被加载类是否满足java虚拟机约束","为被加载类静态字段分配内存",以及"将被加载类中的符号引用解析成为实际引用"的工作,其中,java虚拟机规范并不要求解析阶段一定要在廉洁步骤中完成。
初始化,则是为标记为常量的字段赋值,以及执行方法的过程。类的初始化仅会被执行一次,这个特性被用来实现单例的延迟初始化。
第三大知识点,java虚拟机是如何执行垃圾回收的
java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象,他从一系列GC Roots出发,边标记边探索所有被引用的对象,为了防止在标记过程中堆栈的状态发生改变,java虚拟机采用安全点机制来实现stop-The-World操作,暂停其他非垃圾回收线程。
回收垃圾对象的内存共有三种基础算法,分别为:会造成内存碎片的清楚算法,性能开销较大的压缩算法,以及堆使用效率较低 的赋值算法。
通常来说,java虚拟机会采用分代回收的思想,将堆划分为新生代和老年代,并且通过在不同代中应用不同的垃圾回收算法。
传统的做法是将新生代再划分为Eden区和两个大小一致的Survivor区。在只针对新生代的Minor GC中,Eden区和非空Survivor区的存活对象会被赋值到空的Survivor区中,当Survivor区中的存活对象复制次数超过一定数值时,他将被晋升至老年代。
因为Minor GC只针对新生代进行来及回收,所以在枚举GC Roots的时候,它需要考虑从老年代到新生代的引用。为了避免扫描整个老年代,java虚拟机引入了名为卡麦的技术,大致地标出可能存在老年代到新生代引用的内存区域。
G1 垃圾回收器将堆划分为多个等大的区域,每个区域都可以充当 Eden 区,Survivor 区或者老年代区。G1 会优先收集垃圾最多的区域,从而最大化垃圾回收的效益。这也是 Garbage First 名字的由来。
Java 11 中引入的实验性垃圾回收器 ZGC,仅在扫描 GC Roots 时请求 Stop-The-World,暂停应用线程。因此,它宣称可将 GC 暂停时间控制在 10ms 以下。ZGC 暂时没有应用分代回收的思路,将整个堆空间看成一块,其代价是垃圾回收 CPU 消耗较高。
第四大知识点,java内存模型是什么
在现代计算机中,代码通常不会按照书写顺序执行,造成这一情况的原因有三个,分别是编译器的重排序,处理器的乱序执行,以及内存系统的重排序。
以内存系统重排序为例,在多处理器体系架构下,每个处理器都可能缓存了一部分数据。由于时刻保持缓存数据与内存数据同步的性能代价太大,因此部分体系架构可能允许缓存数据与内存数据不同步。这对 Java 程序的影响便是,两个不同的 Java 线程在同一时间内看到的同一块内存地址中的值可能不同。
Java 内存模型是针对上述问题而提出的一套规范,用以允许 Java 程序员更为细致地定义 Java 程序的内存行为。它通过定义了一系列的 happens-before 操作,让应用程序开发者能够轻易地表达不同线程的操作之间的内存可见性。
在遵守 Java 内存模型的前提下,即时编译器以及底层体系架构能够调整内存访问操作,以达到性能优化的效果。如果开发者没有正确地利用 happens-before 规则,那么将可能导致数据竞争。
Java 内存模型是通过内存屏障来禁止重排序的。对于即时编译器来说,内存屏障将限制它所能做的重排序优化。对于处理器来说,内存屏障会导致缓存的刷新操作。