1. JVM虚拟机高频面试题

目录


Java Web面试题目录清单(高频面试题型)(点击进入…)



Java JVM虚拟机高频面试题

1.JVM包含两个子系统和两个组件

两个子系统为Class loader(类装载)、Execution engine(执行引擎)

子系统描述
ClassLoader(类装载)根据给定的全限定名类名(java.lang.Object)来装载class文件到Runtime data area中的method area
Execution engine(执行引擎)执行classes中的指令

两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)

组件描述
Native Interface(本地接口)与native libraries交互,是其它编程语言交互的接口
Runtime data area(运行时数据区域)JVM内存

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

在这里插入图片描述


2.运行时数据区?

在这里插入图片描述

(1)程序计数器(Program Counter Register)
当前线程所执行的字节码的行号指示器
字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成

(2)Java虚拟机栈(Java Virtual Machine Stacks)
用于存储局部变量表、操作数栈、动态链接、方法出口等信息

(3)本地方法栈(Native Method Stack)
与虚拟机栈的作用是一样的,只不过虚拟机栈是服务Java方法的,而本地方法栈是为虚拟机调用Native方法服务

(4)Java堆(Java Heap)
Java虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存

(5)方法区(Methed Area)
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据


3.深拷贝和浅拷贝

浅拷贝(shallowCopy):只是增加了一个指针指向已存在的内存地址
仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变

深拷贝(deepCopy):增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。在计算机中开辟一块新的内存地址用于存放复制的对象


4.堆栈区别?

(1)物理地址
:物理地址分配对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如:标记-消除、复制、标记-压缩、分代(即新生代使用复制算法,老年代使用:标记-压缩)
:使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快

(2)内存大小
:不连续。所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈
:连续。所以分配的内存大小要在编译期就确认,大小是固定的

(3)存放内容
:存放的是对象的实例和数组。因此该区更关注的是数据的存储
:存放局部变量、操作数栈、返回结果。该区更关注的是程序方法的执行

(4)程序的可见度
:对于整个应用程序都是共享、可见的
:只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同

注意:静态变量放在方法区、静态的对象还是放在堆


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

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

队列
操作名称队列的插入称为入队,队列的删除称为出队栈的插入称为进栈,栈的删除称为出栈
可操作方式队列是在队尾入队,队头出队,即两边都可操作(尾入头出)进栈和出栈都是在栈顶进行的,无法对栈底直接进行操作(栈顶)
操作方法先进先出(FIFO),即队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(不能从中间插入),每次离开的成员总是队列头上(不允许中途离队)后进先出(LIFO),即每次删除(出栈)的总是当前栈中最新的元素,即最后插入(进栈)的元素,而最先插入的被放在栈的底部,要到最后才能删除(压栈)

6.Java会存在内存泄漏吗?

内存泄漏:指不再被使用的对象或者变量一直被占据在内存中。但是垃圾回收器(GC)无法从内存中删除他们的情况,因此他们会被不必要的一直存在;这种情况会耗尽内存资源并降低系统性能,最终以OOM终止(Out of Memory,内存溢出)
OOM:分配的太少;用的太多,用完没有释放

内存泄露:内存用完没有释放。大量的内存泄露会导致OOM(内存溢出)
理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。

Java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的发生场景

内存泄露症状:
(1)应用程序长时间连续运行时性能严重下降
(2)应用程序中的OutOfMemoryError堆错误
(3)自发且奇怪的应用程序崩溃
(4)应用程序偶尔会耗尽连接对象

(1)static字段引起的内存泄露
大量使用static字段会潜在的导致内存泄露,在Java中,静态字段通常拥有与整个应用程序相匹配的生命周期。
解决办法:最大限度的减少静态变量的使用;单例模式时,依赖于延迟加载对象而不是立即加载方式

(2)未关闭的资源导致内存泄露
每当创建连接或者打开流时,JVM都会为这些资源分配内存。如果没有关闭连接,会导致持续占有内存。在任意情况下,资源留下的开放连接都会消耗内存,如果不处理,就会降低性能,甚至OOM。
解决办法:使用finally块关闭资源;关闭资源的代码,不应该有异常;jdk1.7后,可以使用try-with-resource块

