前言:该篇主要对Java虚拟机相关的题目进行介绍。


JVM篇

基本上在面试的时候,都会或多或少的涉及JVM,主要看面试官的侧重点,笔者在面试过程中,是通过volatile问题,引导了JVM相关问题上的。

1)JVM的内存区域,各区域存储什么,及其作用。

程序计数器

#1.当前线程正在执行字节码行号指示器。

#2.为了线程切换后能够恢复到正确的执行位置,每个线程都需要一个独立的程序计数器。(线程私有

#3.当线程执行的是一个Java方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址。

#4.当线程执行的是一个Native方法,则计数器的值为空(Undefined)(原因:native方法体并不是由Java字节码构成,所以无法应用“Java字节码地址”的概念,所以JVM规范规定,当执行Native方法时,计数器的值未定义(任何值都可以))。

#5.该区域是唯一一个在JVM规范中没有规定任何OOM情况的区域。

Java虚拟机栈

#1.描述Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

#2.生命周期与线程相同,线程运行完毕后,相应的内存自动回收。(线程私有

#3.这个区域可能有两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常(如:将一个函数反复递归自己,最终会出现这种异常)。如果JVM栈可以动态扩展(大部分JVM是可以的),当扩展时无法申请到足够内存则抛出OutOfMemoryError(OOM)异常

本地方法栈

#1.本地方法栈与Java虚拟机栈所发挥的作用很相似,区别在于Java虚拟机栈为执行Java代码方法服务,而本地方法栈是为Native方法服务。

#2.和JVM栈一样,这个区域也会抛出StackOverflowError和OutOfMemoryError异常。

#3.线程私有。

方法区

#1.方法区域是全局共享的,比如每个线程都可以访问同一个类的静态变量。它存储了已被JVM加载的类信息、静态变量、编译器编译后的代码等。如,当程序中通过getName、isInterface等方法来获取信息时,这些数据来源于方法区。

#2.由于使用反射机制的原因,虚拟机很难推测哪个类信息不再使用,因此这块区域的回收很难!另外,对这块区域主要是针对常量池回收,值得注意的是JDK1.7已经把常量池转移到堆里面了

#3.当方法区无法满足内存分配需求时,会抛出OutOfMemoryError。

#1.堆是Java虚拟机所管理内存最大的一块,被线程全局共享,在虚拟机启动时创建。

#2.几乎所有的对象实例都在堆中分配内存。但随着JIT编译器的发展,栈上分配等技术的优化,所以是“几乎所有的对象”。

#3.堆是垃圾收集器的主要区域。

#4.根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

#5.如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

2)JVM栈中具体存储的内容,如何存储。

在JVM栈中,方法在执行的时候会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、返回地址(方法出口)等信息。

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

#2.对于基本数据类型,long和double类型的数据会占2个局部变量空间(slot),其余数据类型只占1个。局部变量表所需的内存空间在编译期间完成分配。所以方法在运行期间所需的局部变量空间大小是一定的。

#3.静态方法和实例方法的局部变量表基本类似,但是实例方法表中,第一个位置存放的是当前对象的引用,因为实例方法依赖于实例对象,方法与对象之间关联。

#4.由于Java没有寄存器,因此参数传递都是通过操作数栈,关于操作数栈具体的栈操作过程,参考:https://www.cnblogs.com/wade-luffy/p/5753057.html

#5.动态链接就是Class文件常量池中的符号引用,在运行期间转换为直接引用。对象较小,直接分配在栈上,可自动回收,减轻GC压力。

3)GC(垃圾回收),常见的垃圾回收算法以及其使用场景。

JVM垃圾回收,通俗来讲就是清除内存中(主要堆内存)“无用的”对象(不能再被任何途径使用的对象)。

常见的垃圾回收算法:

标记-清除算法

a.最基础的垃圾回收算法,分“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

b.缺点:

#1.效率问题,标记和清除两个过程效率都不高。

#2.空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾回收操作,影响性能。

c.使用场景:在老年代的垃圾回收中,因为老年代的存活时间较长,不会像新生代那样,频繁的“出生”与“死去”。

复制算法

复制算法是为了解决效率问题,它将内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块的内存用完了,就将还存活的对象复制到另一块上面,然后再把已使用过的内存空间一次性清理掉。这样使得每次都是对整个半区进行内存回收,并且使用的是一块连续的内存空间,不存在内存碎片,简单高效,该算法是以空间换时间的一种方式。

使用场景:使用在MinorGC中,MinorGC发生在新生代的垃圾回收过程中。新生代的对象具有“朝生夕死”的特征,通过高效的复制算法可以,快速的进行垃圾回收操作。

标记-整理算法

复制算法在对象存活率较高时就要进行较多的复制操作,效率将变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端状态,所以老年代一般不使用复制算法。根据老年代的特点(存活时间较长),就提出了“标记-整理”算法,标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让存活的对象都向一端移动,然后清理掉端边界以外的内存。

使用场景:对老年代进行垃圾回收操作。

④分代收集算法

当前的商业虚拟机都采用分代收集算法进行垃圾回收。思想:根据对象的存活周期分为新生代和老年代,根据各年代对象的特点采用合适的收集算法。新生代采用复制算法,老年代采用“标记-清除”或“标记-整理”算法。

4)OOM问题的解决思路及其解决办法。

OOM问题在实际生产过程中经常发生,随着时间的推移,用户人数的增加,JVM的内存不足,容易造成OOM问题。在JVM内存模型中,除了程序计数器外,Java虚拟机栈、本地方法栈、方法区、堆这几块区域中都可能发生OOM问题。

首先需要了解JVM的常见配置参数,见下图。

常见OOM问题:

①Java Heap=>Java.lang.OutOfMemoryError: Java heap space

出现该问题,首先要确定是内存泄漏(申请的空间无法释放)还是内存溢出,通过对dump文件的分析,一般可以确定,如果是内存泄漏,则需要找到泄漏的对象,并想办法解决该问题;如果是内存溢出,则需要检查JVM的-Xms和-Xmx的参数是否合适。

②OOM for Perm=>java.lang.OutOfMemoryError: Java perm space

出现该问题查看JVM的-XX:MaxPermSize(永代区的最大值)是否满足需要,根据实际情况进行调整。另外,注意一点,Perm一般是在JVM启动时加载类进来,如果是JVM运行较长一段时间而不是刚启动后溢出的话,很有可能是由于运行时有类被动态加载进来,此时建议用CMS策略中的类卸载配置。

如:-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled。

③OOM for GC=>java.lang.OutOfMemoryError: GC overhead limit exceeded

此OOM是由于JVM在GC时,对象过多,导致内存溢出,建议调整GC的策略,在一定比例下开始GC而不要使用默认的策略,或者将新代和老代设置合适的大小,需要进行微调存活率。
解决方法:改变GC策略,在老年代80%时开始GC,设置-XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)的值。

④OOM for native thread created=>java.lang.OutOfMemoryError: unable to create new native thread

出现该问题,需要考虑栈的大小是否合适,可适当调小栈的大小,利用-Xss。

上述4种是比较常见的OOM问题,其他OOM问题,请参考:

http://zhaohe162.blog.163.com/blog/static/38216797201110232341953/

https://blog.csdn.net/ls5718/article/details/52411211

5)什么情况下会发生 MinorGC、FullGC。

MinorGC发生在新生代的垃圾回收中,在Eden区没有足够的空间进行分配时,会触发一次MinorGC。

FullGC发生在整个堆垃圾回收中,Full GC触发条件:

#1.调用System.gc时,系统建议执行Full GC,但是不必然执行。

#2.老年代空间不足。

#3.方法区空间不足。

