jvm面试题汇总

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1.说一说jvm的主要组成部分

JVM包含两个子系统和两个组件两个子系统为Class loader(类装载)、Execution engine(执行引擎)两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。
类的加载器将编译器生成的字节码文件加载到运行时数据方法区中只要符合文件结构就加载至于能否运行它不负责而是由执行引擎负责
执行引擎也叫解释器负责执行命令交由操作系统执行。
本地接口融合其它语言为Java所用
运行时时数据区
1.堆Java对象的存储区域用new字段分配的Java实例和数组都被分配在堆上Java7后运行时常量池从方法区上移到了堆上。
2.方法区和堆一样都是所有线程共享的主要存储的是类信息静态变量常量即时编译器编译后的代码等数据。
3.虚拟机栈虚拟机中执行每个方法的时候都会创建一个栈帧用于存储局部变量操作数栈动态链接方法出口等信息。
4本地方法栈与虚拟机栈类似但是使用的是native方法
5.程序计数器指示虚拟机下一条执行的字节码指令。
流程首先编译器将代码编译成字节码文件类加载器利用全类名将字节码文件加载带运行时数据区的方法区字节码只是jvm的一套指令规范操作系统不能识别所以就需要执行引擎将字节码翻译成底层系统指令交给cpu去执行。在这个过程中还需要调用其它语言的本地接口。

2.说一下堆栈的区别

堆主要用于存储实例化的对象数组由jvm动态的分配内存一个jvm只有一个堆内存线程是可以共享数据的物理地址是不连续的内存大小是运行时决定的
栈主要用于存储局部变量和对象的引用每个线程都会有一个独立的栈空间线程之间不共享数据。物理地址是连续的内存大小是编译时确定的

3.Java的内存泄露

内存泄漏指的是JVM中某些不再需要使用的对象仍然存活于JVM中而不能及时释放而导致内存空间的浪费。
Java中我们可能会遇到栈内存泄露和堆内存泄漏。
其中堆内存泄漏是由于创建后的对象一直存在于堆中不再需要的对象其引用一直没有被移除。这些无用的对象会慢慢占用内存最后导致内存溢出。
栈内存泄漏由于方法不断被调用但是一直没有退出方法。这种情况可能发生在无限循环或递归掉用时最终导致栈内存溢出。

4.内存泄漏的原因

Java中内存泄漏主要是因为不能正确释放不需要的资源长生命周期对象持有短生命周期对象的引用。
静态字段
静态字段引起的内存泄漏比较常见如果某个不需要的类中含有静态字段那么就会造成内存泄漏。单例模式中如果持有其他的类引用就会造成内存泄漏静态集合如HashMapLinkedList等持有的一些对象没有及时释放等。
Thread Local
threadlocal引用一个对象使用完成后并没有被及时remove掉线程一直存活的情况下使用线程池时就会发生内存泄漏。
大多时候内存泄漏都是由于开发人员的代码错误导致的要防止这种内存泄漏就需要编写必要的代码来配合垃圾回收器释放资源。

5.实践中如何避免Java内存泄漏

使用最新稳定版本的Java
尽量减少使用静态变量使用完之后及时赋值 null移除引用
明确对象的有效作用域尽量缩小对象的作用域。局部变量回收会很快。
减少长生命周期对象持有短生命周期的引用
各种连接应该及时关闭数据库连接网络IO等
使用内存泄漏检测工具如MAT,Visual VMjprofile 等
避免在代码中使用System.gc()
避免使用内部类
内存泄漏很难定位并修复但是我们可以遵循以下几个步骤去定位并修复

6.定位并修复内存泄漏

确定是否存在内存泄漏启用详细的GC跟踪。
使用一些第三方插件进行分析jprofile Visual VM等
检查调用堆栈是否有未释放的引用分析GC状态
找出对象没有被垃圾回收的原因
编写代码手动删除此类对象

7.GC如何判断一个对象是否为垃圾

