做一家新闻媒体网站多少钱,网络规划设计师 视频 网盘,wordpress安全插件,公司管理系统软件面试阿里被问到JVM#xff0c;不逼逼赖赖#xff0c;直接盘给面试官看#xff01;#xff01;#xff01;概述JVM体系结构类加载机制类加载器类加载过程双亲委派机制全盘负责委托机制打破双亲委派机制自定义类加载器实现JVM运行时数据区程序计数器虚拟机栈本地方法栈堆方法… 面试阿里被问到JVM不逼逼赖赖直接盘给面试官看概述JVM体系结构类加载机制类加载器类加载过程双亲委派机制全盘负责委托机制打破双亲委派机制自定义类加载器实现JVM运行时数据区程序计数器虚拟机栈本地方法栈堆方法区元空间运行时常量池直接内存垃圾回收机制GC对象判定方法垃圾收集算法垃圾收集器JVM调优参数概述
JVM是Java Virtual MachineJava虚拟机的缩写JVM是一种用于计算设备的规范它是一个虚构出来的计算机是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java虚拟机本质上就是一个程序当它在命令行上启动的时候就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机JVM从软件层面帮我们屏蔽不同操作系统在底层硬件与指令上的区别字节码文件.class就可以在该平台上运行。这就是“一次编译多次运行”。
JVM体系结构 Java虚拟机包含类装载器子系统、执行引擎、运行时数据区、本地方法接口和垃圾收集模块。其中垃圾收集模块在Java虚拟机规范中并没有要求Java虚拟机垃圾收集但是在没有发明无限的内存之前大多数JVM实现都是有垃圾收集的。
类装载器子系统根据给定的全限定类名如java.lang.Object来装载class文件到运行时数据区域的方法区中。执行引擎执行字节码或执行本地方法。运行时数据区我们常说的JVM的内存堆方法区虚拟机栈本地方法栈程序计数器。本地方法接口与本地方法库交互作用就是为了融合不同编程语言为Java所用它的初衷是融合C/C程序。
首先通过编译器把Java代码转换成字节码类加载器再把字节码加载到内存中运行时数据区的方法区内而字节码文件只是JVM的一套指令集规范不能直接交给底层系统去执行所以需要特定的命令解析器执行引擎将字节码翻译成底层系统指令再交给CPI去执行而这个过程需要调用其他语言的本地库接口来实现整个程序的功能。
类加载机制
Java类加载机制就是虚拟机把描述类的数据从Class文件加载到内存并对数据进行校验解析和初始化最终形成可以被虚拟机直接使用的java类型。
类加载器
类加载器分为启动类加载器扩展类加载器应用程序类加载器自定义类加载器。各种类加载器之间存在着逻辑上的父子关系但不是真正意义上的父子关系因为它们直接没有从属关系。除了启动类加载器Bootstrap ClassLoader是由C编写的其他都是由Java编写的。由Java编写的类加载器都继承自类java.lang.ClassLoader。
启动类加载器BootstrapClassLoader负责加载$JAVA_HOME/jre/lib目录下的核心类库比如rt.jarcharsets.jar。扩展类加载器ExtClassLoader负责加载支撑J$JAVA_HOME/jre/lib/ext目录下的JAR类包。父加载器是启动类加载器。应用类加载器AppClassLoader负责加载ClassPath路径下的类包主要就是加载我们自己写的那些类。父加载器是扩展类加载器。自定义类加载器CustomClassLoader负责加载用户自定义目录下的类包。父加载器是应用类加载器。 类加载过程
java类加载分为5个过程加载–验证–准备–解析–初始化。这5个阶段一般是顺序发生的但在动态绑定的情况下解析阶段发生在初始化阶段之后。 加载:将字节码从不同的数据源可能是 class 文件也可能是 jar 包甚至网络转化为二进制字节流加载到内存中将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构并在堆中生成一个代表该类的 java.lang.Class 对象作为对方法区这个类的各种数据的访问入口。 验证验证的目的是为了确保加载进来的class文件符合JVM的规范一般是进行文件格式的验证、元数据的验证、字节码验证和符号引用验证。
文件格式的验证验证字节流是否符合Class文件格式的规范并且能被当前版本的虚拟机处理该验证的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。经过该阶段的验证后字节流才会进入内存的方法区中进行存储后面的三个验证都是基于方法区的存储结构进行的。元数据的验证对类的元数据信息进行语义校验其实就是对类中的各数据类型进行语法校验保证不存在不符合Java语法规范的元数据信息。字节码验证该阶段验证的主要工作是进行数据流和控制流分析对类的方法体进行校验分析以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。符号引用验证这是最后一个阶段的验证它发生在虚拟机将符号引用转化为直接引用的时候解析阶段中发生该转化主要是对类自身以外的信息常量池中的各种符号引用进行匹配性的校验。
准备给类的静态变量分配空间并赋予默认值。解析将符号引用替换为直接引用该阶段会把一些静态方法(符号引用比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用)这是所谓的静态链接过程(类加载期间完成)动态链接是在程序运行期间完成的将符号引用替换为直接引用。初始化对类的静态变量初始化为指定的值执行静态代码块。 双亲委派机制
双亲委派机制就是当某个类加载器收到加载类的请求如果这个类没有被加载过该类加载器不会直接加载会先为委派给父加载器如果父加载器没有加载过依次往上传递直到顶层启动类加载器。如果父加载器可以完成加载任务则父加载器加载返回如果父加载器不能完成加载任务才会自己去进行加载。一句话概述双亲委派机制加载流程就是从下往上检查类是否已经被加载从上往下尝试去加载。
双亲委派机制的优点
沙箱安全机制避免核心API被篡改。自己写的java.lang.String.class类不会被加载。避免重复加载如果父加载器已经加载过该类子类加载器就没有必要再去加载。
双亲委派机制加载类的核心代码 ClassLoader类的loadClass()方法
protected Class? loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// 首先会检查该类是否已经被本类加载器加载如果已经被加载则直接返回Class? c findLoadedClass(name);if (c null) {// 如果没有被加载则委托父加载器去加载//加入Java开发交流君样593142328一起吹水聊天long t0 System.nanoTime();try {if (parent ! null) {// 让父加载器对象去调用loadClass方法c parent.loadClass(name, false);} else {// parentnull说明父加载器是启动类加载器。启动类加载器是C编写的这里去调用本地方法区尝试加载该类。c findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}if (c null) {// If still not found, then invoke findClass in order// to find the class.long t1 System.nanoTime();// 如果父加载器没有加载到该类则自己去加载。这里会调用URLClassLoader类的findClass()方法//加入Java开发交流君样593142328一起吹水聊天c findClass(name);sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}全盘负责委托机制
全盘负责委托机制就是当一个Classloader加载一个Class的时候这个Class所依赖的和引用的其它Class通常也由这个Classloader负责加载。
打破双亲委派机制
打破双亲委派机制就是我们希望自定义类加载器去直接加载指定类而不是先委托父加载器去加载或者是自定义类加载器加载不到才让父加载器去进行加载。
自定义类加载器实现
了解双亲委派机制以及打破双亲委派机制之后我们可以自己写一个自定义类加载器。自定义类加载器实现思路
如果使用双亲委派机制就是重写findClass()方法类加载器具体去加载类的方法 如果要打破双亲委派机制在重写findClass()方法基础上还需要重新loadClass()方法这里我们可以改写逻辑先让该类加载器去加载类加载不到再让父加载器去进行加载
JVM运行时数据区
程序计数器 程序计数器线程私有的它的生命周期与线程相同它是一块较小的内存空间可以看作是当前线程所执行的字节码的行号指示器。 如果线程正在执行的是一个Java方法这个计数器记录的是正在执行的虚拟机字节码指令的地址如果线程当前正在执行的方法是本地方法这个计数器值则应为空。 字节码解释器的工作就是通过这个计数器的值来选取下一条需要执行的字节码指令分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 这个区域是唯一不会抛出OutOfMemoryError异常的区域。 虚拟机栈
虚拟机栈是线程私有的它的生命周期与线程相同。每个方法被执行的时候Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完毕的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表存放了编译期可知的各种Java虚拟机基本数据类型boolean、byte、char、short、int、float、long、double对象引用和returnAddress类型。
以下异常条件与Java虚拟机栈相关
如果线程中请求的栈深度大于虚拟机所允许的深度Java虚拟机将会抛出StackOverflowError异常。
如果Java虚拟机栈容量可以动态扩展当栈扩展时无法申请到足够的内存Java虚拟机将会抛出OutOfMemoryError异常。 本地方法栈
本地方法栈是线程私有的生命周期与当前线程一致。与虚拟机栈的作用是一样的区别只是虚拟机栈为虚拟机执行Java方法也就是字节码服务而本地方法栈是为虚拟机使用到的本地方法服务。
以下异常条件与本地方法栈相关联与虚拟机栈一样
如果线程中请求的栈深度大于虚拟机所允许的深度Java虚拟机将会抛出StackOverflowError异常。
如果Java虚拟机栈容量可以动态扩展当栈扩展时无法申请到足够的内存Java虚拟机将会抛出OutOfMemoryError异常。 堆
堆是线程共享的在虚拟机启动的时候创建从中分配类实例几乎所有的对象都存放在堆中但是不是所有的和数组的内存。对于大多数应用来说堆是内存最大的一块区域同时堆是内存模型中最重要的一个区域也是JVM调优重点关注的区域。对象的堆存储由自动存储管理系统称为垃圾收集器回收对象永远不会显式释放。
以下异常情况与堆相关联
如果在Java堆中没有内存完成实例分配并且堆也无法再扩展时Java虚拟机将会抛出OutOfMemoryError异常。
堆内存分为年轻代Young Generation和老年代Old Generation。
年轻代YoungGen年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量Survivor两个区占小容量默认比例是8:1:1。老年代OldGen。 方法区元空间
方法区是线程共享的在虚拟机启动的时候创建它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
以下异常条件与方法区域相关联
如果方法区无法满足新的内存分配需求时Java虚拟机将会抛出OutOfMemoryError异常。 运行时常量池
运行时常量池是方法区的一部分。它包含多种常量范围从编译时已知的数字文字到必须在运行时解析的方法和字段引用。运行时常量池的功能类似于常规编程语言的符号表尽管它包含的数据范围比典型的符号表还大。每个运行时常量池都是从Java虚拟机的方法区分配的。当Java虚拟机创建类或接口时将为该类或接口构造运行时常量池。
以下异常条件与类或接口的运行时常量池的构造相关联
如果运行时常量池无法再申请到内存时则Java虚拟机将抛出OutOfMemoryError异常。 直接内存
直接内存并不是虚拟机运行时数据区的一部分也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存有时候会使用而且也可能导致OutOfMemoryError异常出现所以这里简单提一下。
直接内存的分配不会受到Java 堆大小的限制既然是内存肯定还是会受到本机总内存的大小及处理器寻址空间的限制。
服务器管理员配置虚拟机参数时一般会根据实际内存设置-Xmx等参数信息但经常会忽略掉直接内存使得各个内存区域的总和大于物理内存限制从而导致动态扩展时出现OutOfMemoryError异常。
垃圾回收机制
在java中程序员是不需要显示的去释放一个对象的内存的而是由虚拟机自行执行。在JVM中有一个垃圾回收线程它是低优先级的在正常情况下是不会执行的只有在虚拟机空闲或者当前堆内存不足时才会触发执行扫描那些没有被任何引用的对象并将它们添加到要回收的集合中进行回收。
GC对象判定方法 引用计数法为每个对象创建一个引用计数器有对象引用时计数器1引用被释放时计数器-1当计数器为0时就可以被回收。它有一个缺点就是不能解决循环引用的问题。 可达性算法引用链法从GC Roots 开始向下搜索搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时则证明此对象是可以被回收的。【白嫖资料】
垃圾收集算法 分代收集理论 标记—清除算法
标记无用对象然后进行清除回收。缺点效率不高无法清除垃圾碎片。
标记—复制算法
按照容量划分为2个大小相等的内存区域当一块用完的时候将活着的对象复制到另一块上然后再把已使用区域的内存空间一次清理掉。缺点内存使用率不高只有原来的一半。 标记—整理算法
标记无用对象让所有存活对象都向一端移动然后直接清除掉端边界以外的内存。 垃圾收集器 Serial 收集器标记—复制算法新生代单线程收集器标记和清理都是单线程[优点是简单高效] ParNew 收集器标记—复制算法新生代并行收集器实际上是Serial收集器的多线程版本在多核CPU环境下有着比Serial更好的表现。 Parallel Scavenge 收集器标记—复制算法新生代并行收集器追求高吞吐量高效利用CPU。吞吐量用户线程时间/(用户线程时间GC线程时间)高吞吐量可以高效的利用CPU时间尽快完成程序的运算任务适合后台应用等对交互相应要求不高的场景。 Serial Old 收集器标记—整理算法老年代单线程收集器Serial收集器的老年代版本。 Parallel Old 收集器标记—整理算法老年代并行收集器吞吐量优先Parallel Scavenge收集器的老年代版本。 Concurrent Mark SweepCMS收集器标记—清除算法老年代并行收集器以获取最短回收停顿时间为目标的收集器具有高并发、低停顿的特点追求最短GC回收停顿时间。 Garbage FirstG1收集器标记—整理算法Java堆并行收集器G1收集器是JDK1.7提供的一个新收集器G1收集器基于“标记—整理”算法实现也就是说不会产生内存碎片。此外G1收集器不同于之前的收集器的一个重要特点是G1回收的范围是整个Java堆包括新生代老年代而前六种收集器回收的范围仅限于新生代或者老年代。 JVM调优参数
-Xms4g初始化堆大小为4g-Xmx4g堆最大内存为4g-XX:NewRatio4设置年轻代和老年代的内存比例为1:4-XX:SurvivorRatio8设置新生代Eden和Survivor比例为8:28:1:1-XX:UseParNewGC指定使用ParNew Serial Old垃圾回收器组合-XX:UseParallelOldGC指定使用ParNew ParNew Old垃圾回收器组合-XX:UseConcMarkSweepGC指定使用 CMS Serial Old垃圾回收器组合-XX:PrintGC开启打印gc信息-XX:PrintGCDetails打印gc详细信息
最后祝大家早日学有所成拿到满意offer