(3)不正确的equals()和hashCode()
在HashMap和HashSet这种集合中,常常用到equal()和hashCode()来比较对象,如果重写不合理,将会成为潜在的内存泄露问题。
解决办法:用最佳的方式重写equals()和hashCode()

(4)引用了外部类的内部类
非静态内部内的初始化,总是需要外部类的实例;默认情况下,每个非静态内部类都包含对其包含内的隐式引用,如果在应用程序中使用这个内部类对象,那么即使在包含类对象超出范围后,它也不会被垃圾收集
解决办法:如果内部类不需要访问包含的类成员,考虑转换为静态类

(5)finalize()方法造成的内存泄露
重写finalize()方法时,该类的对象不会立即被垃圾收集器收集,如果finalize()方法的代码有问题,那么会潜在的引发OOM;
解决办法:避免重写finalize()

(6)常量字符串造成的内存泄露
如果读取一个很大的String对象,并调用了inter(),那么它将放到字符串池中,位于PermGen中,只要应用程序运行,该字符串就会保留,这就会占用内存,可能造成OOM
解决办法:增加PermGen的大小,-XX:MaxPermSize=512m;升级Java版本,JDK1.7后字符串池转移到了堆中

(7)使用ThreadLocal造成内存泄露
使用ThreadLocal时,每个线程只要处于存货状态就可保留对其ThreadLocal变量副本的隐式调用,且将保留其自己的副本。使用不当,就会引起内存泄露。

一旦线程不在存在,ThreadLocals就应该被垃圾收集,而现在线程的创建都是使用线程池,线程池有线程重用的功能,因此线程就不会被垃圾回收器回收。所以使用到ThreadLocals来保留线程池中线程的变量副本时,ThreadLocals没有显示的删除时,就会一直保留在内存中,不会被垃圾回收。

解决办法:不在使用ThreadLocal时,调用remove(),该方法删除了此变量的当前线程值。不要使用ThreadLocal.set(null),它只是查找与当前线程关联的Map并将键值对设置为当前线程为null


7.Java垃圾回收机制?

在Java中,程序员是不需要显式的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级(GC守护线程)的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收


8.GC是什么?为什么要GC?

GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存

回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法


9.垃圾回收的优点和原理。并考虑2种回收机制

Java语言最显著的特点就是引入了垃圾回收机制,使Java程序员在编写程序时不再考虑内存管理的问题。由于有这个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有引用的对象才有“作用域”
垃圾回收机制有效的防止了内存泄露,可以有效的使用可使用的内存

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

(2)程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收
垃圾回收有分代复制垃圾回收、标记垃圾回收、增量垃圾回收


10.Java中都有哪些引用类型?

引用类型描述
强引用发生GC的时候不会被回收
软引用有用但不是必须的对象,在发生内存溢出之前会被回收
弱引用有用但不是必须的对象,在下一次GC时会被回收
虚引用无法通过虚引用获得对象,用PhantomReference实现虚引用,虚引用的用途是在GC时返回一个通知

11.怎么判断对象是否可以被回收?

垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需要被回收

一般有两种方法来判断:
(1)引用计数器法
为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题

(2)可达性分析算法
从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的


12.在Java中,对象什么时候可以被垃圾回收?

当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因


13.JVM中的永久代中会发生垃圾回收?

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。查看垃圾收集器的输出信息,就会发现永久代也是会被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因
请参考下Java 8:从永久代到元数据区(注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)


14.JVM有哪些垃圾回收算法?

(1)标记-清除算法
标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片

(2)复制算法
按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。

(3)标记-整理算法
标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。

(4)分代算法
根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法


15.JVM加载Class文件的原理机制

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把Class文件从硬盘读取到内存中。在写程序的时候,几乎不需要关心类的加载,因为这些都是隐式装载的,除非有特殊的用法,像是反射,就需要显式的加载所需要的类

