关于JVM原理

深入了解JVM

什么是JVM?
JVM是一种用于计算机设备的规范,他是一个虚拟出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

为什么我们不了解JVM也能进行JAVA开发?
这是因为Java开发技术在虚拟机层面影藏了底层技术的复杂性以及机器与操作系统的差异性,运行程序的物理机器的情况千差万别,而Java虚拟机在千差万别的物理机上建立了统一的运行平台,实现了在任意一台虚拟机上编译的程序都能在任意一台虚拟机上运行。
JVM和JRE以及JDK
JVM:是一个虚拟计算机,是JAVA实现跨平台的基础,但是JVM本身不是跨平台的
JRE:JVM+核心类库
JDK:JRE+开发工具

JAVA C系列 PYTHON的运行机制
JAVA:编译型和解释型语言
C系列:编译型语言
Python:解释型语言

JAVA是如何实现跨平台的?
首先我们编写的.Java原文件由编译器编译成.class字节码文件,此时的.class字节码文件具有平台无关性,然后通过指定平台的JVM翻译器将.class字节码文件翻译成指定平台的机器码,然后存储在硬盘上,当调用的时候并会执行,从而实现了JAVA的跨平台(不同系统平台的JAVA虚拟机是不一样的)。
JVM的原理示意图
在这里插入图片描述

JVM是java的核心和基础,在java编译器和OS平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于底层的操作系统和硬件平台,可以在上面执行java的字节码程序。
java编译器主要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成特定平台的机器码,通过特定平台运行。
解释器:将字节码转化为机器码
热点代码:一个程序中频繁被调用的方法或者代码块
JIT(Just In Time)代码生成器:用于处理热点代码

JVM的体系结构示意图
在这里插入图片描述
类装载器:用来装载.class文件
执行引擎:执行字节码,或者执行本地方法
程序计数器:程序寄存器是用于存储每个线程下一步将执行的JVM指令,如该方法为本地方法,则程序寄存器中不存储任何信息。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
Java栈:JVM栈中存放的为当前线程中的局部变量,如果是八大基本类型则存储的是实际值,引用类型存储的是指向Java堆上的地址。
本地方法栈:JVM采用本地方法栈来支持本地方法的执行,此区域用于存储每个本地方法调用的状态。Java中的本地方法接口JNI,使得本地方法可以在特定主机系统上的任何一个平台上实现运行(此处用的是设计模式当中的适配器设计模式)。
Java堆:它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。
方法区:方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
所有线程共享的数据区:堆和方法区。
所有线程独立的数据区:程序计数器,Java栈,本地方法栈。这些线程独享的数据区会在每个线程创建时为其创建对应的数据区,生命周期与该线程相同。

TLAB(Thread Local Allocation Buffer)

  1. 堆是JVM中所以线程共享的,因此在其上进行对象内存的分配都需要进行加锁,这也导致了new对象的开销是非常大的。
  2. JVM为了提升对象内存的分配效率,对于所创建的线程都会分配一块独立的空间TLAB。其大小是由JVM根据运行的情况计算而得,在TLAB上分配对象是不需要加锁,因此JVM在给线程的对象分配内存时会尽量在TLAB上分配。这样效率得到了很大的提高,但是当对象很大时,任然直接使用堆内存分配。
  3. TLAB仅作用于新生代的伊甸园区,因此在编写Java程序时,通常多个小对象比大对象分配起来更加高效。
  4. 所以新创建的对象,都会先存储在新生代中。

对象“已死”的判定算法
对于堆内存中的对象,GC要先判断对象是否可以进行回收,有以下两种算法用于判断:1.引用计数算法
2.可达性分析算法
引用计数算法
给对象中添加一个引用计数器,每当一个地方应用了对象,计数器加1;当引用失效,计数器减1;当计数器为0表示该对象已死、可回收。但是它很难解决两个对象之间相互循环引用的情况。
可达性分析算法(常用的判断对象已死的算法)
通过一系列称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即对象到GC Roots不可达),则证明此对象已死、可回收。Java中可以作为GC Roots的对象包括:虚拟机栈中引用的对象、本地方法栈中本地方法引用的对象、方法区静态属性引用的对象、方法区常量引用的对象。

垃圾收集算法

  1. 标记-清除算法
    最基础的算法,分标记和清除两个阶段:首先标记处所需要回收的对象,在标记完成后统一回收所有被标记的对象。
    它有两点不足:一个效率问题,标记和清除过程都效率不高;一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多导致需要分配大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。
    在这里插入图片描述

  2. 复制算法
    为了解决效率问题,出现了“复制”算法,他将可用内存按容量划分为大小相等的两块,每次只需要使用其中一块。当一块内存用完了,将还存活的对象复制到另一块上面,然后再把刚刚用完的内存空间一次清理掉。这样就解决了内存碎片问题,但是代价就是可以用内容就缩小为原来的一半。并且当存活对象较多时,需要进行复制的时间就很长,效率就会降低。

在这里插入图片描述

  1. 标记-整理算法
    复制算法在对象存活率较高时就会进行频繁的复制操作,效率将降低。因此又有了标记-整理算法,标记过程同标记-清除算法,但是在后续步骤不是直接对对象进行清理,而是让所有存活的对象都向一侧移动,然后直接清理掉端边界以外的内存。

在这里插入图片描述

  1. 分带收集算法
    当前商业虚拟机的GC都是采用分代收集算法,这种算法并没有什么新的思想,而是根据对象存活周期的不同将堆分为:新生代和老年代,方法区称为永久代(在新的版本中已经将永久代废弃,引入了元空间的概念,永久代使用的是JVM内存而元空间直接使用物理内存)。这样就可以根据各个年代的特点采用不同的收集算法。
    在这里插入图片描述
    新生代中的对象“朝生夕死”,每次GC时都会有大量对象死去,少量存活,使用复制算法。新生代又分为Eden区和Survivor区(Survivor from、Survivor to),大小比例默认为8:1:1。
    老年代中的对象因为对象存活率高、没有额外空间进行分配担保,就使用标记-清除或标记-整理算法。

猜你喜欢

转载自blog.csdn.net/qq_33271461/article/details/84965427