乐趣区

关于jvm:Java虚拟机JVM面试题2021最新版

Java 内存区域

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

JVM 蕴含两个子系统和两个组件,两个子系统为 Class loader(类装载)、Execution engine(执行引擎);两个组件为 Runtime data area(运行时数据区)、Native Interface(本地接口)。

  • Class loader(类装载):依据给定的全限定名类名 (如:java.lang.Object) 来装载 class 文件到 Runtime data area 中的 method area。
  • Execution engine(执行引擎):执行 classes 中的指令。
  • Native Interface(本地接口):与 native libraries 交互,是其它编程语言交互的接口。
  • Runtime data area(运行时数据区域):这就是咱们常说的 JVM 的内存。

作用:首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的办法区内,而字节码文件只是 JVM 的一套指令集标准,并不能间接交给底层操作系统去执行,因而须要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层零碎指令,再交由 CPU 去执行,而这个过程中须要调用其余语言的本地库接口(Native Interface)来实现整个程序的性能。

上面是 Java 程序运行机制具体阐明

Java 程序运行机制步骤

  • 首先利用 IDE 集成开发工具编写 Java 源代码,源文件的后缀为.java;
  • 再利用编译器 (javac 命令) 将源代码编译成字节码文件,字节码文件的后缀名为.class;
  • 运行字节码的工作是由解释器 (java 命令) 来实现的。

从上图能够看,java 文件通过编译器变成了.class 文件,接下来类加载器又将这些.class 文件加载到 JVM 中。
其实能够一句话来解释:类的加载指的是将类的.class 文件中的二进制数据读入到内存中,将其放在运行时数据区的办法区内,而后在堆区创立一个 java.lang.Class 对象,用来封装类在办法区内的数据结构。

说一下 JVM 运行时数据区

Java 虚拟机在执行 Java 程序的过程中会把它所治理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用处,以及创立和销毁的工夫,有些区域随着虚拟机过程的启动而存在,有些区域则是依赖线程的启动和完结而建设和销毁。Java 虚拟机所治理的内存被划分为如下几个区域:

不同虚拟机的运行时数据区可能稍微有所不同,但都会听从 Java 虚拟机标准,Java 虚拟机标准规定的区域分为以下 5 个局部:

  • 程序计数器(Program Counter Register):以后线程所执行的字节码的行号指示器,字节码解析器的工作是通过扭转这个计数器的值,来选取下一条须要执行的字节码指令,分支、循环、跳转、异样解决、线程复原等根底性能,都须要依赖这个计数器来实现;
  • Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动静链接、办法进口等信息;
  • 本地办法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 办法的,而本地办法栈是为虚拟机调用 Native 办法服务的;
  • Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,简直所有的对象实例都在这里分配内存;
  • 办法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、动态变量、即时编译后的代码等数据。

深拷贝和浅拷贝

浅拷贝(shallowCopy)只是减少了一个指针指向已存在的内存地址,

深拷贝(deepCopy)是减少了一个指针并且申请了一个新的内存,使这个减少的指针指向这个新的内存,

应用深拷贝的状况下,开释内存的时候不会因为呈现浅拷贝时开释同一个内存的谬误。

浅复制:仅仅是指向被复制的内存地址,如果原地址产生扭转,那么浅复制进去的对象也会相应的扭转。

深复制:在计算机中开拓一块 新的内存地址 用于寄存复制的对象。

说一下堆栈的区别?

物理地址

堆的物理地址调配对对象是不间断的。因而性能慢些。在 GC 的时候也要思考到不间断的调配,所以有各种算法。比方,标记 - 打消,复制,标记 - 压缩,分代(即新生代应用复制算法,老年代应用标记——压缩)

栈应用的是数据结构中的栈,先进后出的准则,物理地址调配是间断的。所以性能快。

内存别离

堆因为是不间断的,所以调配的内存是在 运行期 确认的,因而大小不固定。个别堆大小远远大于栈。

栈是间断的,所以调配的内存大小要在 编译期 就确认,大小是固定的。

寄存的内容

堆寄存的是对象的实例和数组。因而该区更关注的是数据的存储

栈寄存:局部变量,操作数栈,返回后果。该区更关注的是程序办法的执行。

PS:

  1. 动态变量放在办法区
  2. 动态的对象还是放在堆。

程序的可见度

堆对于整个应用程序都是共享、可见的。

栈只对于线程是可见的。所以也是线程公有。他的生命周期和线程雷同。

