Jvm内存面试问题总结(包括堆外内存)
逆水行舟,不进则退. 再不努力就被淘汰了.
ps: 本文为纯手打学习笔记 ,在摸索中学习 , 只为分享知识 , 请勿随意转载. 如有我理解错误的地方, 请各路大佬务必留言指导 .
附一份word版分享连接.
https://download.csdn.net/download/whx217/13092323
顺便安利两个笔记软件Typora 和 幕布. 只用电脑端建议Typora, 感觉唯一的缺点就是没有手机端. 幕布最大的优势就是支持思维导图.
- jvm中有哪几块内存?
- 堆内存
- 放对象
- 所有线程共享
- 栈内存
- 放代码的线程。
- 线程独有,自由有自己的栈内存。
- 永久代(java8之前)
- 放我们的类
- 放常量池
- 线程计数器
- 本地方法栈
- 放动态库
- 堆内存
- 堆内存是如何分配的?
- 年轻代(默认情况下eden区和s1 / s2区的比例是 8:1:1)
- eden区
- Survivor 区(两个Survivor区大小完全一样)
- Survivor 1 区
- Survivor 2 区
- 年老代(默认和年轻代一样)
- 年轻代(默认情况下eden区和s1 / s2区的比例是 8:1:1)
- java8之后对内存分布做了什么改进?
- 主要是 永久代里面的变化
- 本来永久代中放了一些类和常量池
- java8以后永久代就没有了
- 将常量放入了堆里面
- 将类放入了metaspace中
- metaspace是不占用jvm内存空间的,占用的是本地空间。
- 主要是 永久代里面的变化
- jvm是如何运行起来的?
- 1 类加载器 先加载war、jar中的class,将我们的类加载到metaspace中
- 2.spring容器会基于反射技术将metaspace中的类创建出一个实例bean。将bean加载到堆内存中。
- 3. 当线程处理请求的时候,在自己线程独享的内存空间中,每个方法拿到一个栈帧 ,局部变量全部放到栈帧里面去,同时局部变量会去堆内存中引用一个对象,方法层层嵌套,进行压栈。
- jvm在什么情况下会触发gc垃圾回收?
- 1. 如果说eden满了,必然会触发gc垃圾回收。youngGC 简称ygc
- 此时回收没有人引用的对象。
- 静态变量/局部变量引用不会被回收。
- 被引用的对象不会被回收。
- 1. 如果说eden满了,必然会触发gc垃圾回收。youngGC 简称ygc
- 常见的垃圾回收算法都有什么?
- 引用计数法
- 通过引用计数器标记占内存中的变量对堆内存中变量的引用数量,当gc垃圾回收的时候,发现引用计数器中的值是0 ,则被回收.
- 引用计数法的缺点 : 无法解决循环引用的问题.
- 标记清除法
- 降垃圾回收分成两个阶段, 标记阶段和 清除阶段
- 标记阶段,jvm会暂停jvm中的所有线程并开启GC线程. 有一个最大的ROOT对象, 从ROOT对象开始,依次标记,最终没有指向ROOT对象的对象 就是垃圾对象.
- 清除阶段 , 清除垃圾对象 .
- 标记清除法优缺点: 解决的了引用计数算法中的循环引用的问题. . 但是每次GC都要扫描全部对象,并且要暂停所有线程, 效率较低 , 同时会产生内存碎片.
- 标记整理法 (JDK1.8 老年代 GC垃圾回收)
- 在标记清除算法上做了优化, 第一阶段标记阶段是一样的.
- 整理阶段, 把所有被标记的对象全部压缩到内存的一端.
- 清理阶段 , 清除所有未被整理的垃圾对象.
- 优缺点: 解决了内存碎片化的问题, 但是比标记清除算法更加消耗cpu . 因为中间多了一步对对象的移动这个操作.
- 复制算法(JDK1.8 年轻代 GC垃圾回收 Survivor区)
- 两块内存大小一样的内存空间,在使用的使用依次使用其中的一个,另一个清除.留做下次使用.如此循环.
- 优缺点:在垃圾对象多的时候,效率高,并且在清理后内存无碎片. 但是垃圾对象较少的时候不适用,因为频繁复制,消耗资源. 另一个缺点就是内存只能使用50% .
- 分代算法
- 根据垃圾回收的特点选择不同的算法.
- 老年代 GC垃圾回收
- 年轻代 GC垃圾回收 Eden + Survivor ( 0 / 1 ) 区
- 引用计数法
- 年轻代垃圾回收算法?YGC
- 对于年轻代而言,大部分对象的生存周期都很短。eden大部分对象都是垃圾!~
- 对于年轻代的垃圾回收算法,是复制算法,每次垃圾回收,将没有被回收的对象复制到一个s区,然后将eden区整个清空。完成一次young GC。下一次ygc 时,将eden区和刚刚的s区中的对象筛选一次,将筛选的结果复制存入空白是s区,此时将刚刚被筛选的eden区和s区中的对象全部清空。完成又一次yGC。总的说就是两个s区和eden区进行配合重复完成ygc。
- 对于年轻代而言,大部分对象的生存周期都很短。eden大部分对象都是垃圾!~
- 什么时候年轻代中的对象会跑到老年代中呢?
- 1 . 对象熬过了很多次垃圾回收。大概是熬过15次垃圾回收。
- 2 . 某次垃圾回收的时候,发现s区放不下的对象。直接放入老年代。
- 3. 创建对象的时候,发现对象是大对象,直接放入老年代。防止ygc时反复移动这个大对象。大概大于100KB,都算作大对象。
- 老年代的垃圾回收算法是什么?OGC
- 老年代中的对象很多都是被长期引用的,尤其是spring管理的各种bean。
- 标记-清除算法有什么缺点:缺点是容易出现很多的内存碎片。
- 标记-整理算法,将老年代中的存活对象标记起来,移动到一起,存活对象压缩到一片内存空间中,其余的空间全部算垃圾对象,一起清理掉。解决了内存碎片产生的问题。完成了一次OGC。
- 常用的垃圾回收器都有什么?
- 串行垃圾回收器 (DefNew)(老年代)
是指使用单线程进行垃圾回收,在垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停,等待垃圾回收完成. 这种现象称为 STW (stop - the - world)
-
- 并行垃圾回收器(ParNew)(年轻代)
在串行垃圾回收器上进行了改进,将串行改成了并行,性能更高.也会出现STW.只是速度更快, 停顿的时间更短.
-
- parallelGC垃圾回收器
- 在parnew回收器的基础上,又新增了系统吞吐量等相关参数.
- 可自动调整堆内存分配, 但是不常用.
- CMS垃圾回收器(针对老年代)
- 优点; 在做垃圾回收的时候 , 让程序尽量较少STW.
- 初始化标记 - 并发标记 - 预处理 - 最终标记 - 并发清理 - 调整堆大小 - 重置 - 初始化标记 ... ...
- parnew (ygc) + cms(ogc) jdk8以及以前。
- [重要]G1 垃圾回收器 直接进行分带回收 、 新版本主推。java9开始变为默认的垃圾回收器.。
- 在做jvm性能调优的时候, 也建议使用G1垃圾回收. 能大大简化性能调优.
- 三种垃圾回收模式 : Young GC / Mixed GC /Full GC
- 调优三大步:
- 第一步: 开启G1垃圾回收器
- 第二步: 设置堆的最大内存
- 第三步:设置最大的停顿时间
- G1垃圾回收器的原理 :
- 取消了年轻代和老年代的物理划分 , 取而代之的一个逻辑上的划分 (区域)Region. 这样我们无需去设置每一个区域的大小.
- YGC时 , 还是使用复制算法.
- Remembered Set (已记忆集合)
- 在YGC时,G1垃圾回收器是如何找到ROOT根对象的 ?ROOT根对象即有可能在年轻代也有可能在老年代。
- G1 垃圾回收器的相关参数
- -XX:UseG1GC
- 使用G1 GC进行垃圾回收
- -XX:MaxGCPauseMillis
- 设置期望达到的最大GC停顿时间STW的指标,默认值是200毫秒.(jvm会去协调,但并不能保证完全做到)
- -XX:G1HeapRegionSize=n
- 设置G1区域的大小,也就是一个Region的大小 ,值是2的幂,范围是1M-32M .目标是根据最小的java堆大小划分出约2018个区域。
- 默认值是堆内存的1/2000.
- -XX:ParallelGCThread=n
- 设置STW工作线程数的值 。将n的值设置为CPU处理器线程的数量。n的值与处理器线程数的数量相同,最多为8 。
- -XX:ConcGCThreads=n
- 设置并行标记的线程数。将n的值设置为ParallelGCThread的1/4左右比较合适。
- -XX:InitiatingHeapOccupancyPercent=n
- 设置触发标记周期的java堆占用率阈值。也就是触发MxiedGC的标准,默认占用率是整个java堆的45%。
- -XX:UseG1GC
- G1垃圾回收器官方给出的优化建议
- 1 避免设置年轻代相关参数
- 避免使用-Xmn选项或-XX:NewRatio等其他相关选项设置年轻代的大小,如果设置的话, 会覆盖掉G1垃圾回收器的相关参数,影响G1垃圾回收器的性能。因为G1垃圾回收器是把内存划分成多个Region。
- 2 暂停的时间目标不要太过于苛刻。
- G1 GC的吞吐量的目标是90%应用程序,10%垃圾回收。如果STW时间过短,会影响吞吐量。
- 1 避免设置年轻代相关参数
- Mxied GC 是一个混合的GC 。
- Mxied GC 在什么时候出发? 当老年代的大小占整个堆大小的45%的时候, 会触发Mxied GC 。
- Mxied GC 的回收步骤。
- 全局并发标记
- 拷贝存活对象
- 在做jvm性能调优的时候, 也建议使用G1垃圾回收. 能大大简化性能调优.
- 老年代的回收比年轻代回收平均慢10倍。
- 在进行垃圾回收的时候,所有的线程都是在等待的。
- parallelGC垃圾回收器
- 可视化GC日志分析工具
- 打印GC日志需要设置的参数
- -XX:+PrintGC 输出GC日志
- -XX:+PrintGCDetails 输出GC详细日志
- -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准形式)
- -XX:+PrintGCDateStamps 输出GC的时间戳(以日期形式 yyyyMMdd等)
- -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
- 打印GC日志需要设置的参数
- 如何设置jvm参数?
- 根据数据量,预估 eden区一秒会创建多少对象。
- eden区满时触发YGC时,s区能有多少存活的对象。
- 老年代多久会被填满。
- 如何检查jvm运行情况?
- 通过 jstat工具去分析jvm运行情况。
- 主要观察ygc的频率,s区能否放得下。
- 老年代几次ygc后会被填满。多久会触发一次ogc。
- 当老年代满了以后,会触发一次FGC,(full GC)同时包含YGC/OGC/metaspaceGC,全员GC。
- 压测,观察QPS,观察cpu
- 如果gc过于频繁,说明堆内存设置不够。
- 项目中JVM是如何优化的?
- 需要对自己生产环境的jvm进行分析。
- 压测,每秒单机500并发。
- 实际操作过才行!~
- 如何排查和处理线上系统的OOM问题?Out Of Memory
- 内存溢出
- 设置参数,一旦发生OOM时,导出内存快照。
- 通过内存快照,找出占用内存过大的对象是谁,在哪些代码中。
- 流程图
- 补充:堆外内存(off-heap)
- 说说你对堆外内存的理解?
- 通过堆外内存将数据发送出去。
- 堆外内存的数据是jvm内存中拷贝出去的。
- 如何使用堆外内存?
- ByteBuffer buffer = ByteBuffer.allocateDirect(1024)
- 堆外内存的优势是什么?
- 可以把数据直接写入到堆外内存中去,可以在发送数据的时候减少一次拷贝。
- 堆外内存是如何进行分配和回收的?
- DirectByteBuffer 从jvm堆内存中引用堆外内存。
- 可以有多个DirectByteBuffer,去引用多个堆外内存。
- 可以通过jvm参数设置你最大的堆外内存的大小
- 如果去申请堆外内存时,剩余的内存空间已经够了,那么 ,会经历一次gc垃圾回收,回收掉那些没用的DirectByteBuffer,释放掉那些DirectByteBuffer引用的堆外内存。
- 如果在尝试9次之后,还是没有堆外内存释放。那么抛出异常。
- DirectByteBuffer 从jvm堆内存中引用堆外内存。
- 堆外内存会内存溢出吗?OOM
- 会, 一直申请堆外内存,而没有堆外内存释放,在尝试9次以后,就会抛出oom异常。