类装载方式,有两种:
(1)隐式装载
程序在运行过程中当通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中

(2)显式装载
通过Classs.forName()等方法,显式加载需要的类

Java类的加载是动态的,并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销


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

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

主要有一下四种类加载器:
(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类的方式实现


17.类装载的执行过程?

(1)加载:根据查找路径找到相应的class文件然后导入
(2)验证:检查加载的class文件的正确性
(3)准备:给类中的静态变量分配内存空间
(4)解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址
(5)初始化:对静态变量和静态代码块执行初始化工作


18.什么是类加载?

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构


19.类加载机制?

Java类从被虚拟机加载开始,到卸载出内存为止。

整个生命周期:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段

其中验证、准备和解析又统称为连接(Linking)阶段
在这里插入图片描述
按部就班地开始,而解析阶段则不一定:它在某些情况下可以再初始化阶段后再开始,这是为了支持Java语言的运行时绑定(动态绑定或晚期绑定)

注意类的加载过程必须按照这种顺序按部就班地开始,而不是按部就班地进行或完成,因为这些阶段通常都是相互交叉地混合式进行的,通常会在一个阶段执行的过程中调用、激活另外一个阶段

类的加载时机

在这里插入图片描述

(1)加载(Loading):加载.class文件到内存中

“加载”是“类加载机制”的第一个过程,在加载阶段,虚拟机主要完成三件事:
(1)通过一个类的全限定名来获取其定义的二进制字节流
(2)将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
(3)在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口

相对于类加载的其他阶段而言,加载阶段是可控性最强的阶段,因为程序员可以使用系统的类加载器加载,还可以使用自己的类加载器加载

(2)验证(Verification):验证.class文件是否符合jvm规范

验证阶段主要确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。

(1)文件格式验证:基于字节流验证
(2)元数据验证:基于方法区的存储结构验证
(3)字节码验证:基于方法区的存储结构验证
(4)符号引用验证:基于方法区的存储结构验证

(3)准备(Preparation)

如果.class文件符合jvm规范,为类对象分配内存空间。给类变量(static修复的变量),来一个默认值(比如整型默认0)

准备阶段主要为类变量分配内存并设置初始值。这些内存都在方法区分配。在这个阶段只需要注意两点就好了,也就是类变量和初始值两个关键词:

(1)类变量(static)会分配内存,但是实例变量不会,实例变量主要随着对象的实例化一块分配到java堆中
(2)这里的初始值指的是数据类型默认值,而不是代码中被显示赋予的值

//在这里准备阶段过后的value值为0,而不是1。赋值为1的动作在初始化阶段
public static int value = 1; 

标注为final之后,value的值在准备阶段初始化为123而非0

public static final int value = 1; 
(4)解析(Resolution)

解析阶段是把常量池内的符号引用替换成直接引用的过程,符号引用就是Class文件中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量

1.符号引用(Symbolic References)
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要可以唯一定位到目标即可。符号引用于内存布局无关,所以所引用的对象不一定需要已经加载到内存中。各种虚拟机实现的内存布局可以不同,但是接受的符号引用必须是一致的,因为符号引用的字面量形式已经明确定义在Class文件格式中

2.直接引用(Direct References)
直接引用时直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存布局相关,同一个符号引用在不同虚拟机上翻译出来的直接引用一般不会相同。如果有了直接引用,那么它一定已经存在于内存中

以下Java虚拟机指令会将符号引用指向运行时常量池,执行任意一条指令都需要对它的符号引用进行解析

(5)初始化(Initialization)

类初始化阶段是类加载过程的最后一步。在前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的java程序代码(字节码)

在准备阶段,变量已经赋过一次系统要求的初始值(零值);而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者更直接地说:初始化阶段是执行类构造器()方法的过程。()方法时由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问


20.JVM调优工具?

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

调优工具描述
jconsole用于对JVM中的内存、线程和类等进行监控
jvisualvmJDK自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、GC变化等
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未禾

您的支持是我最宝贵的财富!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值