队列和栈是什么?有什么区别?

队列和栈都是被用来预存储数据的。

  • 操作的名称不同。队列的插入称为入队,队列的删除称为出队。栈的插入称为进栈,栈的删除称为出栈。
  • 可操作的形式不同。队列是在队尾入队,队头出队,即两边都可操作。而栈的进栈和出栈都是在栈顶进行的,无奈对栈底间接进行操作。
  • 操作的办法不同。队列是先进先出(FIFO),即队列的批改是依先进先出的准则进行的。新来的成员总是退出队尾(不能从两头插入),每次来到的成员总是队列头上(不容许中途归队)。而栈为后进先出(LIFO), 即每次删除(出栈)的总是以后栈中最新的元素,即最初插入(进栈)的元素,而最先插入的被放在栈的底部,要到最初能力删除。

HotSpot 虚拟机对象探秘

对象的创立

说到对象的创立,首先让咱们看看 Java 中提供的几种对象创立形式:

Header 解释
应用 new 关键字 调用了构造函数
应用 Class 的 newInstance 办法 调用了构造函数
应用 Constructor 类的 newInstance 办法 调用了构造函数
应用 clone 办法 没有调用构造函数
应用反序列化 没有调用构造函数

上面是对象创立的次要流程:

虚拟机遇到一条 new 指令时,先查看常量池是否曾经加载相应的类,如果没有,必须先执行相应的类加载。类加载通过后,接下来分配内存。若 Java 堆中内存是相对规整的,应用“指针碰撞“形式分配内存;如果不是规整的,就从闲暇列表中调配,叫做”闲暇列表“形式。划分内存时还须要思考一个问题 - 并发,也有两种形式: CAS 同步解决,或者本地线程调配缓冲 (Thread Local Allocation Buffer, TLAB)。而后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最初执行<init> 办法。

为对象分配内存

类加载实现后,接着会在 Java 堆中划分一块内存调配给对象。内存调配依据 Java 堆是否规整,有两种形式:

  • 指针碰撞:如果 Java 堆的内存是规整,即所有用过的内存放在一边,而闲暇的的放在另一边。分配内存时将位于两头的指针指示器向闲暇的内存挪动一段与对象大小相等的间隔,这样便实现分配内存工作。
  • 闲暇列表:如果 Java 堆的内存不是规整的,则须要由虚拟机保护一个列表来记录那些内存是可用的,这样在调配的时候能够从列表中查问到足够大的内存调配给对象,并在调配后更新列表记录。

抉择哪种调配形式是由 Java 堆是否规整来决定的,而 Java 堆是否规整又由所采纳的垃圾收集器是否带有压缩整顿性能决定。

解决并发平安问题

对象的创立在虚拟机中是一个十分频繁的行为,哪怕只是批改一个指针所指向的地位,在并发状况下也是不平安的,可能呈现正在给对象 A 分配内存,指针还没来得及批改,对象 B 又同时应用了原来的指针来分配内存的状况。解决这个问题有两种计划:

  • 对分配内存空间的动作进行同步解决(采纳 CAS + 失败重试来保障更新操作的原子性);
  • 把内存调配的动作依照线程划分在不同的空间之中进行,即每个线程在 Java 堆中事后调配一小块内存,称为本地线程调配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上调配。只有 TLAB 用完并调配新的 TLAB 时,才须要同步锁。通过 -XX:+/-UserTLAB 参数来设定虚拟机是否应用 TLAB。

对象的拜访定位

Java程序须要通过 JVM 栈上的援用拜访堆中的具体对象。对象的拜访形式取决于 JVM 虚拟机的实现。目前支流的拜访形式有 句柄 间接指针 两种形式。

指针: 指向对象,代表一个对象在内存中的起始地址。

句柄: 能够了解为指向指针的指针,保护着对象的指针。句柄不间接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的实在内存地址。

句柄拜访

Java堆中划分出一块内存来作为 句柄池 ,援用中存储对象的 句柄地址 ,而句柄中蕴含了 对象实例数据 对象类型数据 各自的 具体地址 信息,具体结构如下图所示:

劣势 :援用中存储的是 稳固 的句柄地址,在对象被挪动(垃圾收集时挪动对象是十分广泛的行为)时只会扭转 句柄中 实例数据指针 ,而 援用 自身不须要批改。

间接指针

如果应用 间接指针 拜访,援用 中存储的间接就是 对象地址 ,那么Java 堆对象外部的布局中就必须思考如何搁置拜访 类型数据 的相干信息。

