尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

热门八股-JVM

热门八股-JVM 内存结构1.介绍一下JVM内存模型JVM内存模型通常指JVM运行时数据区主要包括程序计数器记录当前线程执行到哪条字节码指令Java虚拟机栈每个线程私有方法调用时创建栈帧本地方法栈给native方法使用堆线程共享主要存放对象实例是GC管理的重点区域方法区线程共享存放类元信息、常量、静态变量、JIT编译后的代码运行时常量池方法区的一部分存放字面量和符合引用直接内存不属于JVM运行时数据区但经常被NIO使用可能OOM。2.String s new String(abc)执行过程中分别对应哪些内存区域分布在3处内存区域堆、字符串常量池、虚拟机栈abc是字符串字面量会放入字符串常量池new String(abc)执行new关键字在堆中创建一个新的String对象s局部变量存在当前方法的虚拟机栈栈帧里s最终指向堆里新建的String对象创建几个对象如果常量池之前没有abc可能会创建两个对象常量池里的abc和堆里的new String对象如果常量池之前有abc只会在堆里新建一个String对象3.堆为什么分新生代和老年代比例默认是多少堆分代是因为大多数对象都有“朝生夕死”的特点。很多对象创建后很快就不用了少数对象会存活很久。如果把所有对象混在一起回收每次GC都扫描整个堆成本很高。新生代存放新创建、生命周期短的对象老年代存放存活时间长、体积较大的对象常见默认内存比例新生代老年代12新生代内部划分Eden区Survivor0Survivor18:1:14.Eden、From、To区作用为什么会有两个Survivor新生代一般分为Eden、From Survivor、ToSurvivor。大多数对象先在Eden区分配。Minor GC时Eden和当前From区中存活的对象会被复制到To区然后From和To角色互换两个Survivor的作用配合复制算法使用让存活对象在两个Survivor之间来回复制记录对象年龄达到阈值后晋升老年代为什么不是一个Survivor复制算法需要一个空区域作为目标空间。如果只有一个Survivor清理和复制会混在一起不好保证空间连续和回收效率为什么不是多个Survivor两个已经足够完成复制和年龄判断更多会浪费空间5.方法区/元空间作用存放什么方法区是JVM规范中的概念用来存放类相关的信息。通常存放类的元信息比如类名、父类、接口、字段、方法运行时常量池静态变量JIT编译后的代码缓存JDK 8 以后HotSpot用元空间Metaspace实现方法区。元空间使用的是本地内存不再使用永久代6.永久代和元空间的区别JDK 8为什么废除永久代改用元空间永久代PermGen是JDK 8之前HotSpot对方法区的实现使用JVM堆内存的一部分元空间Metaspace是JDK 8之后的方法区实现使用本地内存为什么废除永久代永久代大小不好设置容易出现OutOfMemoryError:PermGen space类元信息放在本地内存中更加灵活受限更少方便HotSpot与其他JVM实现统一方法区概念字符串常量池等内容逐步移到堆中永久代的职责变的不合适。主要元空间不是无限的也可能OOM可以通过-XX:MaxMetaspace限制大小7.直接内存是什么会不会OOM直接内存Direct Memory也叫堆外内存不属于JVM运行时数据区是操作系统本地内存Java NIO的ByteBuffer,allocateDirect()会使用直接内存它减少了Java堆和native内存之间的数据拷贝提升IO读写性能适合IO场景。直接内存也会OOMOutOfMemoryError:Direct buffer memory大量direct buffer没有被释放Netty等框架使用直接内存过多-XX:MaxDirectMemorySize设置太小或没有规范类加载机制1.类加载的五步类加载通常分为五个阶段加载 Loading:通过类全限定名获取字节码生成Class对象验证 Verification:检查字节码是否合法防止破坏JVM准备 Preparation:静态变量分配内存赋默认零值解析 Resolution:把符号引用替换成直接引用初始化 Initialization:执行类构造器clinit()给静态变量赋真实值执行静态代码块2.什么是双亲委派机制双亲委派机制是类加载器加载类时的一种委派规则当一个类加载器收到类加载请求时不会自己先加载而是先把请求交给父类加载器。父类加载器还会继续向上委派。只有父类加载器加载不了子类加载器才会尝试自己加载它的核心思想是先让上层加载器加载基础类避免核心类被随意替换双亲委派机制的优点保证类的唯一性确保所有请求都能传到启动类加载器避免了不同类加载器重复加载相同类的情况保证了安全性由于Java的核心类库在启动类加载器中加载而启动类加载器只加载信任路径中的类这样可以防止不可信的类假冒核心类实现了类的复用核心类只需要加载一次所有子加载器复用减少内存消耗3.双亲委派机制的缺点是什么主要缺点不够灵活父加载器无法使用子加载器加载的类不适合某些插件化、热部署、模块隔离场景SPI场景下Java核心类需要加载第三方实现类双亲委派机制会受限制比如JDBC中DriverManager是核心类库里的类但具体数据库驱动由应用提供所以需要线程上下文类加载器来解决4.如何打破双亲委派机制打破双亲委派机制通常是自定义类加载器重写loadClass()或调整加载顺序默认情况下loadClass()实现了双亲委派。如果只重写findClass()一般仍然遵循双亲委派。要打破它通常会在loadClass()中改成自己先加载加载不了再交给父加载器。典型场景Tomcat Web应用隔离OSGi模块化插件系统热部署框架不要随意打破双亲委派机制可能导致类冲突、安全问题和类型转换异常。5.如何实现自定义类加载器常见做法是继承ClassLoader重写findClass()。垃圾回收机制1.如何判断对象是否存活可达性分析引用计数判断对象是否存活主要有两种思路引用计数法和可达性分析法引用计数法每个对象维护一个计数器有引用指向它就1引用失效就-1计数为0就可以回收。缺点是无法解决循环引用。比如对象A引用BB引用A但它们都不被外部访问计数仍然不为0.Java主流JVM使用可达性分析法可达性分析法从GC Roots出发沿引用链向下搜索。可以被GC Roots直接或间接到达的对象是存活对象无法到达的对象可以被回收。2.GC Roots有哪些常见的包括虚拟机栈中引用的对象。比如局部变量本地机栈中JNI引用的对象方法区中类静态属性引用的对象方法区中常量引用的对象被synchronized持有的对象JVM内部引用比如系统类加载器、基本数据类型Class对象。3.三大垃圾回收算法以及各自的优缺点标记-清除先标记存活对象再清楚未标记对象优点实现简单不需要移动对象。缺点会产生内存碎片回收效率不稳定标记-复制标记存活对象将所有存活对象复制到另一块空闲内存区域原来整块区域全部清空优点没有内存碎片分配块。缺点浪费一部分空间存活对象多时复制成本高标记-整理标记存活对象再把存活对象向一端移动然后清理边界外空间优点没有碎片适合老年代。缺点移动对象成本较高4.为什么新生代使用复制算法而老年代使用标记整理新生代对象大多数朝生夕死存活对象少。使用复制算法只需要复制少量存活对象效率很高。老年代对象存活率高如果用复制算法要复制大量对象成本很高而且还要额外保留一大块空闲区域5.新生代中的对象符合哪些条件之后会晋升到老年代常见情况对象年龄达到阈值默认最大常见值是15因为对象头中年龄字段通常是4bit大对象直接进入老年代比如很大的数组或字符串Survivor空间放不下存活对象会提前晋升动态年龄判断某个年龄及以上对象总大小超过Survivor一半时这些对象可能直接晋升6.什么是Minor GC、Major GC、Full GC触发条件Minor GC回收新生代。通常在Eden区空间不足时触发Major GC通常指回收老年代Full GC回收整个Java堆和方法区相关区域停顿时间通常更长常见Full GC触发条件老年代空间不足方法区或元空间不足调用System.gc()不一定立刻执行但可能触发Minor GC前判断老年代担保空间不足大对象分配失败面试重点说Minor GC比较频繁Full GC成本高线上尽量减少频繁Full GC。7.什么是STWSTW是Stop The World意思是垃圾回收时暂停用户线程GC过程中为了保证对象引用关系不再乱变JVM可能会暂停所有业务线程只让GC线程工作STW影响接口响应变慢、系统短暂停顿。停顿时间越长用户感知越明显现代垃圾收集器如G1,ZGC,Shenandoah都在尽量降低STW时间8.常见的垃圾收集器有哪些Serial单线程收集器适合客户端或小内存场景ParNewSerial的多线程版本常与CMS搭配Parallel Scavenage关注吞吐量适合后台计算机任务Serial Old老年代单线程收集器Parallel OldParallel Scavenage的老年代版本CMS低停顿老年代收集器使用标记-清楚可能产生碎片G1面向服务端应用按Region管理堆兼顾吞吐和低停顿ZGC超低延迟收集器。适合大内存低停顿场景Shenandoah低停顿收集器目标类似ZGC9.什么是三色标记法三色标记法是并发标记中最常用的对象标记思想它把对象分为三种颜色白色还没被访问最终仍是白色可能被回收灰色已经被访问但它引用的对象还没被扫描完黑色已经被访问并且它引用的对象也扫描完了GC从GC Root开始把对象从白变灰再从灰变黑逐步完成标记并发标记时用户线程还在运行引用关系可能变化所以需要写屏障和读屏障等机制保证标记正确。JVM实战调优1.常见的OOM有哪几种分别什么原因常见的OOM包括堆内存溢出java.lang.OutOfMemoryError:Java heap space原因通常是对象太多、缓存无界、集合持续增长内存泄漏元空间溢出java.lang.OutOfMemoryError:Metaspace原因通常是动态生成类太多比如CGLIB、反射代理、热部署类加载器泄漏直接内存溢出java.lang.OutOfMemoryError:Direct buffer memory原因通常是NIO、Netty直接内存使用过多或释放不及时栈溢出java.lang.StackOverflowError:原因通常是递归太深、方法调用链太长、栈帧过大2.常见JVM参数-Xms、-Xmx、-Xss、-XX:MetaspaceSize含义-Xms堆初始大小-Xmx堆最大大小生产中常把-Xms、-Xmx设置成一样避免运行时频繁扩缩容-Xss每个线程的栈大小。设置太大能创建的线程数变少设置太小容易栈溢出-XX:MetaspaceSize元空间初始触发GC的阈值不是最大值-XX:MaxMetaspaceSize元空间最大值-XX:MaxDirectMemorySize直接内存最大值-XX:HeapDumpOnOutOfMemoryErrorOOM时自动导出堆dump线上排查很有用。3.如何线上排查OOM完整排查流程先看错误类型是heap、metaspace、direct memory还是stack查看应用日志确认OOM发生时间和业务操作确认JVM参数比如堆大小元空间大小GC日志配置如果是堆OOM获取heap dump用MAT、VisualVM、JProfiler等工具分析dump查看大对象、对象数量、GC Roots引用链判断是内存泄漏还是瞬时流量导致内存不够修复代码或调整参数再压测验证注意生产dump可能很大抓取和拷贝要注意磁盘空间和业务影响4.如何排查频繁Full GC频繁Full GC常见原因老年代空间不足大对象频繁进入老年代内存泄漏对象回收不掉元空间不足显式调用System.gc()新生代太小导致对象过早晋升排查流程查看GC日志确认Full GC频率、耗时、回收前后内存变化如果Full GC后老年代下降明显可能是内存压力大或参数不合理如果Full GC后老年代几乎不下降重点怀疑内存泄漏用jstat观察各区域变化用jmap导出dump分析大对象和引用链检查是否有大缓存。无界队列、大集合、ThreadLocal未清理调优方向通常是先定位对象来源再考虑调整堆、新生代比例或更换GC5.什么的内存泄漏和内存溢出的区别内存泄漏是指对象已经不在被业务需要但仍然被引用导致GC无法回收内存溢出是指程序申请内存时JVM没有足够内存可以分配于是抛出OOM内存泄漏持续发生可能导致内存溢出常见内存泄漏场景静态集合一直添加不清理ThreadLocal用完不remove监听器、回调未注销连接、文本句柄没关闭本地缓存没有容量和过期限制6.线上CPU飙高、线程死锁怎么排查CPU飙高排查流程用top找到CPU高的Java进程pid用top -Hp pid找到CPU高的线程tid把tid转成16进制用jstack pid导出线程栈在栈里搜索16进制线程id定位具体代码线程死锁排查使用jstack pid它通常会直接显示deadlock信息查看哪些线程持有哪些锁、等待哪些锁根据堆栈定位代码中的锁顺序问题常见修复方式统一加锁顺序减少锁粒度避免嵌套锁使用tryLock超时退出7.jps、jstack、jmap、jhat、jstat常用命令作用jps查看当前机器上的Java进程jstack查看线程栈排查死锁、线程阻塞、CPU飙高jmap查看堆信息导出heap dumpjhat分析heap dump的老工具现在不太推荐更多用MAT、VisualVM.jstat查看GC、类加载、JIT等运行时统计面试重点CPU高看jstack堆问题看jmapGC趋势看jstat8.堆dump怎么抓取怎么分析常见heap dump抓取方式OOM自动抓取-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/dump手动抓jmap -dump:formatb,fileheap.hprof pid也可以jcmd pid GC.heap_dump heap.hprof分析思路用MAT或VisualVM打开dump看占用内存最大的对象看Dominator Tree找GC Roots引用链判断对象为什么没有被回收回到代码里定位集合、缓存、ThreadLocal、类加载器等来源注意线上抓取dump可能导致短暂停顿文件也可能很大操作前要确认磁盘空间和业务影响总结JVM运行时数据区包括程序计数器、虚拟机栈、本地方法栈、堆、方法区直接内存也常被问到堆分代是因为大多数对象生命周期短新生代用复制算法老年代更适合标记整理两个Survivor是为了配合复制算法一个作为来源一个作为目标JDK 8 后永久代被元空间替代元空间使用本地内存类加载的五步是加载、验证、准备、解析、初始化双亲委派是先交给父加载器加载父加载不了子加载器再加载Java判断对象存活注意靠可达性分析而不是引用计数GC Roots常见有栈引用、静态变量、常量、JNI引用、锁持有对象STW是暂停用户线程现代GC都在减少STW时间OOM排查先看类型再抓日志。GC、dump最后分析引用链内存结构知道每块区域存什么是否线程共享、会不会OOM类加载掌握五步流程、双亲委派、自定义类加载器垃圾回收理解对象存活判断、GC Roots、回收算法、分代收集、STW实战调优会看OOM、Full GC、CPU飙高、死锁和常用工具
返回列表