运行时数据区的总结以及常见大厂面试题
线程私有的:程序计数器、本地方法栈、虚拟机栈
虚拟机栈里的栈帧的结构:返回值、局部变量表、操作数栈、动态链接(装着指向运行时常量池的当前方法的引用,知道当前方法是引用运行时常量池中的哪个方法)
常见面试题
百度:
说一下 JVM 内存模型吧,有哪些区?分别干什么的?
答:
JVM内存区域主要包含:方法区 程序计数器 Java虚拟机栈 本地方法栈 Java堆
1、方法区:
方法区在JDK1.8之后把名字改成了“Metaspace",可以翻译成"元数据空间",这是一块线程共享的内存空间,主要用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
Java虚拟机规范将方法区描述为堆的一个逻辑部分,但它有一个别名”Non-Heap 非堆“ 用于与Java堆区分。
很多人愿意把方法区成为”永久代“,本质上两者并不等价。HotSpot虚拟机 团队选择把GC分代收集扩展至方法区,或者说使用永久带实现方法区而已。
方法区存在的问题:
永久代容易遇到内存溢出问题(HotSpot永久代有-XX:MaxPermSize上限)当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常运行时常量池也是方法区的一个部分,主要用于存放编译器生成的各种字面量和符号引用。类加载后会进入方法区的运行时常量池
运行时常量池存在的问题:
当常量池无法再申请到内存空间时会抛出OutOfMemoryError异常
2、虚拟机栈
虚拟机栈是线程私有的内存空间,其生命周期与线程相同,是用于Java方法执行的内存模型。方法执行时会创建栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等。
虚拟机栈存在的问题:
线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常虚拟机栈动态扩展时无法申请到足够的内存,抛出OutOfMemoryError异常
3、本地方法栈
其发挥的作用和存在的问题与Java虚拟机栈类似,这里主要说一下其区别。其区别主要是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机使用到Native方法服务。
知识点:Hotspot虚拟机中将本地方法栈与虚拟机栈合二为一。
4、Java堆
Java堆是虚拟机中占内存最大的一块内存空间,是所有线程共享的内存区域,当虚拟机启动的时候就会创建。它的作用主要是存放对象实例,几乎所有的对象实例都在这里分配内存。我们来看下Java虚拟机规范是怎么描述它的:
所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配到堆上也渐渐变得不是那么”绝对“了。
这一块内存空间也是垃圾收集器管理的主要区域,所以有时候也被称为”GC堆“。
Java堆存在的问题:
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
5、程序计数器
程序计数器是线程私有的一块占用较小的内存空间,主要用于记录当前线程执行到哪里了。而且这也是唯一一个没有内存溢出的区域。
6、直接内存
除了以上几块内存空间外,还有一块内存空间就是直接内存,它不属于运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分频繁使用也可能导致OutOfMemoryError异常。NIO可以使用Native函数库直接分配堆外内存空间。
文末小结
关于jvm内存区域空间要重点关注方法区,程序计数器,Java虚拟机栈和Java堆这些内存区域的作用。只有在了解了虚拟机是怎么使用内存的之后,才能在出现内存溢出和泄漏时更快速的定位和排查解决问题。
蚂蚁金服:
Java8 的内存分代改进 JVM 内存分哪几个区,每个区的作用是什么?
一面:JVM 内存分布/内存结构?栈和堆的区别?堆的结构?为什么两个 survivor 区?
二面:Eden 和 survior 的比例分配
答:8 : 1 : 1
小米:
jvm 内存分区,为什么要有新生代和老年代
答:
其实不分代完全可以,分代的唯一理由就是优化 GC 性能。如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。GC 的时候要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而很多对象都是朝生夕死的,如果分代的话,把新创建的对象放到某一地方,当 GC 的时候先把这块存储“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。
答案一:
对于JVM而言,大部分对象都是属于一个朝生夕死的状态,这部分对象随着方法的调用而创建,方法的结束而消亡,只有少部分的对象会长久的留在JVM 内存中,所以根据这样的特性JVM 把内存分为了新生代 和老年代两个区,一般情况新创建的对象会放到新生代中,只有经过一定次数的GC后还没有被回收的对象,我们认为这部分对象在未来也会长时间存在,所以会把这部分的对象转移到老年代的区域中去。
答案二:
1)新生代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Suvivor Space(from 和to)。 2)老年代(Tenured Gen):年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁。
字节跳动:
二面:Java 的内存分区
二面:讲讲 vm 运行时数据库区 什么时候对象会进入老年代?
答:
长期存活的对象:虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,。对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当他的年龄增加到一定程度(默认是15岁), 就将会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
当遇到超大对象时,发现新生代中的Eden区(即便进行了Minor GC)放不下,就会直接尝试放到老年代Tenured/OId,如果老年代也放不下,就触发Major GC 或 Full GC,之后老年代区能放得下就放,不能的话就报OOM。大对象对虚拟机的内存分配就是坏消息,尤其是一些朝生夕灭的短命大对象,写程序时应避免。
动态对象年龄判定:为了能更好地适应不同程度的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
京东:
JVM 的内存结构,Eden 和 Survivor 比例。
JVM 内存为什么要分成新生代,老年代,持久代。
新生代中为什么要分为 Eden 和 survivor。
答:
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,大概是Minor GC的十倍以上, 如果Major GC触发次数多的话,就会降低性能,所以要尽可能避免或减少老年代触发Major GC。因此需要分为Eden和Survivor。
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor spaceS1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
天猫:
一面:Jvm 内存模型以及分区,需要详细到每个区放什么。
一面:JVM 的内存模型,Java8 做了什么改
答:
JDK1.6 及之前 有永久代(permanet),静态变量存储在永久代上 JDK1.7 有永久代,但已经逐步 “去永久代”,字符串常量池,静态变量移除,保存在堆中 JDK1.8 无永久代,类型信息,字段,方法,常量保存在本地内存的元空间,但字符串常量池、静态变量仍然在堆中。 其中,String Table之所以要调整位置:
jdk7 中将 StringTable 放到了堆空间中。因为永久代的回收效率很低,在 full gc 的时候才会触发。而 full gc 是老年代的空间不足、永久代不足时才会触发。
这就导致 StringTable 回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。
拼多多:
JVM 内存分哪几个区,每个区的作用是什么?
美团:
java 内存分配 jvm 的永久代中会发生垃圾回收吗?
一面:jvm 内存分区,为什么要有新生代和老年代?