java虚拟机

什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,顾名思义它是一个虚拟计算机,也是 Java 程序能够实现跨平台的基础。它的作用是加载 Java 程序,把字节码翻译成机器码再交由 CPU 执行的一个虚拟计算器。

JVM是如何工作的

首先程序在执行之前先要把 Java 代码(.java)转换成字节码(.class),JVM 通过类加载器(ClassLoader)把字节码加载到内存中,但字节码文件是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine) 将字节码翻译成底层机器码,再交由 CPU 去执行,CPU 执行的过程中需要调用本地库接口(Native Interface)来完成整个程序的运行。

说一下 JVM 的主要组成部分?及其作用?

  • class loader 类加载器:加载类文件到内存。Class loader只管加载,只要符合文件结构就加载,至于能否运行,它不负责,那是有Exectution Engine 负责的。

  • Exectution engine :执行引擎也叫解释器,负责解释命令,交由操作系统执行。

  • native interface:本地库接口。本地接口的作用是融合不同的语言为java所用。它使得在 Java 虚拟机(VM) 内部运行的Java 代码能够与用其它编程语言(如 C、C++ 和汇编语言)编写的应用程序和库进行互操作

  • Runtimedata area 运行数据区:运行数据区是jvm的重点,我们所有所写的程序都被加载到这里,之后才开始运行。

第一部分–java内存模型 (内存区域,jvm运行时数据区)

为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。

java源文件.java会被编译成.class文件,.class文件会被类装载器装载到jvm里,所有的数据都在运行时数据区(我们优化的大部分都在运行时数据区,更多的是堆的优化),就由我们jvm的执行引擎负责执行,执行方法就会在虚拟机栈里边进行方法的依次调用,入栈出栈,这些操作,程序走到哪儿,走到哪一行了,程序计数器在记录
在这里插入图片描述

1. JVM的内存被划分5个区域

  • 堆区、方法区——这两个区域的数据共享
  • 虚拟机栈、本地方法栈、程序计数器——这三个区域的数据私有隔离,不可共享

1.1 堆区

堆是JVM内存占用最大,是被所有线程共享的,其唯一的用途就是存放对象实例:所有的对象实例及数组都在对上进行分配。在实现上也可以是固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是可扩展的,通过 -Xmx 和 -Xms 控制。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError(内存溢出)异常。

1.1.1 堆空间内存分配(默认情况下)

  • 老年代 : 三分之二的堆空间
  • 年轻代(新生代) : 三分之一的堆空间
    • eden区: 8/10 的年轻代空间
    • survivor0(幸存者区) : 1/10 的年轻代空间
    • survivor1 : 1/10 的年轻代空间

新创建的对象需要分配内存,

  1. 先会去新生代进行分配,先看eden区内存够不够,够的话直接分配内存,如果不够进行一次YGC(简单GC),主要清理新生代的空间(比如eden区存了10个对象,其中一个对象还在用,其他9个都没用,就会把这9个清除,剩下的一个会放到幸存者区)
    一次YGC总是要把eden区清理干净,能到幸存者区到幸存者区,不能到的再放到老年代。
  2. 如果一个对象经过多次YGC依然存活,就会被移到老年代,
  3. 如果新生代经过GC后还是放不下,对象就会被放到老年代,如果空间不够,就会进行FGC(全面GC,非常慢,比YGC慢10倍左右),如果还不够,就会报内存溢出异常。
    老年代存的都是大对象或者生命力持久的对象。

性能监控的时候一定要避免我们应用经常性发生fgc的问题

1.2 方法区

方法区也是所有线程共享。方法区(Methed Area)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的字节码等数据。
方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
Jdk1.8之后方法区被元空间取代,元空间使用直接内存

1.3 虚拟机栈

我们通常所说的“方法入栈”、“栈区”其实指代的就是虚拟机栈。

与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

1.3.1 Java 虚拟机栈会出现两种异常:StackOverFlowError (栈溢出)和OutOfMemoryError。

  • StackOverFlowError: 若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
  • OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。

1.3.2 说一下堆栈的区别

  • 功能方面:堆是用来存放对象的,栈是用来执行程序的。

  • 共享性:堆是线程共享的,栈是线程私有的。

  • 空间大小:堆大小远远大于栈。

1.4 本地方法栈

和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

1.5 程序计数器

程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
在虚拟机的概念模型里,字节码解析器的工作是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

由于 JVM 的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,也就是任何时刻,一个处理器(或者说一个内核)都只会执行一条线程中的指令。因此为了线程切换后能恢复到正确的执行位置,每个线程都有独立的程序计数器。

如果线程正在执行 Java 中的方法,程序计数器记录的就是正在执行虚拟机字节码指令的地址,如果是 Native 方法,这个计数器就为空(undefined),因此该内存区域是唯一一个在 Java 虚拟机规范中没有规定 OutOfMemoryError 的区域。

2. Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)

下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。
在这里插入图片描述

2.1 类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

2.2 分配内存

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

内存分配的两种方式:(补充内容,需要掌握)

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的

在这里插入图片描述

2.3 初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

2.4 设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

2.5 执行 init 方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

3. 对象的访问定位的两种方式(句柄和直接指针两种方式)

建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有①使用句柄和②直接指针两种:

  1. 句柄: 如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
    在这里插入图片描述
  2. 直接指针: 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。
    在这里插入图片描述
    这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

第二部分–JVM垃圾回收

简述 java 垃圾回收机制?

在 java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。

在JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

1. 如何判断对象是否死亡(是否可以被回收?GC判定的两种方法)

引用计数法
引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。

