盒子
盒子
文章目录
  1. 1. 什么情况下会发生栈内存溢出
  2. 2. JVM的内存结构,Eden和Survivor的比例
  3. 3. JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor
  4. 4. JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的JVM参数
  5. 5. 你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点
  6. 6. 垃圾回收算法的实现原理
  7. 7. 当出现了内存溢出,你怎么排错
  8. 8. JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存等
  9. 9. 简单说说你了解的类加载器,可以打破双亲委派么,怎么打破
  10. 10. 你们线上应用的JVM参数有哪些
  11. 11. g1和cms区别,吞吐量优先和响应优先的垃圾收集器选择
  12. 12. 怎么打出线程栈信息

腾讯篇之JVM

1. 什么情况下会发生栈内存溢出

栈分配空间太小,或执行的方法递归层数太多创建了太多的栈帧导致溢出
解决方案:配置-Xss参数增加线程栈大小,优化程序也至关重要

2. JVM的内存结构,Eden和Survivor的比例

内存结构:

  • 堆:存放对象
  • 方法区: 存放类和变量
  • JAVA虚拟机栈:存放运行时栈帧
  • 本地方法栈
  • 程序计数器

Eden区是一块,Survivor是两块,均属于堆中的新生代
Eden和Survivor的比例是8:1:1,可以通过-XX:SurvivorRatio来设定

JDK1.8后取消了方法区,将永久代放到了本地内存里面

3. JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor

  • 共享内存区 = 持久代 + 堆
  • 持久代 = 方法区 + 其他
  • 堆 = 新生代 + 老年代 (1:2通过-XX:NewRatio设置)
  • 新生代 = Eden + S0 + S1(8:1:1通过-XX:SurvivorRatio设置)

Survivor具有预筛选保证,只有对象经历了16次Minor GC才会被送到老年代,Survivor可以减少被送到老年代的对象,进而减少Full GC发生
有两个Survivor,在Minor GC后,可以保证一个为空,另一个非空且无内存碎片

4. JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的JVM参数

完整GC流程:

  • 当Eden区满了,JVM触发一次Minor GC,以收集新生代的垃圾,存活下来的对象,会被转移到Survivor区
  • 大对象(如很长的字符串)直接进入老年代
  • 如果对象在Eden出生,每经历过一次MinorGC,并且被Survivor容纳的话,年龄加1,直到年龄超过15,就会进入老年代
  • Major GC发生在老年代,通常会伴随着至少一次Minor GC,比Minor GC慢10倍以上

-Xss: 栈容量
-Xms:设置最小堆内存
-Xmx:设置最大堆内存
-Xmn10M:设置新生代10M
-XX:SurvivorRatio=8:设置Eden、Survivor比例8:1
-XX:PermSize=32M:永久代最小内存32M
-XX:MaxPermSize=64M:永久代最大扩展内存64M
-XX:+HeapDumpOnOutOfMemoryError:堆内存溢出时Dump出当前的内存堆转储快照以便事后分析

5. 你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点

  • Serial收集器:新生代单线程的收集器,收集垃圾时,必须stop the world,使用复制算法
  • ParNew收集器:新生代Serial多线程版本收集器,也需要stop the world,使用复制算法
  • Paraller Scavenge收集器:新生代并发的多线程收集器,目标达到一个可控的吞吐量,使用复制算法

  • Serial Old收集器: 是Serial收集器的老年代版本,单线程,使用标记整理算法

  • Parallel Old收集器: Parallel Scavenge收集器的老年代版本,多线程,使用标记整理算法
  • CMS收集器:是一种以最短回收停顿时间为目标的老年代收集器,使用标记清除算法,运行过程:初始标记-并发标记-重新标记-并发清除,收集后会产生大量空间碎片
  • G1收集器: 可作用于新生代与老年代,是标记整理算法,运行过程:初始标记-并发标记-最终标记-筛选标记,不会产生碎片,可以精确的控制停顿
CMS收集器 G1收集器
算法类型 标记-清除 标记-整理
收集范围 老年代 新生代、老年代
目标 最短的停顿时间为目标 可预测的垃圾回收时间

6. 垃圾回收算法的实现原理

  • 引用计数法 堆中每个对象拥有一个引用计数。被引用一次,计数加1,被引用变量值变为null,则计数减1,直到计算为0,则表示变为无用对象。缺点是无法识别循环引用对象
  • 引用可达法(根搜索算法) 从一个节点GC ROOT开始,寻找一个引用的节点,找到后,继续寻找这个节点的引用节点,寻找完毕后,剩余的节点则认为是没有被引用的对象

7. 当出现了内存溢出,你怎么排错

JVM除了程序计数器其他区域都可能发生内存溢出

