一、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程序运行机制详细说明
首先利用IDE集成开发工具编写Java源代码,源文件的后缀为.java;
再利用编译器(javac命令)将源代码编译成字节码文件,字节码文件的后缀名 为.class;
运行字节码的工作是由解释器(java命令)来完成的。

从上图可以看,java文件通过编译器变成了.class文件,接下来类加载器又将这 些.class文件加载到JVM 中。
一句话解释:类的加载指的是将类的.class文件中的二进制数据读入 到内存中,将其放在运 行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结 构。
二、说一下JVM 运行时数据区
**Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个 不同的数据区域。**这些区域都有各自的用途,以及创建和销毁的时间,有些区域 随着虚拟机进程的启动而存在,有些区域则是依 赖线程的启动和结束而建立和销 毁。Java 虚拟机所管理的内存被划分为如下几个区域:

不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范, Java 虚拟机规范规定的 区域分为以下 5 个部分:
01、程序计数器:当前线程所执行的字节码的行号 指示器,字节码解 析器的工作是通过改变这个计数器的值,来选取下一条需要执行的 字节码指令,分支、循环、跳 转、异常处理、线程恢复等基础功能,都需要依赖这个 计数器来完成;
02、Java 虚拟机栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
03、本地方法栈:与虚拟机栈的作用是一样的,只不过虚 拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
04、Java 堆:Java 虚拟机中内存大的一块,是被所有线程共享 的,几乎所有的对象实例 都在这里分配内存;
05、方法区:用于存储已被虚拟机加载的类信息、常量、静态变 量、即时编译后的代 码等数据。
三、深拷贝和浅拷贝
浅拷贝只是增加了一个指针指向已存在的内存地址。
深拷贝是增加了一个指针并且申请了一个新的内存,使这个增加 的指针指向这个新的内存。
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来 的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
四、说一下堆栈的区别?
物理地址
堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到 不连续的分配,所以有 各种算法。比如,标记-消除,复制,标记-压缩,分代 (即新生代使用复制算法,老年代使用标记------ 压缩) 栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
内存分别
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般 堆大小远远大于栈。栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
存放的内容
堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。
PS:
-
静态变量放在方法区。
-
静态的对象还是放在堆。
程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
五、队列和栈是什么?有什么区别?
队列和栈都是被用来预存储数据的。
01、操作的名称不同。队列的插入称为入队,队列的删除称为出队。栈的插入称为进 栈,栈的删除称为 出栈。
02、可操作的方式不同。队列是在队尾入队,队头出队,即两边都可操作。而栈的进 栈和出栈都是在栈 顶进行的,无法对栈底直接进行操作。
03、操作的方法不同。队列是先进先出(FIFO),即队列的修改是依先进先出的原 则进行的。新来的 成员总是加入队尾(不能从中间插入),每次离开的成员总是队列 头上(不允许中途离队)。而栈 为后进先出(LIFO),即每次删除(出栈)的总是当 前栈中新的元素,即后插入(进栈)的元素, 而先插入的被放在栈的底部,要到后才能删除。
六、内存溢出异常 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 中都有哪些引用类型?
1、强引用:发生 gc 的时候不会被回收。
2、软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
3、弱引用:有用但不是必须的对象,在下一次GC时会被回收。
4、虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。
十二、怎么判断对象是否可以被回收?
垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是「存活」 的,是不可以被回收的;哪些对象已经「死掉」了,需要被回收。
一般有两种方法来判断:
01、引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用 被释放时计数 -1, 当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用 的问题;
02、可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。 当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
十三、说一下 JVM 有哪些垃圾回收算法?
01、标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清 除垃圾碎片。
02、复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的 对象复制到另一块 上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不 高,只有原来的一半。
03、标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清 除掉端边界以外的 内存。
04、分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年 代,新生代基本采用 复制算法,老年代采用标记整理算法。
1、标记-清除算法
标记无用对象,然后进行清除回收。
标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收 集分为两个阶段:
标记阶段:标记出可以回收的对象。
清除阶段:回收被标记的对象所占用的空间。
标记-清除算法之所以是基础的,是因为后面讲到的垃圾收集算法都是在此算法 的基础上进行改进的。
**优点:**实现简单,不需要对象进行移动。
**缺点:**标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。
2、复制算法
为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划 为两个相等的区域,每次 只使用其中一个区域。垃圾收集时,遍历当前使用的区 域,把存活对象复制到另外一个区域中,最后将 当前使用的区域的可回收的对象 进行回收。
优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
缺点:可用的内存大小缩小为 原来的一半,对象存活率高时会频繁进行复制。
3、标记-整理算法
在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年 代的对象存活率会较 高,这样会有较多的复制操作,导致效率变低。标记-清除 算法可以应用在老年代中,但是它效率不高, 在内存回收后容易产生大量内存碎 片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标 记-整理 算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使 他们紧凑的排 列在一起,然后对端边界以外的内存进行回收。回收后,已用和未 用的内存都各自一边。
优点:解决了标记-清理算法存在的内存碎片问题。
缺点:仍需要进行局部对象移动,一定程度上降低了效率。
4、分代收集算法
当前商业虚拟机都采用分代收集 的垃圾收集算法。分代收集算法,顾名思义是根据对象的存活周期将内存划分为几块。一般包括年轻代 、老年代 和永久代。

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

