万万没想到,JVM内存区域的面试题也可以问的这么难?

简介: 万万没想到,JVM内存区域的面试题也可以问的这么难?

二、Java内存区域


1、Java内存结构


image.png


  • 程序计数器


当前线程所执行字节码的行号指示器。若当前方法是native的,那么程序计数器的值就是undefined。

线程私有,Java内存区域中唯一一块不会发生OOM或StackOverflow的区域。


  • 虚拟机栈


就是常说的Java栈,存放栈帧,栈帧里存放局部变量表等信息,方法执行到结束对应着一个栈帧的入栈到出栈。

线程私有,会发生StackOverflow。


  • 本地方法栈


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

线程私有,会发生StackOverflow。



Java 虚拟机中内存最大的一块,几乎所有的对象实例都在这里分配内存。

是被所有线程共享的,会发生OOM。


  • 方法区


也称非堆,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

是被所有线程共享的,会发生OOM。


  • 运行时常量


是方法区的一部分,存常量(比如static final修饰的,比如String 一个字符串)和符号引用。

是被所有线程共享的,会发生OOM。


2、对象创建时堆内存分配算法


  • 指针碰撞


前提要求堆内存的绝对工整的。

所有用过的内存放一边,没用过的放另一边,中间放一个分界点的指示器,当有对象新生时就已经知道大小了,指示器只需要像没用过的内存那边移动与对象等大小的内存区域即可。


image.png


  • 空闲列表


假设堆内存并不工整,那么空闲列表最合适。

JVM维护一个列表 ,记录哪些内存块是可用的,当对象创建时从列表中找到一块足够大的空间划分给新生对象,并将这块内存标记为已用内存。


image.png


3、对象在内存中的存储布局


分为三部分:


  • 对象头


包含两部分:自身运行时数据和类型指针。

自身运行时数据包含:hashcode、gc分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳等

对象指针就是对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例


  • 实例数据


用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)


  • 对齐填充


JVM要求对象起始地址必须是8字节的整数倍(8字节对齐),所以不够8字节就由这部分来补充。


4、对象怎么定位


如下两种,具体用哪种有JVM来选择,hotspot虚拟机采取的直接指针方式来定位对象。


  • 直接指针


栈上的引用直接指向堆中的对象。好处就是速度快。没额外开销。


  • 句柄


Java堆中会单独划分出一块内存空间作为句柄池,这么一来栈上的引用存储的就是句柄地址,而不是真实对象地址,而句柄中包含了对象的实例数据等信息。好处就是即使对象在堆中的位置发生移动,栈上的引用也无需变化。因为中间有个句柄。


5、判断对象是否能被回收的算法



  • 引用计数法


给对象添加一个引用计数器,每当有一个地方引用他的时候该计数器的值就+1,当引用失效的时候该计数器的值就-1;当计数器的值为0的时候,jvm判定此对象为垃圾对象。存在内存泄漏的bug,比如循环引用的时候,所以jvm虚拟机采取的是可达性分析法。


  • 可达性分析法


有一些根节点GC Roots作为对象起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时候,则证明此对象为垃圾对象。

补充:哪些可作为GC Roots?

  • 虚拟机栈中的引用的对象
  • 方法区中的类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(native方法)引用的对象


6、如何判断对象是否能被回收


  • 该对象没有与GC Roots相连


  • 该对象没有重写finalize()方法或finalize()已经被执行过则直接回收(第一次标记)、否则将对象加入到F-Queue队列中(优先级很低的队列)在这里finalize()方法被执行,之后进行第二次标记,如果对象仍然应该被GC则GC,否则移除队列。(在finalize方法中,对象很可能和其他 GC Roots中的某一个对象建立了关联,那就自救了,就不会被GC掉了,finalize方法只会被调用一次,且不推荐使用finalize方法)


7、Java堆内存组成部分


image.png


堆大小 = 新生代 + 老年代。如果是Java8则没有Permanent Generation,Java8将此区域换成了Metaspace。


其中新生代(Young) 被分为 Eden和S0(from)和S1(to)。

默认情况下Edem : from : to = 8 : 1 : 1,此比例可以通过 –XX:SurvivorRatio 来设定


8、什么时候抛出StackOverflowError


方法运行的时候栈的深度超过了虚拟机容许的最大深度的时候,所以不推荐递归的原因之一也是因为这个,效率低,死归的话很容易就StackOverflowError了。


9、Java中会存在内存泄漏吗,请简单描述。


虽然Java会自动GC,但是使用不当的话还是存在内存泄漏的,比如ThreadLocal忘记remove的情况。(ThreadLocal篇幅过长,不适合放到这里,懂者自懂,不懂Google)


10、栈帧是什么?包含哪些东西