堆溢出OutOfMemoryError
用visualVM工具分析堆快照(-XX:+HeapDumpOnOutOfMemoryError)
如果发生内存泄漏
1) 找到泄漏的对象 2)找到泄漏对象的GC ROOT 3)根据泄漏对象和GC ROOT找到导致内存泄漏的代码 4) 设法排除泄漏对象和GC ROOT的连接
如果不存在内存泄漏,看下能否增大JVM堆的容量

栈溢出
一般由于递归,导致栈空间不足,发生OutOfMemoryError:Java heap space说明运行时常量池移到了堆中

方法区溢出
方法区是存放类的地方。如果多个项目有多个相同jar,且都在WEB-INF/lib下,则每个项目都会加载一遍jar,会导致方法区有大量相同类,又不会被GC,则可建立共享lib库,否则尝试增加-XX:MaxPermSize

8. JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存等

思路: 先画出Java内存模型图,结合例子volatile ,说明什么是重排序,内存屏障,最好能给面试官写以下demo说明。

1)Java内存模型图:
Java模型规定了所有的变量都存储在主内存中,每个线程线程有自己的工作内存,线程的工作内存保存了所使用变量主内存的副本拷贝。
线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。
不同线程之间无法访问对方工作内存的变量,线程间变量传递依赖于主内存。

2)指令重排
处理器将指令乱序执行,可以大大提高执行效率,这就是指令重排

3)内存屏障
内存屏障,也叫内存栅栏,是一种CPU指令,用于控制特定条件下的重排序与内存可见性问题

  • LoadLoad屏障: 对于语句Load1;LoadLoad;Load2,在Load2及后续读取操作要读取的数据被访问前,保证load1要读取的数据被读取完毕
  • StoreStore屏障:对于语句Store1;StoreStore;Store2,在Store2及后续写入操作执行时,保证Store的写入操作对其他处理器可见
  • LoadStore屏障: 对于语句Load1;LoadStore;Store2,在Store2及后续写入操作执行时,保证Load要读取的数据被读取完毕
  • StoreLoad屏障: 对于语句Store1;StoreLoad;Load1,在Load及以后所有的读取操作执行前,保证Store1的写入操作对所有处理器可见。它的开销是最大的,这个屏障是个万能屏障,兼具其他三种屏障的功能

4)happen-before原则

happens-before规则

9. 简单说说你了解的类加载器,可以打破双亲委派么,怎么打破

思路: 先说明一下什么是类加载器,可以给面试官画个图,再说一下类加载器存在的意义,说一下双亲委派模型,最后阐述怎么打破双亲委派模型。

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方法

10. 你们线上应用的JVM参数有哪些

思路: 可以说一下堆栈配置相关的,垃圾收集器相关的,还有一下辅助信息相关的。

1) 堆栈配置相关

1
2
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurviorRatio=4 -XX:MaxTenuringThreshold=0

-Xmx3550m: 最大堆大小为3550m
-Xms3550m: 最小堆大小为3550m
-Xmn2g: 设置新生代大小为2g
-Xss128k: 设置每个线程的栈大小为128K
-XX:MaxPermSize: 设置永久代的大小为16m
-XX:NewRatio: 设置新生代(包括Eden和两个Survivor区)与老年代的比值(除去永久代)
-XX:SurvivorRatio: 设置新生代Eden与Survivor区的大小比值,设置为4,则两个Survivor区与Eden区比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxTenuringThreshold: 设置垃圾最大年龄。如果设置为0的话,则新生代不经过Survivor区,直接进入老年代

2)垃圾收集器相关

1
2
3
4
5
-XX:+UseParallelGC # 选择垃圾收集器为并行收集器
-XX:ParallelGCThreads=20 # 配置并行收集器的线程数
-XX:+UseConcMarkSweepGC # 设置老年代为并发收集
-XX:CMSFullGCsBeforeCompaction=5 # 由于CMS收集器不对内存空间进行压缩整理,所有会产生内存碎片。此值设置运行5次Full GC后对内存空间压缩整理
-XX:+UseCMSCompactAtFullCollection # 打开对老年代的压缩,可能会影响性能,但是可以消除碎片

3)辅助信息相关

1
2
-XX:+PrintGC
-XX:+PrintGCDetails

-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

11. g1和cms区别,吞吐量优先和响应优先的垃圾收集器选择

详见5

12. 怎么打出线程栈信息

思路: 可以说一下jps,top ,jstack这几个命令,再配合一次排查线上问题进行解答。

  • 首先执行jps,获取进程号
  • 然后执行top -Hp pid,获取本进程所有线程的CPU耗时性能
  • jstack pid,查看当前java进程的堆栈信息
  • 或者 jstack -l /tmp/output.txt 把堆栈信息打到一个txt文件
  • 可以使用fastthread进行堆栈定位
支持一下
扫一扫,支持沈健
  • 微信扫一扫
  • 支付宝扫一扫