1.引用计数法
主要是查看该对象是否还有引用指向它如果有则说明该对象不是垃圾反之则为垃圾。
具体就是给一个对象上标一个数字用来记录有多少个引用指向了该对象当这个数字记录为0时那就表示这个对象已经没有引用指向它了那么这个对象就变成了垃圾。存在问题循环引用
2.根可达算法
基本思路就是通过一系列名为”GC Roots”的对象作为起始点从这些节点开始向下搜索搜索所走过的路径称为引用链(Reference Chain)当一个对象到GC Roots没有任何引用链相连时则证明此对象是不可用的。

8.GC Roots如何选取

在Java语言中可以作为GCRoots的对象包括下面几种
(1). 虚拟机栈栈帧中的局部变量区也叫做局部变量表中引用的对象。
(2). 方法区中的类静态属性引用的对象。
(3). 方法区中常量引用的对象。
(4). 本地方法栈中JNI(Native方法)引用的对象。

9.jvm有哪些垃圾回收算法

1.标记-清除算法
标记-清除算法对根集合进行扫描对存活的对象进行标记。标记完成后再对整个空间内未被标记的对象扫描进行回收。
优点
实现简单不需要进行对象进行移动。
缺点
标记、清除过程效率低产生大量不连续的内存碎片提高了垃圾回收的频率。
2.复制算法
这种收集算法解决了标记清除算法存在的效率问题。它将内存区域划分成相同的两个内存块。每次仅使用一半的空间JVM生成的新对象放在一半空间中。当一半空间用完时进行GC把可到达对象复制到另一半空间然后把使用过的内存空间一次清理掉。
优点
按顺序分配内存即可实现简单、运行高效不用考虑内存碎片。
缺点
可用的内存大小缩小为原来的一半对象存活率高时会频繁进行复制。
3.标记-整理算法
标记-整理算法 采用和 标记-清除算法 一样的方式进行对象的标记但后续不直接对可回收对象进行清理而是将所有的存活对象往一端空闲空间移动然后清理掉端边界以外的内存空间。
-优点
解决了标记-清理算法存在的内存碎片问题。
缺点
仍需要进行局部对象移动一定程度上降低了效率。
3.分代收集算法
绝大多数最新被创建的对象会被分配到这里由于大部分对象在创建后会很快变得不可达所以很多对象被创建在新生代然后消失。对象从这个区域消失的过程我们称之为 minor GC。
新生代 中存在一个Eden区和两个Survivor区。新对象会首先分配在Eden中如果新对象过大会直接分配在老年代中。在GC中Eden中的对象会被移动到Survivor中直至对象满足一定的年纪定义为熬过GC的次数会被移动到老年代。
可以设置新生代和老年代的相对大小。这种方式的优点是新生代大小会随着整个堆大小动态扩展。参数 -XX:NewRatio 设置老年代与新生代的比例。例如 -XX:NewRatio=8 指定 老年代/新生代 为8/1. 老年代 占堆大小的 7/8 新生代 占堆大小的 1/8默认即是 1/8。
老年代Old generation
对象没有变得不可达并且从新生代中存活下来会被拷贝到这里。其所占用的空间要比新生代多。也正由于其相对较大的空间发生在老年代上的GC要比新生代要少得多。对象从老年代中消失的过程可以称之为major GC或者full GC。

10.jvm有哪些垃圾回收器

新生代收集器全部的都是复制算法Serial、ParNew、Parallel Scavenge
老年代收集器CMS标记-清理、Serial Old标记-整理、Parallel Old标记整理
整堆收集器 G1一个Region中是标记-清除算法2个Region之间是复制算法 同时先解释几个名词
1并行Parallel多个垃圾收集线程并行工作此时用户线程处于等待状态
2并发Concurrent用户线程和垃圾收集线程同时执行
3吞吐量运行用户代码时间/运行用户代码时间+垃圾回收时间

11.说一说双亲委派机制

首先我们需要知道的是Java语言系统中支持以下4种类加载器
Bootstrap ClassLoader 启动类加载器
Extention ClassLoader 标准扩展类加载器
Application ClassLoader 应用类加载器
User ClassLoader 用户自定义类加载器
所谓的双亲委派机制指的就是当一个类加载器收到了类加载的请求的时候他不会直接去加载指定的类而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候才会由当前这个加载器来负责类的加载。

