谈谈对JVM的理解
JVM可谓是学习JAVA基础中的基础了,但仍有不少同学对JVM概念还是比较模糊,甚至没有听说过,对java的理解也只是在基础语法 层面,本文就将对JVM进行初步介绍,因篇幅所限,只能介绍JVM基础,如需要进一步学习,建议阅读机械工业出版社出版的《深入理解JAVA虚拟机》。
请尊重作者劳动成果,转载请标明原文链接:
Java虚拟机规范中规定的JVM如下图所示:
可以看出,JVM由JVM运行时数据区(图示中蓝色框包含部分)、执行引擎、本地库接口、本地方法库组成。
JVM运行时数据区,分为线程共享部分(方法区、堆)和线程隔离区(虚拟机栈、本地方法栈和程序计数器)。
1.方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池(Runtime Constant Pool)是方法区的一部分。.Class文件中除了有类的版本/字段/方法/接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将类在加载后进入方法区的运行时常量池中存放.
运行时常量区的内容并不只是在编译期间产生,通过String.intern()也可以实现在运行时向常量区中添加内容。
需要注意的是:从JDK8开始,方法区被元数据区替代了。具体的原因和两者的区别可以参考官网。
2.堆
是JVM中最大的一块内存区域,该区域的目的只是用于存储对象实例及数组。该区域也是GC的最主要区域。
根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样.在实现时,既可以实现固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制).如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,就会抛出OutOfMemoryError异常。
3.虚拟机栈
每个线程方法在执行时都会创建一个栈帧,包含局部变量表、返回地址、操作数栈等信息。每个方法的执行与完成就对应的栈帧的入栈与出栈过程 。局部变量表占用空间的大小在编译期就确定了。这里需要注意:如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时将会抛出OutOfMemoryError异常。
4.本地方法栈
与虚拟机栈类似,不过其中执行是本地方法。对于HotSpot虚拟机而言,本地方法栈和虚拟机栈是统一的。
5.程序计数器
是一个小的内存空间,如果线程正在执行的是一个java方法,则此内存区域记录正在执行的虚拟机字节码指令的地址;如果线程正在执行的是native方法,则计算器中的值为空。此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
这几部分都有相关的JDK自带工具可以分析查看,比如jps, jstack, jmap, jhat, jstat等,还有图形化工具jconsole,jvisualvm,但对于Linux服务器就无能为力了。
好了,关于JVM就讲完了,接下来进行提问:JVM哪些区域会发生OOME,以及各自原因是什么?
堆内存不足是最常见的 OOM 原因之一,抛出的错误信息是“java.lang.OutOfMemoryError:Java heap space”,原因可能千奇百怪,例如,可能存在内存泄漏问题;也很有可能就是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显式指定 JVM 堆大小或者指定数值偏小;或者出现 JVM 处理引用不及时,导致堆积起来,内存无法释放等。
而对于 Java 虚拟机栈和本地方法栈,这里要稍微复杂一点。如果我们写一段程序不断的进行递归调用,而且没有退出条件,就会导致不断地进行压栈。类似这种情况,JVM 实际会抛出 StackOverFlowError;当然,如果 JVM 试图去扩展栈空间的的时候失败,则会抛出 OutOfMemoryError。
对于老版本的 Oracle JDK,因为永久代的大小是有限的,并且 JVM 对永久代垃圾回收(如,常量池回收、卸载不再需要的类型)非常不积极,所以当我们不断添加新类型的时候,永久代出现 OutOfMemoryError 也非常多见,尤其是在运行时存在大量动态类型生成的场合;类似 Intern 字符串缓存占用太多空间,也会导致 OOM 问题。对应的异常信息,会标记出来和永久代相关:“java.lang.OutOfMemoryError: PermGen space”。
随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的 OOM 有所改观,出现 OOM,异常信息则变成了:“java.lang.OutOfMemoryError: Metaspace”。
直接内存不足,也会导致 OOM。
后记:
JVM是JAVA面试过程中最容易被问到的基础题目,在本篇JVM介绍完后,后面还准备了更多有意思的JAVA基础面试题,这些将作为提升JAVA基础能力的免费系列课程,从面试题出发,对每个问题举一反三,以点带面,不做不求甚解之人,最终目标不是学会答题,而是学会题目本身涉及到的所有知识点。暂定的全部面试题目课程包含如下内容:
谈谈你对JVM的理解?
final,finally,finalize有什么区别?
String,StringBuffer,StringBuilder有什么区别?
Exception和Error有什么区别?
Hashtable,HashMap,TreeMap有什么区别?
Vector,ArrayList,LinkedList有什么区别?
int和Integer有什么区别?
接口和抽象类有什么区别?
如何保证集合是线程安全的?
谈谈你知道的设计模式?
搜索关注微信公众号“程序员姜小白”,获取更新精彩内容哦。