#4.通过Minor GC后进入老年代的平均大小(内存)大于老年代的可用内存。

参考:

https://www.zhihu.com/question/41922036

6)CMS 具体过程、每个过程是单线程还是多线程运行、每个过程是否和用户线程并行运行,CMS 适用于什么场景,哪些是 GC Roots 对象。

JVM垃圾回收算法为内存回收的方法论,JVM垃圾收集器为内存回收的具体实现。

常见7中垃圾收集器,可根据收集对象的不同状态分类:

新生代垃圾收集器(新生代采用复制算法):Serial、ParNew、Parallel Scavenge

老年代垃圾收集器(老年代采用标记-整理算法):CMS、Serial Old(MSC)、Parallel Old

G1垃圾收集器对新生代和老年代都可以收集,处于当中。

Serial:最基本,发展历史最悠久的收集器。是单线程的,并且会"Stop The World",直到它收集结束。用于client模式。

ParNew:为Serial收集器的多线程(并行多线程)版本。也会"Stop The World"。能与CMS配合的新生代收集器。

Parallel Scavenge:并行的多线程收集器。主要目的是达到一个可控的吞吐量,也会"Stop The World"。

Serial Old:是Serial收集器的老年代版本,单线程,会"Stop The World"。

Parallel Old:是Parallel Scavenge收集器的老年代版本,并行多线程。

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器主要应用于以Java为基础的互联网站或B/S系统的服务器端,因为这类系统重视服务器的响应速度,希望停顿时间最短,以带给用户较好的体验。CMS收集器是基于“标记-清除”算法的实现。

CMS运行分四个过程:

①初始标记

②并发标记

③重新标记

④并发清除

解释:

初始标记和重新标记这两个步骤需要“Stop The World”(暂停正在执行的任务),所以这两个过程是单线程的。并发标记和并发清除可以与用户线程一起工作(多线程

初始标记仅仅只是标记一下GC Roots能直接关联到的对象,虽然要暂停虚拟机,但是速度很快。

并发标记紧随初始标记,在初始标记的基础上向下追溯标记,并发标记线程是与用户线程并发执行,用户不会感觉到停顿。

重新标记是为了修正并发标记阶段因用户线程运行而导致标记产生变动的那一部分对象(因为并发标记是并行执行的),该阶段也会暂停虚拟机(Stop The World),停顿时间会比初始标记要长一些,但远比并发标记的时间短。

并发清除与用户线程并行执行,清除垃圾对象。

由于整个过程中耗时最长并发标记并发清除都可以与用户线程一起执行,因此,从总体上来说CMS收集器的内存回收过程是与用户线程并行执行的。

CMS适用的场景对响应时间重要性需求大于吞吐量的要求,也就是要响应时间快,如实时交易平台、实时通信平台,要求响应时间顿,基本无停顿感。

GC Roots对象包括:

①虚拟机栈帧中的本地变量表的引用对象。(个人理解为实例对象)

②方法区中类静态属性引用的对象。

③方法区中常量引用的对象。

④本地方法栈中JNI(也就是Native方法)引用的对象。

因为以上四种对象的存活时间较长,不会被轻易清除。


by Shawn Chen,于2018.6月,开始找工作途中......


相关内容

面试总结——Java篇

面试总结——数据库篇

面试总结——JVM篇的更多相关文章

  1. JAVA高级面试总结-JVM篇

    1.Sun HotSpot VM,是JDK和Open JDK中自带的虚拟机,也是目前使用范围最广的Java虚拟机. 2.JVM内存分布 程序计数器:是一块较小的内存空间,可以看作是当前线程所执行的字节 ...

  2. 面试总结——Java篇

    前言:前期对Java基础的相关知识点进行了总结,具体参看:Java基础和面试知识点.近期由于笔者正在换工作(ing),因此下面将笔者在面试过程中或笔者朋友面试过程中反馈的题目进行总结,相信弄清楚下面题 ...

  3. 【JAVA秒会技术之秒杀面试官】秒杀Java面试官——集合篇(一)

    [JAVA秒会技术之秒杀面试官]秒杀Java面试官——集合篇(一) [JAVA秒会技术之秒杀面试官]JavaEE常见面试题(三) http://blog.csdn.net/qq296398300/ar ...

  4. 基于JDK1.8的JVM 内存结构【JVM篇三】

    目录 1.内存结构还是运行时数据区? 2.运行时数据区 3.线程共享:Java堆.方法区 4.线程私有:程序计数器.Java 虚拟机栈.本地方法栈 5.JVM 内存结构总结 在我的上一篇文章别翻了,这 ...

  5. Go 面试每天一篇(第 65 天)

    Go 面试每天一篇(第 65 天) 1.下面列举的是 recover() 的几种调用方式,哪些是正确的? A. 1func main() { 2 recover() 3 panic(1) 4} B. ...

  6. Java 面试知识点解析(三)——JVM篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  7. 面试必问之JVM篇

    前言 只有光头才能变强 JVM在准备面试的时候就有看了,一直没时间写笔记.现在到了一家公司实习,闲的时候就写写,刷刷JVM博客,刷刷电子书. 学习JVM的目的也很简单: 能够知道JVM是什么,为我们干 ...

  8. 面试大全之JVM篇

    JVM 内存模型以及分区,需要详细到每个区放什么. JVM 分为堆区和栈区,还有方法区,初始化的对象放在堆里面,引用放在栈里面,class类信息常量池(static常量和static变量)等放在方法区 ...

  9. 【Java面试必备JVM】JVM看这篇就够了

    链接–>JVM

随机推荐

  1. 人工智能第三课:数据科学中的Python

    我用了两天左右的时间完成了这一门课<Introduction to Python for Data Science>的学习,之前对Python有一些基础,所以在语言层面还是比较顺利的,这门 ...

  2. UML 序列图

    序列图      序列图主要用于按照交互发生的一系列顺序,显示对象之间的这些交互.显示不同的业务对象如何交互,对于交流当前业务如何进行很有用.序列图是一个用来记录系统需求,和整理系统设计的好图.序列图 ...

  3. Android Studio 使用Intent

    1.显式Intent Intent intent=new Intent(yzj.this,MainActivity.class);//当前活动的实例,要去的实例 startActivity(inten ...

  4. JS基础(三)构造函数

    JS中的构造函数 <script language="JavaScript"> window.onload = function(){ function Bottle( ...

  5. 字符串拼接引发的BUG

    译者按: bug虽小,却是个磨人的小妖精! 原文: Fixing a bug: when concatenated strings turn into numbers in JavaScript 译者 ...

  6. 你还在等着用户反馈BUG?

    译者按: 等待用户反馈BUG,一切都晚了!实时监控线上应用才是王道. 原文: Why relying on your users to report errors is the dumbest thi ...

  7. C#中try catch finally的执行顺序

    1.首先明确一点,就是不管怎样,finally一定会执行,即使程序有异常,并且在catch中thorw 了 ,finally还是会被执行. 2.当try和catch中有return时,finally仍 ...

  8. laravel框架详解

    一.基础篇 1.概念 Laravel是一个有着美好前景的年轻框架,它的社区充满着活力,同时提供了完整而清晰的文档,而且为快速.安全地开发现代应用提供了必要的功能.2011年,Taylor Otwell ...

  9. loadrunner 脚本优化-参数化之Parameter List参数同行取值

    脚本优化-参数化之Parameter List参数同行取值 by:授客 QQ:1033553122 select next row 记录选择方式 Same line as,这个选项只有当参数多余一个时 ...

  10. ionic提示弹框

    //提示框 .factory('TipsPort', function ($ionicPopup) { var TipsPort = function (tipsText, SureFunction, ...