12.为什么需要双亲委派

首先通过委派的方式可以避免类的重复加载当父加载器已经加载过某一个类时子加载器就不会再重新加载这个类。
另外通过双亲委派的方式还保证了安全性。因为Bootstrap ClassLoader在加载的时候只会加载JAVA_HOME中的jar包里面的类如java.lang.Integer那么这个类是不会被随意替换的除非有人跑到你的机器上 破坏你的JDK。
那么就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改

13."父子加载器"之间的关系是继承吗

双亲委派模型中类加载器之间的父子关系一般不会以继承Inheritance的关系来实现而是都使用组合Composition关系来复用父加载器的代码的。

14.什么情况下父加载器会无法加载某一个类呢

Java中提供的这四种类型的加载器是有各自的职责的
Bootstrap ClassLoader 主要负责加载Java核心类库%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
Extention ClassLoader主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
Application ClassLoader 主要负责加载当前应用的classpath下的所有类
User ClassLoader 用户自定义的类加载器,可加载指定路径的class文件
那么也就是说一个用户自定义的类如com.li.ClassHollis 是无论如何也不会被Bootstrap和Extention加载器加载的。

15.为什么Tomcat要破坏双亲委派

我们知道Tomcat是web容器那么一个web容器可能需要部署多个应用程序。
不同的应用程序可能会依赖同一个第三方类库的不同版本但是不同版本的类库中某一个类的全路径名可能是一样的。
如多个应用都要依赖hollis.jar但是A应用需要依赖1.0.0版本但是B应用需要依赖1.0.1版本。这两个版本中都有一个类是com.hollis.Test.class。
如果采用默认的双亲委派类加载机制那么是无法加载多个相同的类。
所以Tomcat破坏双亲委派原则提供隔离的机制为每个web容器单独提供一个WebAppClassLoader加载器。
Tomcat的类加载机制为了实现隔离性优先加载 Web 应用自己定义的类所以没有遵照双亲委派的约定每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件加载不到时再交给CommonClassLoader加载这和双亲委派刚好相反。

16.说一说类的加载过程

类加载的过程主要分为三个部分加载,链接,初始化
而链接又可以细分为三个小部分验证,准备,解析
加载
简单来说加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。
这里有两个重点
字节码来源。一般的加载来源包括从本地路径下编译生成的.class文件从jar包中的.class文件从远程网络以及动态代理实时编译
类加载器。一般包括启动类加载器扩展类加载器应用类加载器以及用户的自定义类加载器。
验证
主要是为了保证加载进来的字节流符合虚拟机规范不会造成安全错误。
包括对于文件格式的验证比如常量中是否有不被支持的常量文件中是否有不规范的或者附加的其他信息
对于元数据的验证比如该类是否继承了被final修饰的类类中的字段方法是否与父类冲突是否出现了不合理的重载
对于字节码的验证保证程序语义的合理性比如要保证类型转换的合理性。
准备
主要是为类变量注意不是实例变量分配内存并且赋予初值。
特别需要注意初值不是代码中具体写的初始化的值而是Java虚拟机根据不同变量类型的默认初始值。
比如8种基本类型的初值默认为0引用类型的初值则为null常量的初值即为代码中设置的值final static tmp = 456 那么该阶段tmp的初值就是456
解析
将常量池内的符号引用替换为直接引用的过程。
两个重点
符号引用。即一个字符串但是这个字符串给出了一些能够唯一性识别一个方法一个变量一个类的相关信息。
直接引用。可以理解为一个内存地址或者一个偏移量。比如类方法类变量的直接引用是指向方法区的指针而实例方法实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量
举个例子来说现在调用方法hello()这个方法的地址是1234567那么hello就是符号引用1234567就是直接引用。
在解析阶段虚拟机会把所有的类名方法名字段名这些符号引用替换为具体的内存地址或偏移量也就是直接引用。
初始化
这个阶段主要是对类变量初始化是执行类构造器的过程。
换句话说只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候其父类尚未初始化则优先初始化其父类。
如果同时包含多个静态变量和静态代码块则按照自上而下的顺序依次执行。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6