Jvm内存面试问题总结(包括堆外内存)
-
jvm中有哪几块内存?堆内存
-
放对象
-
所有线程共享
-
栈内存
-
放代码的线程。
-
线程独有,自由有自己的栈内存。
-
永久代(java8之前)
-
放我们的类
-
放常量池
-
线程计数器
-
本地方法栈
-
放动态库
-
堆内存是如何分配的?
-
年轻代(默认情况下eden区和s1 / s2区的比例是 8:1:1)eden区Survivor 区(两个Survivor区大小完全一样)Survivor 1 区Survivor 2 区
-
年老代(默认和年轻代一样)
-
java8之后对内存分布做了什么改进?
-
主要是 永久代里面的变化
-
本来永久代中放了一些类和常量池
-
java8以后永久代就没有了将常量放入了堆里面将类放入了metaspace中
-
metaspace是不占用jvm内存空间的,占用的是本地空间。
-
jvm是如何运行起来的?
-
1 类加载器 先加载war、jar中的class,将我们的类加载到metaspace中
-
2.spring容器会基于反射技术将metaspace中的类创建出一个实例bean。将bean加载到堆内存中。
-
3. 当线程处理请求的时候,在自己线程独享的内存空间中,每个方法拿到一个栈帧 ,局部变量全部放到栈帧里面去,同时局部变量会去堆内存中引用一个对象,方法层层嵌套,进行压栈。
-
jvm在什么情况下会触发gc垃圾回收?
-
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。
-
什么时候年轻代中的对象会跑到老年代中呢?
-
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%。G1垃圾回收器官方给出的优化建议1 避免设置年轻代相关参数避免使用-Xmn选项或-XX:NewRatio等其他相关选项设置年轻代的大小,如果设置的话, 会覆盖掉G1垃圾回收器的相关参数,影响G1垃圾回收器的性能。因为G1垃圾回收器是把内存划分成多个Region。2 暂停的时间目标不要太过于苛刻。G1 GC的吞吐量的目标是90%应用程序,10%垃圾回收。如果STW时间过短,会影响吞吐量。Mxied GC 是一个混合的GC 。Mxied GC 在什么时候出发? 当老年代的大小占整个堆大小的45%的时候, 会触发Mxied GC 。Mxied GC 的回收步骤。全局并发标记拷贝存活对象
-
老年代的回收比年轻代回收平均慢10倍。
-
在进行垃圾回收的时候,所有的线程都是在等待的。
-
可视化GC日志分析工具
-
打印GC日志需要设置的参数
-
-XX:+PrintGC 输出GC日志
-
-XX:+PrintGCDetails 输出GC详细日志
-
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准形式)
-
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期形式 yyyyMMdd等)
-
-XX:+PrintHeapAtGC 在进行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次之后,还是没有堆外内存释放。那么抛出异常。
-
堆外内存会内存溢出吗?OOM
-
会, 一直申请堆外内存,而没有堆外内存释放,在尝试9次以后,就会抛出oom异常
-
如果有任何疑问,可以加我的QQ(2449659175)了解更多哦。