劣势 :速度更,节俭了 一次指针定位 的工夫开销。因为对象的拜访在 Java 中十分频繁,因而这类开销千里之行; 始于足下后也是十分可观的执行老本。HotSpot 中采纳的就是这种形式。

内存溢出异样

Java 会存在内存透露吗?请简略形容

内存透露是指不再被应用的对象或者变量始终被占据在内存中。实践上来说,Java 是有 GC 垃圾回收机制的,也就是说,不再被应用的对象,会被 GC 主动回收掉,主动从内存中革除。

然而,即便这样,Java 也还是存在着内存透露的状况,java 导致内存泄露的起因很明确:长生命周期的对象持有短生命周期对象的援用就很可能产生内存泄露,只管短生命周期对象曾经不再须要,然而因为长生命周期对象持有它的援用而导致不能被回收,这就是 java 中内存泄露的产生场景。

垃圾收集器

简述 Java 垃圾回收机制

在 java 中,程序员是不须要显示的去开释一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有一个垃圾回收线程,它是低优先级的,在失常状况下是不会执行的,只有在虚拟机闲暇或者以后堆内存不足时,才会触发执行,扫面那些没有被任何援用的对象,并将它们增加到要回收的汇合中,进行回收。

GC 是什么?为什么要 GC

GC 是垃圾收集的意思(Gabage Collection), 内存解决是编程人员容易呈现问题的中央,遗记或者谬误的内存

回收会导致程序或零碎的不稳固甚至解体,Java 提供的 GC 性能能够主动监测对象是否超过作用域从而达到主动

回收内存的目标,Java 语言没有提供开释已分配内存的显示操作方法。

垃圾回收的长处和原理。并思考 2 种回收机制

java 语言最显著的特点就是引入了垃圾回收机制,它使 java 程序员在编写程序时不再思考内存治理的问题。

因为有这个垃圾回收机制,java 中的对象不再有“作用域”的概念,只有援用的对象才有“作用域”。

垃圾回收机制无效的避免了内存泄露,能够无效的应用可应用的内存。

垃圾回收器通常作为一个独自的低级别的线程运行,在不可预知的状况下对内存堆中曾经死亡的或很长时间没有用过的对象进行革除和回收。

程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。

垃圾回收有分代复制垃圾回收、标记垃圾回收、增量垃圾回收。

垃圾回收器的基本原理是什么?垃圾回收器能够马上回收内存吗?有什么方法被动告诉虚拟机进行垃圾回收?

对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及应用状况。

通常,GC 采纳有向图的形式记录和治理堆 (heap) 中的所有对象。通过这种形式确定哪些对象是 ” 可达的 ”,哪些对象是 ” 不可达的 ”。当 GC 确定一些对象为 ” 不可达 ” 时,GC 就有责任回收这些内存空间。

能够。程序员能够手动执行 System.gc(),告诉 GC 运行,然而 Java 语言标准并不保障 GC 肯定会执行。

Java 中都有哪些援用类型?

  • 强援用:产生 gc 的时候不会被回收。
  • 软援用:有用但不是必须的对象,在产生内存溢出之前会被回收。
  • 弱援用:有用但不是必须的对象,在下一次 GC 时会被回收。
  • 虚援用(幽灵援用 / 幻影援用):无奈通过虚援用取得对象,用 PhantomReference 实现虚援用,虚援用的用处是在 gc 时返回一个告诉。

怎么判断对象是否能够被回收?

垃圾收集器在做垃圾回收的时候,首先须要断定的就是哪些内存是须要被回收的,哪些对象是「存活」的,是不能够被回收的;哪些对象曾经「死掉」了,须要被回收。

个别有两种办法来判断:

  • 援用计数器法:为每个对象创立一个援用计数,有对象援用时计数器 +1,援用被开释时计数 -1,当计数器为 0 时就能够被回收。它有一个毛病不能解决循环援用的问题;
  • 可达性剖析算法:从 GC Roots 开始向下搜寻,搜寻所走过的门路称为援用链。当一个对象到 GC Roots 没有任何援用链相连时,则证实此对象是能够被回收的。

在 Java 中,对象什么时候能够被垃圾回收

当对象对以后应用这个对象的应用程序变得不可涉及的时候,这个对象就能够被回收了。
垃圾回收不会产生在永恒代,如果永恒代满了或者是超过了临界值,会触发齐全垃圾回收(Full GC)。如果你认真查看垃圾收集器的输入信息,就会发现永恒代也是被回收的。这就是为什么正确的永恒代大小对防止 Full GC 是十分重要的起因。