1、Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点 是简单高效;
2、ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程 版本,在多核CPU 环境下有着比Serial更好的表现;
3、Parallel Scavenge收集器(复制算法): 新生代并行收集器,追求高吞吐量,高效 利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高 效率的利用CPU时间,尽快完成程 序的运算任务,适合后台应用等对交互相应要求不 高的场景;
4、Serial Old收集器(标记-整理算法): 老年代单线程收集器,Serial收集器的老年 代版本;
5、Parallel Old收集器(标记-整理算法): 老年代并行收集器,吞吐量优先, Parallel Scavenge收集器 的老年代版本;
6、CMS收集器(标记-清除算法): 老年代并行收集 器,以获取最短回收 停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最 短GC回收停顿时间。
7、G1收集器 (标记-整理算法): Java堆并行收集器,G1收集器是 JDK1.7提供的一个新 收集器,G1收集器基于"标记-整理"算法实现,也就是说不会 产生内存碎片。此外,G1收集器不同 于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种 收集器回收的范围仅限于新生代 或老年代。
十五、详细介绍一下 CMS 垃圾回收器?
CMS 是以牺牲吞吐量 为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。
在启动 JVM 的参数加上"- XX:+UseConcMarkSweepGC"来指定使用 CMS 垃圾回收器。 CMS 使用的是标记-清除的算法实现的, 所以在GC的时候回产生大量的内存碎 片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
十六、新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么 区别?
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器 一般采用的是复制算法,复制算法的优点是效率高,缺点是内 存利用率低;
老年代回收器 一般采用的是标记-整理的算法进行垃圾回收。
十七、什么是双亲委派模型?
在介绍双亲委派模型 之前先说下类加载器。对于任意一个类,都需要由加载它的 类加载器和这个类本身 一同确立在 JVM 中的唯一性,每一个类加载器,都有一 个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。

类加载器分类:
**启动类加载器:**是虚拟机自身的一部分,用来加载 Java_HOME/lib/目 录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚 拟机识别的类库;
其他类加载器:
01、扩展类加载器:负责加载\lib\ext目录或Java. ext. dirs系统变量指 定的路径中的所有类库;
02、**应用程序类加载器:**负责加载用户类路径 (classpath)上的指 定类库,我们可以直接使用这个类加载器。一般情况,如果我 们没有自定义类加载器默认就 是用这个加载器。
双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
十八、说一下 JVM 调优的工具?
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视 图监控工具。
jconsole:用于对 JVM 中的内存、线程和类等进行监控;
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序 死锁、监控内存的 变化、gc 变化等。
十九、常用的 JVM 调优的参数都有哪些?
01、-Xms2g:初始化推大小为 2g;
02、-Xmx2g:堆最大内存为 2g;
03、-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4; -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2; --XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器 组合; -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合。
04、-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组 合;
05、-XX:+PrintGC:开启打印 gc 信息;
06、-XX:+PrintGCDetails:打印 gc 详细信息。