JVM原理-超详细总结

JVM概念

  • JVM是java的核心和基础在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。

JVM的生命周期:

  1. JVM的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。
  2. JVM实例的运行:main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。
  3. JVM实例的消亡:当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用java.lang.Runtime类或者java.lang.System.exit()来退出

JVM运行内存模型

概念:JVM把文件.class字节码加载到内存,对数据进行校验,转换和解析,并初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制
以下是JVM的概念图

  • ?加载:(重点)
    加载阶段是“类加载机制”中的一个阶段,这个阶段通常也被称作“装载”,主要完成:
    1.通过“类全名”来获取定义此类的二进制字节流
    2.将字节流所代表的静态存储结构转换为方法区的运行时数据结构—-模板
    3.在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
    解析:相对于类加载过程的其他阶段,加载阶段(准备地说,是加载阶段中获取类的二进制字节流的动作)是开发期可控性最强的阶段,因为加载阶段可以使用系统提供的类加载器(ClassLoader)来完成,也可以由用户自定义的类加载器完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式。
    加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式有虚拟机实现自行定义,虚拟机并未规定此区域的具体数据结构。然后在java堆中实例化一个java.lang.Class类的对象,这个对象作为程序访问方法区中的这些类型数据的外部接口。
  • ?验证:
    验证是链接阶段的第一步,这一步主要的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
    验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符 引用验证。
  • ?准备:
    准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的知识点,首先是这时候进行内存分配的仅包括类变量(static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。其次是这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量定义为:
    public static int value = 12;
    那么变量value在准备阶段过后的初始值为0而不是12,因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为12的动作将在初始化阶段才会被执行。
    上面所说的“通常情况”下初始值是零值,那相对于一些特殊的情况,如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,建设上面类变量value定义为:
    public static final int value = 123;
    编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value设置为123。
  • ?解析:
    解析阶段是虚拟机常量池内的符 引用替换为直接引用的过程。
    符 引用:符 引用是一组符 来描述所引用的目标对象,符 可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符 引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。
    直接引用:直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符 引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
    解析的动作主要针对类或接口、字段、类方法、接口方法四类符 引用进行。分别对应编译后常量池内的CONSTANT_Class_Info、CONSTANT_Fieldref_Info、CONSTANT_Methodef_Info、CONSTANT_InterfaceMethoder_Info四种常量类型。
    1.类、接口的解析
    2.字段解析
    3.类方法解析
    4.接口方法解析
  • ?初始化:(了解)
    类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。

类装载器的内部机制—————-双亲委派机制(重点)

原理:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

?Java栈:(喝多了吐)——非线程共享

  • ?Java栈也称作虚拟机栈(Java Vitual Machine Stack),也就是我们常常所说的栈。JVM栈是线程私有的,每个线程创建的同时都会创建自己的JVM栈,互不干扰。 Java栈是Java方法执行的内存模型。
  • ?Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。
  • ?当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。 当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。
  • ?局部变量表: 用来存储方法中的局部变量,对于引用类型的变量,则存的是指向对象的引用,对于基本数据类型的变量,则直接存储它的值
  • ?操作数栈: 栈最典型的一个应用就是用来对表达式求值。在一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
  • ?指向运行时常量池的引用: 因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
  • ?方法返回地址: 当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址

?程序计数器:——非线程共享

  • ?程序计数器(Program Counter Register),也有称作为PC寄存器。
    由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。
  • ?在JVM规范中规定,如果线程执行的是非native(本地)方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。
  • ?由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

?本地方法栈:——非线程共享

JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。
本地方法栈与Java栈的作用和原理非常相似。**区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。**在JVM规范中,并没有对本地方法栈的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

创建对象内存分析

  • ? 标记-清除(Mark-Sweep)算法

标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。(需要两次扫描)

  • ? 复制算法

?Minor GC、Full GC触发条件

Minor GC触发条件

  • Eden区域满了,或者新创建的对象大小 > Eden所剩空间
    CMS设置了CMSScavengeBeforeRemark参数,这样在CMS的Remark之前会先做一次Minor GC来清理新生代,加速之后的Remark的速度。这样整体的stop-the-world时间反而短
  • Full GC的时候会先触发Minor GC

Full GC触发条件

  • Minor GC后存活的对象晋升到老年代时由于悲观策略的原因,有两种情况会触发Full GC,
    一种是之前每次晋升的对象的平均大小 > 老年代剩余空间;
    一种是Minor GC后存活的对象超过了老年代剩余空间。

    这两种情况都是因为老年代会为新生代对象的晋升提供担保,而每次晋升的对象的大小是无法预测的,所以只能基于统计,一个是基于历史平均水平,一个是基于下一次可能要晋升的最大水平。
    这两种情况都是属于promotion failure
  • CMS失败,发生concurrent mode failure会引起Full GC,这种情况下会使用Serial Old收集器,是单线程的,对GC的影响很大。concurrent mode failure产生的原因是老年代剩余的空间不够,导致了和gc线程并发执行的用户线程创建的大对象(由PretenureSizeThreshold控制新生代直接晋升老年代的对象size阀值)不能进入到老年代,只要stop the world来暂停用户线程,执行GC清理。可以通过设置CMSInitiatingOccupancyFraction预留合适的CMS执行时剩余的空间
  • 新生代直接晋升到老年代的大对象超过了老年代的剩余空间,引发Full GC。注意于promotion failure的区别,promotion failure指的是Minor GC后发生的担保失败
  • Perm永久代空间不足会触发Full GC, 可以让CMS清理永久代的空间。设置CMSClassUnloadingEnabled即可
  • System.gc()引起的Full GC, 可以设置DisableExplicitGC来禁止调用System.gc引发Full GC

?OOM – Out of Memory,内存溢出
软件所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。此时软件或游戏就运行不了,系统会提示内存溢出,有时候会自动关闭软件,重启电脑或者软件后释放掉一部分内存又可以正常运行该软件

?JVM调优参数简介

-XX 参数被称为不稳定参数,之所以这么叫是因为此类参数的设置很容易引起JVM 性能上的差异,使JVM 存在极大的不稳定性。如果此类参数设置合理将大大提高JVM 的性能及稳定性。

?JVM参数示例

以上是我对JVM的个人理解以及引用了一些大佬的概念,希望大家一起学习探讨~有错误的地方希望能给我指出 0.0

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览92992 人正在系统学习中

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年8月15日
下一篇 2022年8月15日

相关推荐