JVM 中的永恒代中会产生垃圾回收吗

垃圾回收不会产生在永恒代,如果永恒代满了或者是超过了临界值,会触发齐全垃圾回收(Full GC)。如果你认真查看垃圾收集器的输入信息,就会发现永恒代也是被回收的。这就是为什么正确的永恒代大小对防止 Full GC 是十分重要的起因。请参考下 Java8:从永恒代到元数据区
(译者注:Java8 中曾经移除了永恒代,新加了一个叫做元数据区的 native 内存区)

说一下 JVM 有哪些垃圾回收算法?

  • 标记 - 革除算法:标记无用对象,而后进行革除回收。毛病:效率不高,无奈革除垃圾碎片。
  • 复制算法:依照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,而后再把已应用的内存空间一次清理掉。毛病:内存使用率不高,只有原来的一半。
  • 标记 - 整顿算法:标记无用对象,让所有存活的对象都向一端挪动,而后间接革除掉端边界以外的内存。
  • 分代算法:依据对象存活周期的不同将内存划分为几块,个别是新生代和老年代,新生代根本采纳复制算法,老年代采纳标记整顿算法。

标记 - 革除算法

标记无用对象,而后进行革除回收。

标记 - 革除算法(Mark-Sweep)是一种常见的根底垃圾收集算法,它将垃圾收集分为两个阶段:

  • 标记阶段:标记出能够回收的对象。
  • 革除阶段:回收被标记的对象所占用的空间。

标记 - 革除算法之所以是根底的,是因为前面讲到的垃圾收集算法都是在此算法的根底上进行改良的。

长处:实现简略,不须要对象进行挪动。

毛病:标记、革除过程效率低,产生大量不间断的内存碎片,进步了垃圾回收的频率。

标记 - 革除算法的执行的过程如下图所示

复制算法

为了解决标记 - 革除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只应用其中一个区域。垃圾收集时,遍历以后应用的区域,把存活对象复制到另外一个区域中,最初将以后应用的区域的可回收的对象进行回收。

长处:按程序分配内存即可,实现简略、运行高效,不必思考内存碎片。

毛病:可用的内存大小放大为原来的一半,对象存活率高时会频繁进行复制。

复制算法的执行过程如下图所示

标记 - 整顿算法

在新生代中能够应用复制算法,然而在老年代就不能抉择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记 - 革除算法能够利用在老年代中,然而它效率不高,在内存回收后容易产生大量内存碎片。因而就呈现了一种标记 - 整顿算法(Mark-Compact)算法,与标记 - 整顿算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,而后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。

长处:解决了标记 - 清理算法存在的内存碎片问题。

毛病:仍须要进行部分对象挪动,肯定水平上升高了效率。

标记 - 整顿算法的执行过程如下图所示

分代收集算法

以后商业虚拟机都采纳 分代收集 的垃圾收集算法。分代收集算法,顾名思义是依据对象的 存活周期 将内存划分为几块。个别包含 年老代 老年代 永恒代,如图所示:

说一下 JVM 有哪些垃圾回收器?

如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展现了 7 种作用于不同分代的收集器,其中用于回收新生代的收集器包含 Serial、PraNew、Parallel Scavenge,回收老年代的收集器包含 Serial Old、Parallel Old、CMS,还有用于回收整个 Java 堆的 G1 收集器。不同收集器之间的连线示意它们能够搭配应用。

  • Serial 收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,长处是简略高效;
  • ParNew 收集器 (复制算法): 新生代收并行集器,实际上是 Serial 收集器的多线程版本,在多核 CPU 环境下有着比 Serial 更好的体现;
  • Parallel Scavenge 收集器 (复制算法): 新生代并行收集器,谋求高吞吐量,高效利用 CPU。吞吐量 = 用户线程工夫 /(用户线程工夫 +GC 线程工夫),高吞吐量能够高效率的利用 CPU 工夫,尽快实现程序的运算工作,适宜后盾利用等对交互相应要求不高的场景;
  • Serial Old 收集器 (标记 - 整顿算法): 老年代单线程收集器,Serial 收集器的老年代版本;
  • Parallel Old 收集器 (标记 - 整顿算法):老年代并行收集器,吞吐量优先,Parallel Scavenge 收集器的老年代版本;
  • CMS(Concurrent Mark Sweep)收集器(标记 - 革除算法):老年代并行收集器,以获取最短回收进展工夫为指标的收集器,具备高并发、低进展的特点,谋求最短 GC 回收进展工夫。
  • G1(Garbage First)收集器 (标记 - 整顿算法):Java 堆并行收集器,G1 收集器是 JDK1.7 提供的一个新收集器,G1 收集器基于“标记 - 整顿”算法实现,也就是说不会产生内存碎片。此外,G1 收集器不同于之前的收集器的一个重要特点是:G1 回收的范畴是整个 Java 堆(包含新生代,老年代),而前六种收集器回收的范畴仅限于新生代或老年代。