栈帧中存放的是局部变量、操作数栈、动态链接、方法出口等信息,栈帧中的局部变量表存放基本类型+对象引用+returnAddress,局部变量所需的内存空间在编译期间就完成分配了,因为基本类型和对象引用等都能确定占用多少slot,在运行期间也是无法改变这个大小的。


11、简述一个方法的执行流程


方法的执行到结束其实就是栈帧的入栈到出栈的过程,方法的局部变量会存到栈帧中的局部变量表里,递归的话会一直压栈压栈,执行完后进行出栈,所以效率较低,因为一直在压栈,栈是有深度的。


12、方法区会被回收吗


方法区回收价值很低,主要回收废弃的常量和无用的类。


如何判断无用的类:


  • 该类所有实例都被回收(Java堆中没有该类的对象)
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方利用反射访问该类


13、一个对象包含多少个字节


会占用16个字节。比如

Object obj = new Object();


因为obj引用占用栈的4个字节,new出来的对象占用堆中的8个字节,4+8=12,但是对象要求都是8的倍数,所以对象的字节对齐(Padding)部分会补齐4个字节,也就是占用16个 字节。


再比如:


public class NewObj {
    int count;
    boolean flag;
    Object obj;
}
NewObj obj = new NewObj();


这个对象大小为:空对象8字节+int类型4字节+boolean类型1字节+对象的引用4字节=17字节,需要8的倍数,所以字节对齐需要补充7个字节,也就是这段程序占用24字节。


14、为什么把堆栈分成两个


  • 栈代表了处理逻辑,堆代表了存储数据,分开后逻辑更清晰,面向对象模块化思想。
    栈是线程私有,堆是线程共享区,这样分开也节省了空间,比如多个栈中的地址指向同一块堆内存中的对象。


  • 栈是运行时的需要,比如方法执行到结束,栈只能向上增长,因此会限制住栈存储内容的能力,而堆中的对象是可以根据需要动态增长的。


15、栈的起始点是哪


main函数,也是程序的起始点。


16、为什么基本类型不放在堆里


因为基本类型占用的空间一般都是1-8个字节(所需空间很少),而且因为是基本类型,所以不会出现动态增长的情况(长度是固定的),所以存到栈上是比较合适的。反而存到可动态增长的堆上意义不大。


17、Java参数传递是值传递还是引用传递


值传递。


基本类型作为参数被传递时肯定是值传递;引用类型作为参数被传递时也是值传递,只不过“值”为对应的引用。假设方法参数是个对象引用,当进入被调用方法的时候,被传递的这个引用的值会被程序解释到堆中的对象,这个时候才对应到真正的对象,若此时进行修改,修改的是引用对应的对象,而不是引用本身,也就是说修改的是堆中的数据,而不是栈中的引用。


18、为什么不推荐递归


因为递归一直在入栈入栈,短时间无法出栈,导致栈的压力会很大,栈也有深度的,容易爆掉,所以效率低下。


19、为什么参数大于2个要放到对象里


因为除了double和long类型占用局部变量表2个slot外,其他类型都占用1个slot大小,如果参数太多的话会导致这个栈帧变大,因为slot大,放个对象的引用上去的话只会占用1个slot,增加堆的压力减少栈的压力,堆自带GC,所以这点压力可以忽略。


20、常见笔试题


问题:输出结果是什么?


答案:aaa、aaa、abc


原因:其实也是值传递还是引用传递的问题。具体核心原因:main函数的str引用和zcd在栈上,而其对应的值在方法区或堆上。test1、test2、test3的参数也在栈上,这个空间和main上的不是同一块,是不同的栈帧。所以你修改test方法的数据对于main函数其实是无感知的。但是对象的引用的话修改的是堆内存中的对象属性值,所以有感知,那为什么test2输出的是aaa而不是abc呢?因为test2把堆中的对象都给换了,重新生成一个全新对象,对main上的引用来讲是看不到的,具体如下三幅图:


(1)aaa


image.png


image.png


image.png


public class TestChuandi {
    public static void main(String[] args) {
        String str = "aaa";
        test1(str);
        // aaa
        System.out.println(str);
        Zhichuandi zcd = new Zhichuandi();
        zcd.setName("aaa");
        // aaa
        test2(zcd);
        System.out.println(zcd.getName());
        // abc
        test3(zcd);
        System.out.println(zcd.getName());
    }
    private static void test1(String s) {
        s = "abc";
    }
    private static void test2(Zhichuandi zcd) {
        zcd = new Zhichuandi();
        zcd.setName("abc");
    }
    private static void test3(Zhichuandi zcd) {
        zcd.setName("abc");
    }
}
class Zhichuandi {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}


END


相关文章
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
518 1
|
13天前
|
存储 监控 算法
Java JVM 面试题
Java JVM(虚拟机)相关基础面试题
|
28天前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
2月前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
2月前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
|
2月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
30 3
|
2月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
63 1
|
2月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。

热门文章

最新文章