大家好,今天总结了常见的JVM,也是面试必问的知识点。首先,什么是JVM?1、Java虚拟机(JVM)是??可以运行Java代码的虚拟计算机。2.Jvm充当翻译器。我们平时写的Java程序是不能被操作系统直接识别的。这时候JVM的作用就体现出来了。它负责将我们的程序翻译成系统。“听”,告诉它我们的程序需要做什么。3、Jvm为每个操作系统开发了它对应的解释器,所以只要它的操作系统有对应版本的Jvm,那么这段Java编译代码就可以运行。有一句话想必大家都听过:“Java一次编译,随处运行”,就是这个道理。2.Jvm的架构?JVM由这四部分组成:运行时数据类加载器执行引擎垃圾收集器下面说说这四部分吧~~2.1运行时数据Java虚拟机会在进程中执行Java程序,它管理的内存分为几个不同的数据区,每个其中有自己的功能和生命周期。有些区域随着虚拟机进程的启动而存在,有些区域的建立和销毁取决于用户线程的启动和结束。运行区数据的划分:方法区、虚拟机栈、本地方法栈、堆、程序计数器。想必大家都看过上图。其实还可以细分的更细。看下面两张图:可以看出1.8版本前后有什么区别?我们来看看这几个区域是干什么的~~其实你可以把程序计数器看成是当前线程执行的字节码的行号指示符。Jvm在工作的时候,就是通过改变这个计数器的值来选择下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等都需要依赖程序计数器来完成。它就像十字路口的红绿灯。特点:1.占用内存小。2.每个线程都是私有的,就像下面的字节码一样。每行开头的黄色数字可以看作是程序计数器存储的内容:publicvoiddoSth1();descriptor:()vflags:ACC_PUBLICode:stack=2,locals=3,args_size=10:ldc#52:dup3:astore_14:monitorenter5:getstatic#28:ldc#310:invokevirtual#413:aload_1virtualmachinestack虚拟机栈,描述了线程内存模型,也称为线程栈,也是每个线程私有的,以及它的生命周期与线程一致。每个方法执行时,jvm都会同步创建一个栈帧来存放局部变量表、操作数栈、动态连接、方法退出等信息。一个方法的生命周期实现了一个栈帧从入栈到出栈的全过程。特点:1.Bornwiththethread,diewiththethread2.高级栈示意图:本地方法栈本地方法栈其实和虚拟栈很像。我们知道java的底层使用了大量的C代码来实现。而调用c端的方法会有native来表示本地方法,native方法栈就是为它准备的。特点:1.每个线程私有2.与本地方法相关的本地修改:publicfinalnativebooleancompareAndSwapInt(Objectvar1,longvar2,intvar4,intvar5);堆可以说是jvm中最大的内存区域,它是所有线程共享的,几乎所有的对象实例都会分配在这里。java堆是垃圾收集器回收的主要区域。从内存回收的角度来看,堆空间可以分为新生代和老年代,新生代又可以分为Eden区和Survivor区。特点:1.所有线程共享2.占用内存空间大3.先进先出堆划分:方法区方法区也是各个线程共享的内存区域,用来存放类信息已经被虚拟机加载的常量、静态变量、即时编译代码等数据。从上图我们可以看出1.8前后方法区的位置是不一样的。在Java8之前,有一个永久代的概念,其实就是指HotSpot虚拟机上的永久代。它使用永久代来实现JVM规范定义的方法区功能。这部分是在堆中实现的,是受GC的,但是由于永久代有-XX:MaxPermSize的上限,如果你大量调用String.intern方法(将字段字符串放入常量区generation)或者动态生成classes(将class信息放入permanentgeneration),很容易造成OOM。因此在Java8中,方法区的实现被移到了本地内存中的元空间中,使得方法区不受JVM控制,该区域不会被GC,从而提高了性能。如果放在本地内存,不会出现永久代限制大小导致的OOM异常。另外,运行时常量池也是方法区的一部分,用于存放编译过程中产生的各种字面量和符号引用。这部分内容在类加载后进入常量池。特点:1.所有线程共享2.1.8后移至元空间3.涉及常量池直接内存从上图我们可以看出这块区域有直接内存。虚拟机运行时,直接内存不属于数据区。其实可以理解为堆外内存。在某些场景下,例如:NIO类引入了一种基于通道(Channel)和缓冲区(Buffer)的IO方法。它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的对象作为对这块内存的引用,这样可以显着提高性能,因为它避免了在Java堆之间来回复制数据和本机堆。2.2类加载器1.什么是类加载机制?JVM运行时,java虚拟机从Class文件中加载描述类的数据到内存中,并对数据进行校验、转换、解析和初始化,最终形成一个类,可以被jvm使用的类型直接来说,这就是类加载机制。2.说说类加载的过程?一张开场图:这张图说明了一个类从加载到虚拟机内存到卸载内存的整个生命周期。一般来说,我们将Java类的加载过程分为三个主要步骤:加载、链接和初始化。具体行为在Java虚拟机规范中有详细定义。1、首先是加载阶段,即Java从不同的数据源读取字节码(jar包)数据到JVM中,并将其映射为JVM可识别的数据结构(Class对象)。重点:加载阶段是用户参与阶段,我们可以自定义类加载器来实现自己的类加载过程。通过字节流将类的.class文件中的二进制数据读入内存。然后在堆中创建一个java.lang.class对象,将类的数据结构封装在方法区。只会创建一个Class对象,它描述了有哪些构造方法,有哪些成员变量。2、第二个阶段是Linking,这是最核心的一步,简单来说就是把原来的类定义信息平滑的转换成JVM运行的过程。这里又可以进一步细分为三个步骤:①验证这是对虚拟机安全的重要保证。JVM需要验证字节信息是否符合Java虚拟机规范,否则会被认为是VerifyError,防止恶意信息或不正确的Complied信息危害JVM的运行,验证阶段可能会触发更多的加载类。②准备在类或接口中创建静态变量,并初始化静态变量的初始值。但是这里的“初始化”和下面的显式初始化阶段是不一样的。测试的重点是分配需要的内存空间,不会执行进一步的JVM指令。这里的初始化是指:1.8种基本数据类型默认初始值为0。2.引用类型默认初始值为null。3.带有staticfinal修饰的常量会直接赋值,例如:staticfinalintx=123;那么x会直接初始化为123。③解析这一步会将常量池中的符号引用替换为直接引用。符号引用是唯一的字符串,直接引用可以理解为地址值和偏移量。3、最后是初始化阶段,是真正执行类初始化的代码逻辑的步骤,包括static字段的动作,执行类定义中的static逻辑编译器在初始化块中会在编译时组织这部分逻辑阶段,父类型的初始化逻辑优先于当前类型的逻辑。初始化顺序:先是父类静态字段(静态成员变量)或静态代码库块,再是子类静态字段或子类静态代码块,所以java.lang.Object类总是先初始化。初始化?通过new关键字实例化对象,读取或设置类的静态变量,调用类的静态方法。当以上三种行为通过反射初始化子类时,会触发父类的初始化作为程序入口运行,即指的是main方法4、类加载器有哪些?启动类加载器:负责加载环境变量下jre/lib下的jar文件扩展类加载器:负责加载环境变量下jre/lib/ext目录下的jar包应用类加载器:就是加载内容我们熟悉的类路径。自定义加载器:继承ClassLoader即可实现。就是这样。概念:表示一个类加载器收到一个类加载请求,它不会先加载它,而是交给它的父类加载,逐层迭代。用上图来说明,如果应用类加载器收到一个类加载请求,它会先发送给扩展类加载器,然后再发送给启动类加载器。如果启动类加载器无法完成类加载请求,则返回扩展类加载器。如果扩展类加载器无法完成,最后会转到应用程序类加载器。好处:1.避免重复加载Java类型2.沙盒安全机制:保证核心类不被篡改。6、classLoader和class.forName的区别class.forName()除了会把类的.class文件加载到jvm中,还会解释类,执行类中的static块。当然你可以指定是否执行静态块。classLoader只做一件事,就是将.class文件加载到jvm中,并不会执行static中的内容,只执行newInstance中的static块。7.思维导图最后送上一张自己总结的思维导图。今天就写到这里!!给大家介绍下JVM、运行时数据、类加载机制。希望大家在面试前能够掌握Jvm相关的知识。