具体介绍一下 CMS 垃圾回收器?

CMS 是英文 Concurrent Mark-Sweep 的简称,是以就义吞吐量为代价来取得最短回收进展工夫的垃圾回收器。对于要求服务器响应速度的利用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定应用 CMS 垃圾回收器。

CMS 应用的是标记 - 革除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当残余内存不能满足程序运行要求时,零碎将会呈现 Concurrent Mode Failure,长期 CMS 会采纳 Serial Old 回收器进行垃圾革除,此时的性能将会被升高。

新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1

新生代垃圾回收器个别采纳的是复制算法,复制算法的长处是效率高,毛病是内存利用率低;老年代回收器个别采纳的是标记 - 整顿的算法进行垃圾回收。

简述分代垃圾回收器是怎么工作的?

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

新生代应用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

  • 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
  • 清空 Eden 和 From Survivor 分区;
  • From Survivor 和 To Survivor 分区替换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

每次在 From Survivor 到 To Survivor 挪动时都存活的对象,年龄就 +1,当年龄达到 15(默认配置是 15)时,降级为老生代。大对象也会间接进入老生代。

老生代当空间占用达到某个值之后就会触发全局垃圾发出,个别应用标记整顿的执行算法。以上这些周而复始就形成了整个分代垃圾回收的整体执行流程。

内存调配策略

简述 java 内存调配与回收策率以及 Minor GC 和 Major GC

所谓主动内存治理,最终要解决的也就是内存调配和内存回收两个问题。后面咱们介绍了内存回收,这里咱们再来聊聊内存调配。

对象的内存调配通常是在 Java 堆上调配(随着虚拟机优化技术的诞生,某些场景下也会在栈上调配,前面会具体介绍),对象次要调配在新生代的 Eden 区,如果启动了本地线程缓冲,将依照线程优先在 TLAB 上调配。多数状况下也会间接在老年代上调配。总的来说调配规定不是百分百固定的,其细节取决于哪一种垃圾收集器组合以及虚拟机相干参数无关,然而虚拟机对于内存的调配还是会遵循以下几种「普世」规定:

对象优先在 Eden 区调配

少数状况,对象都在新生代 Eden 区调配。当 Eden 区调配没有足够的空间进行调配时,虚拟机将会发动一次 Minor GC。如果本次 GC 后还是没有足够的空间,则将启用调配担保机制在老年代中分配内存。

这里咱们提到 Minor GC,如果你仔细观察过 GC 日常,通常咱们还能从日志中发现 Major GC/Full GC。

  • Minor GC 是指产生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 十分频繁,个别回收速度也十分快;
  • Major GC/Full GC 是指产生在老年代的 GC,呈现了 Major GC 通常会随同至多一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。

大对象间接进入老年代

所谓大对象是指须要大量间断内存空间的对象,频繁呈现大对象是致命的,会导致在内存还有不少空间的状况下提前触发 GC 以获取足够的间断空间来安置新对象。

后面咱们介绍过新生代应用的是标记 - 革除算法来解决垃圾回收的,如果大对象间接在新生代调配就会导致 Eden 区和两个 Survivor 区之间产生大量的内存复制。因而对于大对象都会间接在老年代进行调配。

长期存活对象将进入老年代

虚拟机采纳分代收集的思维来治理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因而虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在 Eden 区出世,并且可能被 Survivor 包容,将被挪动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到肯定水平(默认 15)就会被降职到老年代。

虚拟机类加载机制

简述 java 类加载机制?

虚拟机把形容类的数据从 Class 文件加载到内存,并对数据进行校验,解析和初始化,最终造成能够被虚拟机间接应用的 java 类型。

形容一下 JVM 加载 Class 文件的原理机制

