JVM常见面试题

1. 什么情况下会发生栈内存溢出。
栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储
局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用
类型
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常,方法递
归调用产生这种结果。
如果 Java 虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成
扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么 Java 虚拟机将抛出一
OutOfMemory 异常。 ( 线程启动过多 )
参数 -Xss 去调整 JVM 栈的大小
2. 详解 JVM 内存模型
JVM 内存结构
程序计数器:当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程
私有。
Java 虚拟栈:存放基本数据类型、对象的引用、方法出口等,线程私有。
Native 方法栈:和虚拟栈相似,只不过它服务于 Native 方法,线程私有。
Java 堆: java 内存最大的一块,所有对象实例、数组都存放在 java 堆, GC 回收的地方,线程共享。
方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。(即永久带),
回收目标主要是常量池的回收和类型的卸载,各线程共享
3.JVM 内存为什么要分成新生代,老年代,持久代。新生代中为什么
要分为 Eden Survivor
1 )共享内存区划分
共享内存区 = 持久带 +
持久带 = 方法区 + 其他
Java = 老年代 + 新生代
新生代 = Eden + S0 + S1
2 )一些参数的配置
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ,可以通过参数 –XX:NewRatio
置。
默认的, Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 )
Survivor 区中的对象被复制次数为 15( 对应虚拟机参数 -XX:+MaxTenuringThreshold)
3) 为什么要分为 Eden Survivor? 为什么要设置两个 Survivor 区? 如果没有 Survivor Eden 区每进行一次 Minor GC ,存活的对象就会被送到老年代。老年代很快被
填满,触发 Major GC. 老年代的内存空间远大于新生代,进行一次 Full GC 消耗的时间比 Minor 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 space
S1 (这个过程非常重要,因为这种复制算法保证了 S1 中来自 S0 Eden 两部分的存活对象占用连续
的内存空间,避免了碎片化的发生)
4. JVM 中一次完整的 GC 流程是怎样的,对象如何晋升到老年代
Java = 老年代 + 新生代
新生代 = Eden + S0 + S1
Eden 区的空间满了, Java 虚拟机会触发一次 Minor GC ,以收集新生代的垃圾,存活下来的对
象,则会转移到 Survivor 区。
大对象 (需要大量连续内存空间的 Java 对象,如那种很长的字符串) 直接进入老年态
如果对象在 Eden 出生,并经过第一次 Minor GC 后仍然存活,并且被 Survivor 容纳的话,年龄设为
1 ,每熬过一次 Minor GC ,年龄 +1 若年龄超过一定限制( 15 ),则被晋升到老年态 。即 长期存
活的对象进入老年态
老年代满了而 无法容纳更多的对象 Minor GC 之后通常就会进行 Full GC Full GC 清理整个内存
包括年轻代和年老代
Major GC 发生在老年代的 GC 清理老年区 ,经常会伴随至少一次 Minor GC Minor GC 10
倍以上
5. 你知道哪几种垃圾收集器,各自的优缺点,重点讲下 cms G1 ,包
1 )几种垃圾收集器:
Serial 收集器: 单线程的收集器,收集垃圾时,必须 stop the world ,使用复制算法。
ParNew 收集器: Serial 收集器的多线程版本,也需要 stop the world ,复制算法。
Parallel Scavenge 收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达
到一个可控的吞吐量。如果虚拟机总共运行 100 分钟,其中垃圾花掉 1 分钟,吞吐量就是 99%
Serial Old 收集器: Serial 收集器的老年代版本,单线程收集器,使用标记整理算法。
Parallel Old 收集器: Parallel Scavenge 收集器的老年代版本,使用多线程,标记 - 整理算法。
CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,
记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除 ,收集结束会产生大量空间碎
片。
G1 收集器: 标记整理算法实现, 运作流程主要包括以下:初始标记,并发标记,最终标记,筛选
标记 。不会产生空间碎片,可以精确地控制停顿。
2 CMS 收集器和 G1 收集器的区别:
CMS 收集器是老年代的收集器,可以配合新生代的 Serial ParNew 收集器一起使用;
G1 收集器收集范围是老年代和新生代,不需要结合其他收集器使用; CMS 收集器以最小的停顿时间为目标的收集器;
G1 收集器可预测垃圾回收的停顿时间
CMS 收集器是使用 标记 - 清除 算法进行的垃圾回收,容易产生内存碎片
G1 收集器使用的是 标记 - 整理 算法,进行了空间整合,降低了内存空间碎片。
6.JVM 内存模型的相关知识了解多少,比如重排序,内存屏障,
happen-before ,主内存,工作内存。
1 Java 内存模型图:
Java 内存模型规定了所有的 变量都存储在主内存 中,每条 线程还有自己的工作内存 ,线程的工作内存中
保存了该线程中是用到的变量的主内存副本拷贝, 线程对变量的所有操作都必须在工作内存中 进行,
不能直接读写主内存 。不同的线程之间也 无法直接访问对方工作内存中的变量 ,线程间变量的传递均需
要自己的工作内存和主存之间进行数据同步进行。
2 )指令重排序。
在这里,先看一段代码
运行结果可能为 (1,0) (0,1) (1,1) ,也可能是 (0,0) 。因为,在实际运行时,代码指令可能并不是严格按
照代码语句顺序执行的。大多数现代微处理器都会采用将指令乱序执行( out-of-order execution ,简
OoOE OOE )的方法,在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取
下一条指令所需数据时造成的等待 3 。通过乱序执行的技术,处理器可以大大提高执行效率。而这就是
指令重排
3 )内存屏障
内存屏障 ,也叫内存栅栏,是一种 CPU 指令,用于控制特定条件下的重排序和内存可见性问题。
LoadLoad 屏障 :对于这样的语句 Load1; LoadLoad; Load2 ,在 Load2 及后续读取操作要读取的
数据被访问前,保证 Load1 要读取的数据被读取完毕。
StoreStore 屏障 :对于这样的语句 Store1; StoreStore; Store2 ,在 Store2 及后续写入操作执行
前,保证 Store1 的写入操作对其它处理器可见。
LoadStore 屏障 :对于这样的语句 Load1; LoadStore; Store2 ,在 Store2 及后续写入操作被刷出
前,保证 Load1 要读取的数据被读取完毕。
StoreLoad 屏障 :对于这样的语句 Store1; StoreLoad; Load2 ,在 Load2 及后续所有读取操作执行
前,保证 Store1 的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实
现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
4 happen-before 原则
单线程 happen-before 原则 :在同一个线程中,书写在前面的操作 happen-before 后面的操作。
锁的 happen-before 原则:同一个锁的 unlock 操作 happen-before 此锁的 lock 操作。
volatile happen-before 原则 :对一个 volatile 变量的写操作 happen-before 对此变量的任意操
( 当然也包括写操作了 )
happen-before 的传递性原则 :如果 A 操作 happen-before B 操作, B 操作 happen-before C
作,那么 A 操作 happen-before C 操作。
线程启动的 happen-before 原则 :同一个线程的 start 方法 happen-before 此线程的其它方法。
线程中断的 happen-before 原则 :对线程 interrupt 方法的调用 happen-before 被中断线程的检测
到中断发送的代码。
线程终结的 happen-before 原则: 线程中的所有操作都 happen-before 线程的终止检测。
对象创建的 happen-before 原则: 一个对象的初始化完成先于他的 finalize 方法调用。
7. 简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。
1) 什么是类加载器?
类加载器 就是根据指定全限定名称将 class 文件加载到 JVM 内存,转为 Class 对象。
启动类加载器( Bootstrap ClassLoader ):由 C++ 语言实现(针对 HotSpot , 负责将存放在
<JAVA_HOME>\lib 目录或 -Xbootclasspath 参数指定的路径中的类库加载到内存中。
其他类加载器:由 Java 语言实现,继承自抽象类 ClassLoader 。如:
扩展类加载器( Extension ClassLoader ):负责加载 <JAVA_HOME>\lib\ext 目录或
java.ext.dirs 系统变量指定的路径中的所有类库。
应用程序类加载器( Application ClassLoader )。负责加载用户类路径( classpath )上
的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载
器默认就是用这个加载器。
2 )双亲委派模型
双亲委派模型工作过程是:
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派
给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的
类时(即 ClassNotFoundException ),子加载器才会尝试自己去加载。
双亲委派模型图:
3 )为什么需要双亲委派模型?
在这里,先想一下,如果没有双亲委派,那么用户是不是可以 自己定义一个 java.lang.Object 的同名
java.lang.String 的同名类 ,并把它放到 ClassPath , 那么 类之间的比较结果及类的唯一性将无法
保证 ,因此,为什么需要双亲委派模型? 防止内存中出现多份同样的字节码
4 )怎么打破双亲委派模型?
打破双亲委派机制则不仅 要继承 ClassLoader 类,还要 重写 loadClass findClass 方法。
8. 说说你知道的几种主要的 JVM 参数
1 )堆栈配置相关
-Xmx3550m 最大堆大小为 3550m
-Xms3550m 设置初始堆大小为 3550m
-Xmn2g 设置年轻代大小为 2g
-Xss128k 每个线程的堆栈大小为 128k
-XX:MaxPermSize 设置持久代大小为 16m
-XX:NewRatio=4: 设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)。
-XX:SurvivorRatio=4 设置年轻代中 Eden 区与 Survivor 区的大小比值。设置为 4 ,则两个 Survivor
与一个 Eden 区的比值为 2:4 ,一个 Survivor 区占整个年轻代的 1/6
-XX:MaxTenuringThreshold=0 设置垃圾最大年龄。如果设置为 0 的话,则年轻代对象不经过
Survivor 区,直接进入年老代。
2 )垃圾收集器相关
-XX:+UseParallelGC 选择垃圾收集器为并行收集器。
-XX:ParallelGCThreads=20 配置并行收集器的线程数
-XX:+UseConcMarkSweepGC 设置年老代为并发收集。
-XX:CMSFullGCsBeforeCompaction :由于并发收集器不对内存空间进行压缩、整理,所以运行一段
时间以后会产生 碎片 ,使得运行效率降低。此值设置运行多少次 GC 以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection 打开对年老代的压缩。可能会影响性能,但是可以消除碎
3 )辅助信息相关
-XX:+PrintGC 输出形式 :
[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K),
0.0650971 secs]
-XX:+PrintGCDetails 输出形式 :
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633
secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K),
0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs
9. 怎么打出线程栈信息。
可以说一下 jps top jstack 这几个命令,再配合一次排查线上问题进行解答。
输入 jps ,获得进程号。
top -Hp pid 获取本进程中所有线程的 CPU 耗时性能
jstack pid 命令查看当前 java 进程的堆栈状态
或者 jstack -l > /tmp/output.txt 把堆栈信息打到一个 txt 文件。
可以使用 工具 堆栈定位
10. 强引用、软引用、弱引用、虚引用的区别?
1 )强引用
我们平时 new 了一个对象就是强引用,例如 Object obj = new Object(); 即使在内存不足的情况下, JVM
宁愿抛出 OutOfMemory 错误也不会回收这种对象。
2 )软引用
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会
回收这些对象的内存。
3 )弱引用
具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦
发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
4 )虚引用
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚
引用主要用来跟踪对象被垃圾回收器回收的活动。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值