可达性分析算法 通过一种GC ROOT的对象,可以作为GC Root根节点的对象有:虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(即一般说的Native方法)引用的对象)来判断,如果有一条链能够到达GC ROOT就说明,对象还在被引用,不能到达GC ROOT就说明对象已经不再被引用,可以回收

2. 引用的分类

  1. 强引用
    发生 gc 的时候不会被回收
    以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
  2. 软引用(SoftReference)
    有用但不是必须的对象,在发生内存溢出之前会被回收
    如果一个对象只具有软引用。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。(例如mybatis)
  3. 弱引用(WeakReference)
    用但不是必须的对象,在下一次GC时会被回收
    如果一个对象只具有弱引用。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
  4. 虚引用(PhantomReference) "虚引用"顾名思义,就是形同虚设。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
    为对象设置虚引用的目的只有一个,就是当着个对象被收集器回收时收到一条系统通知。

特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

3. 如何判断一个常量是废弃常量?

假如在常量池中存在字符串 “abc”,如果当前没有任何String对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,“abc” 就会被系统清理出常量池。

4. 如何判断一个类是无用的类?

判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是“无用的类” :

  1. 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  2. 加载该类的 ClassLoader 已经被回收。
  3. 该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。

5. 垃圾收集有哪些算法?

  1. 标记-清除算法
  2. 复制算法
  3. 标记-整理算法
  4. 分代收集算法

5.1 标记-清除算法

标记-清除算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,效率也很高,但是会带来两个明显的问题:

效率不高
空间问题(标记清除后会产生大量不连续的碎片)
在这里插入图片描述

5.2 复制算法

为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
在这里插入图片描述

5.3 标记-整理算法

根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
在这里插入图片描述

5.4 分代收集算法

根据对象存活周期的不同将内存划分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,所以我们选择“标记-清除”或“标记-整理”算法进行垃圾收集

6. HotSpot为什么要分为新生代和老年代?

将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集

7. 垃圾回收器的分类

这里介绍的是最简单的两种吧,有时间再看其他的

CMS 一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。

G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项

8. 介绍一下CMS,G1收集器(理解不了)

8.1 CMS收集器

CMS(Concurrent Mark Sweep)一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。

8.1.1 CMS 垃圾回收器有哪些优缺点?

CMS 垃圾回收器的优点是使用多线程,标记清除垃圾的,它缺点如下。

  • 对 CPU 资源要求敏感:CMS 回收器过分依赖于多线程环境,默认情况下,开启的线程数为(CPU 的数量 + 3)/ 4,当 CPU 数量少于 4 个时,CMS 对用户本身的操作的影响将会很大,因为要分出一半的运算能力去执行回收器线程;
  • CMS 无法清除浮动垃圾:浮动垃圾指的是 CMS 清除垃圾的时候,还有用户线程产生新的垃圾,这部分未被标记的垃圾叫做“浮动垃圾”,只能在下次 GC 的时候进行清除;
  • CMS 垃圾回收会产生大量空间碎片:CMS 使用的是标记-清除算法,所有在垃圾回收的时候回产生大量的空间碎片。

8.2 G1收集器

G1 垃圾回收器是一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。G1 可以直观的设定停顿时间的目标,相比于 CMS CG,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。

G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 Region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候,G1 的优势更加明显。

9. Minor Gc和Full GC 有什么不同呢?

新生代内存不够用时候发生 MGC,JVM 内存不够的时候发生 FGC

Minor GC:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
Major GC:指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor
GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上。
Full GC清理整个heap区,包括新生代和老年代。

第三部分–类加载机制

简述类加载机制

虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的 java 类型。

1. 简单介绍一下Class类文件结构(常量池主要存放的是那两大常量?Class文件的继承关系是如何确定的?字段表、方法表、属性表主要包含那些信息?)

这个知识的量有点大,等面试如果问的多的话再看吧

2. 简单说说类加载过程

类加载分为以下 5 个步骤:

  1. 加载:通过全限定名来加载生成 class 对象到内存中;
  2. 验证:验证这个 class 文件,包括文件格式校验、元数据验证,字节码校验等。
  3. 准备: 给类中的静态变量分配内存空间;
  4. 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
  5. 初始化:对静态变量和静态代码块执行初始化工作

3. 类加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。

主要有以下三种类加载器(还有一种自定义类加载器,暂时先不了解):

  1. 启动类加载器(Bootstrap ClassLoader)用来加载 java 核心类库,无法被 java 程序直接引用。

  2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。

  3. 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

4. 什么是类加载器双亲委派模型?

双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。

第四部分–虚拟机性能监控和故障处理工具(工具挑一两个看看,主要看看参数吧)

1. 垃圾回收的调优参数有哪些?

垃圾回收的常用调优如下:

  • -Xmx:512 设置最大堆内存为 512 M;
  • -Xms:215 初始堆内存为 215 M;
  • -XX:MaxNewSize 设置最大年轻区内存;
  • -XX:MaxTenuringThreshold=5 设置新生代对象经过 5 次 GC 晋升到老年代;
  • -XX:PretrnureSizeThreshold 设置大对象的值,超过这个值的大对象直接进入老生代;
  • -XX:NewRatio 设置分代垃圾回收器新生代和老生代内存占比;
  • -XX:SurvivorRatio 设置新生代 Eden、Form Survivor、To Survivor 占比。

部分知识引用自:
https://blog.csdn.net/u011552404/article/details/80306316
https://blog.csdn.net/weixin_38896998/article/details/86499993
https://www.cnblogs.com/dailyprogrammer/p/12272769.html
https://www.jianshu.com/p/20ddf72122af
https://zhuanlan.zhihu.com/p/96756501
https://www.imooc.com/article/42827

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值