Java 中的所有类,都须要由类加载器装载到 JVM 中能力运行。类加载器自身也是一个类,而它的工作就是把 class 文件从硬盘读取到内存中。在写程序的时候,咱们简直不须要关怀类的加载,因为这些都是隐式装载的,除非咱们有非凡的用法,像是反射,就须要显式的加载所须要的类。

类装载形式,有两种:

1. 隐式装载,程序在运行过程中当碰到通过 new 等形式生成对象时,隐式调用类装载器加载对应的类到 jvm 中,

2. 显式装载,通过 class.forname()等办法,显式加载须要的类

Java 类的加载是动静的,它并不会一次性将所有类全副加载后再运行,而是保障程序运行的根底类 (像是基类) 齐全加载到 jvm 中,至于其余类,则在须要的时候才加载。这当然就是为了节俭内存开销。

什么是类加载器,类加载器有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

次要有一下四品种加载器:

  1. 启动类加载器 (Bootstrap ClassLoader) 用来加载 java 外围类库,无奈被 java 程序间接援用。
  2. 扩大类加载器(extensions class loader): 它用来加载 Java 的扩大库。Java 虚拟机的实现会提供一个扩大库目录。该类加载器在此目录外面查找并加载 Java 类。
  3. 零碎类加载器(system class loader):它依据 Java 利用的类门路(CLASSPATH)来加载 Java 类。一般来说,Java 利用的类都是由它来实现加载的。能够通过 ClassLoader.getSystemClassLoader()来获取它。
  4. 用户自定义类加载器,通过继承 java.lang.ClassLoader 类的形式实现。

说一下类装载的执行过程?

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

  • 加载:依据查找门路找到相应的 class 文件而后导入;
  • 验证:查看加载的 class 文件的正确性;
  • 筹备:给类中的动态变量分配内存空间;
  • 解析:虚拟机将常量池中的符号援用替换成间接援用的过程。符号援用就了解为一个标示,而在间接援用间接指向内存中的地址;
  • 初始化:对动态变量和动态代码块执行初始化工作。

什么是双亲委派模型?

在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都须要由加载它的类加载器和这个类自身一起确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是依据指定全限定名称将 class 文件加载到 JVM 内存,而后再转化为 class 对象。

类加载器分类:

  • 启动类加载器(Bootstrap ClassLoader),是虚拟机本身的一部分,用来加载 Java_HOME/lib/ 目录中的,或者被 -Xbootclasspath 参数所指定的门路中并且被虚拟机辨认的类库;
  • 其余类加载器:
  • 扩大类加载器(Extension ClassLoader):负责加载 \lib\ext 目录或 Java. ext. dirs 零碎变量指定的门路中的所有类库;
  • 应用程序类加载器(Application ClassLoader)。负责加载用户类门路(classpath)上的指定类库,咱们能够间接应用这个类加载器。个别状况,如果咱们没有自定义类加载器默认就是用这个加载器。

双亲委派模型:如果一个类加载器收到了类加载的申请,它首先不会本人去加载这个类,而是把这个申请委派给父类加载器去实现,每一层的类加载器都是如此,这样所有的加载申请都会被传送到顶层的启动类加载器中,只有当父加载无奈实现加载申请(它的搜寻范畴中没找到所需的类)时,子加载器才会尝试去加载类。

当一个类收到了类加载申请时,不会本人先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去实现类的加载。

JVM 调优

说一下 JVM 调优的工具?

JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最罕用的是 jconsole 和 jvisualvm 这两款视图监控工具。

  • jconsole:用于对 JVM 中的内存、线程和类等进行监控;
  • jvisualvm:JDK 自带的全能剖析工具,能够剖析:内存快照、线程快照、程序死锁、监控内存的变动、gc 变动等。

罕用的 JVM 调优的参数都有哪些?

  • -Xms2g:初始化推大小为 2g;
  • -Xmx2g:堆最大内存为 2g;
  • -XX:NewRatio=4:设置年老的和老年代的内存比例为 1:4;
  • -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
  • –XX:+UseParNewGC:指定应用 ParNew + Serial Old 垃圾回收器组合;
  • -XX:+UseParallelOldGC:指定应用 ParNew + ParNew Old 垃圾回收器组合;
  • -XX:+UseConcMarkSweepGC:指定应用 CMS + Serial Old 垃圾回收器组合;
  • -XX:+PrintGC:开启打印 gc 信息;
  • -XX:+PrintGCDetails:打印 gc 详细信息。

作者:ThinkWon
起源:https://thinkwon.blog.csdn.ne…

退出移动版