前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Java面试题集中营》- JVM 知识

《Java面试题集中营》- JVM 知识

作者头像
阿提说说
发布2024-07-14 08:34:20
800
发布2024-07-14 08:34:20
举报
文章被收录于专栏:Java技术进阶

《深入理解Java虚拟机:JVM高级特性与最佳实践(最新第二版)》

JVM运行时内存区域划分

线程独享区域:程序计数器,本地方法栈,虚拟机栈

线程共享区域:元空间(<=1.7方法区), 堆

程序计数器:线程私有,是一块较小的内存空间,可以看做是当前线程执行的字节码指示器,也是唯一的没有定义OOM的区块

本地方法栈: 用于执行Native 方法时使用 虚拟机栈:用于存储局部变量,操作数栈,动态链接,方法出口等信息

元空间:存储已被虚拟机加载的类元信息,常量,静态变量,即时编译器编译后的代码等数据依旧存储在方法区中,方法区位于堆中

堆:存储对象实例

示例:

代码语言:javascript
复制
/**
 * @author: jujun chen
 * @description: 使用了CGLIB来动态生成类,元空间存储类信息,-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
 * 如果只设置堆的大小,并不会溢出
 * @date: 2019/4/7
 */
public class JavaMetaSpaceOOM {

    static class OOMObject{}
    public static void main(final String[] args) {
        while (true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o,objects);
                }
            });
            enhancer.create();
        }
    }

}
OOM,及SOE的示例、原因,排查方法
代码语言:javascript
复制
//OOM -Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError
public class OOMTest {
    public static void main(String[] args) {
        List<Object> objList = new ArrayList();
        while(true) {
            objList.add(new Object());
        }
    }
}

//SOE栈异常 -Xss125k
public class SOETest() {
    static int count = 0;
    public static void main(String[] args) {
        try {
            stackMethod();
        } catch(Error err) {
            err.printStackTrace();
            System.out.println("执行count=" + count);
        }
    }
    private static void stackMethod() {
        count ++;
        stackMethod();
    }
}
  • OOM排查:如果能看到日志,可以从打印的日志中获取到发送异常的代码行,再去代码中查找具体哪块的代码有问题。如果没有记录日志,通过设置的 -XX:+HeapDumpOnOutOfMemoryError 在发生OOM的时候生成.hprof文件,再导入JProfiler能够看到是由于哪个对象造成的OOM,再通过这个对象去代码中寻找
  • SOE排查:栈的深度一般为1000-2000深度,超过了深度或者超过了栈大小就会导致SOE,通过打印的日志定位错误代码位置,检测是否有无限递归,发生了死循环等情况,修改代码
如何判断对象可以回收或存活

判断是否可以回收,或者存活主要是看:

  1. 堆中是否存在该实例
  2. 加载该类的classloader是否已经被回收
  3. 该类的java.lang.Class对象在任何地方没有被引用,也就是不能够通过反射方法获取该类信息
哪些对象可以作为GC ROOT 对象
  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 本地方法中JNI引用的对象
  3. 方法区中的静态变量和常量引用的对象
常见的GC算法
  1. 标记-清除算法:先标记出需要回收的对象,再清除这些被标记了的对象,缺点是:标记清除过程效率不高;会产生内存碎片
  2. 复制算法:将内存划分成同等大小两块,只使用其中一块内存,当这一块的内存快用完后,将已存活的对象复制到另外一块内存,再对已使用的内存空间进行一次清理
  3. 标记-整理算法:标记出已存活的对象,将对象移动到内存一端,再对端以外的内存进行清理回收
  4. 分代收集算法:年轻代使用复制算法,永久代使用 标记-清除或者标记-整理算法
常见的JVM性能监测分析工具
  1. jps 能够查看正在运行的虚拟机进程,并显示虚拟机的执行主类及进程ID
  2. jstat [option vmid [interval[s|ms] [count]] ] 可以显示本地或者远程虚拟机中的类装载、内存、垃圾收集、JIT编译等运行数据
  3. jinfo 实时查看和调整虚拟机的各项参数
  4. jmap 生成堆转储快照
  5. jhat 生成页面分析导出的堆存储快照
  6. jstack 用于生成虚拟机当前时刻的线程快照
  7. jstatd 启动RMI服务端程序,代理本地的Java进程,供远程计算机连接调式
  8. 查看当前JVM使用的垃圾收集器 java -XX:+PrintFlagFinal -version 或者 java -XX:+PrintCommandLineFlags -version
  9. jconsole Java监视和管理控制台,能够监控内存,线程,类等
  10. jvisualvm
代码语言:javascript
复制
 多合一监视工具

更多资料请学习官网:https://docs.oracle.com/en/java/javase/11/tools/index.html

JVM优化
代码语言:javascript
复制
1. 响应时间优先:年轻代设的大些,直到接近系统的最低响应时间限制。年轻代设大,可以减少到达年老代的对象。对于永久代的设置需要参考:永久代并发收集的次数、年轻代和永久代回收时间比例,调整达到一个合适的值
2. 吞吐量优先:年轻代设的大些,永久代较小
什么时候会触发FullGC
  1. 永久代空间不足
  2. 手动调用触发gc
类加载器有几种
代码语言:javascript
复制
1. Bootstrap ClassLoader(C++实现) 
   负责加载JDK自带的rt.jar包中的类文件,它是所有类加载器的父加载器,Bootstrap ClassLoader没有任何父类加载器。
2. Extension ClassLoader(ExtClassLoader)负责加载Java的扩展类库,也就是从jre/lib/ext目录下或者java.ext.dirs系统属性指定的目录下加载类。  
3. System ClassLoader(AppClassLoader)负责从classpath环境变量中加载类文件,classpath环境变量通常由"-classpath" 或 "-cp" 命令行选项来定义,或是由 jar中 Mainfest文件的classpath属性指定,System ClassLoader是Extension ClassLoader的子加载器
4. 自定义加载器
什么是双亲委派模型?双亲委派模型的破坏

一个类在加载的时候,首先会将加载请求委派给父加载器,只有当父加载器反馈无法加载完成这个请求时,子加载器才会尝试自己加载 双亲委派模型的破坏指的是不按照双亲委派模型来加载类,比如JNDI,它的代码由启动类加载器加载,但JDNI需要调用部署在ClassPath的JNDI接口,但启动类加载器是不知道这些代码的,所以就有了线程上下文类加载器(Thread Context ClassLoader),可以通过java.lang.Thread类setContextClassLoader设置类加载器,通过这个父加载器就可以请求子类加载器完成类加载的动作。

类的生命周期

类的生命周期一个有7个阶段:加载、验证、准备、解析、初始化、使用、卸载

  • 加载: 加载阶段,虚拟机需要完成以下3件事
  1. 通过类的全限定名来获取此类的二进制字节流
  2. 将字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口
  • 验证:分4个验证
  1. 文件格式验证,验证是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
  2. 元数据验证,对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求
  3. 字节码验证,通过数据流和控制流分析,确定程序语义是否合法、符合逻辑
  4. 符合引用验证,是对类自身以外的信息进行匹配性校验(常量池中各种符合引用)
  • 准备:正式为类变量分配内存并设置初始值的阶段,这里设置初始值是数据类型的默认值
  • 解析:虚拟机将常量池中的符号引用替换为直接引用的过程
  • 初始化:执行类构造器的过程
强引用、软引用、弱引用、虚引用
  1. 强引用:大部分使用都是强引用,当内存不足时,会OOM,程序异常终止,也不会随意回收具有强引用的对象
  2. 软引用:内存足够时,不会清除对象,在内存不足时就会回收这些对象
  3. 弱引用:弱引用的对象,在发生GC的时候,就会被回收
  4. 虚引用:虚引用主要用来跟踪垃圾回收的活动,虚引用必须和引用队列联合使用。
编译器会对指令做哪些优化?

编译器优化分编译期和运行期

  • 编译期: 1.标注检查,检查变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配,对常量进行折叠 2.数据及控制流分析,检查诸如程序局部变量在使用前是否有赋值、是否所有的受检异常都被正确处理等问题 3.将语法糖还原为基础的语法结构 4.生成字节码
  • 运行期: 即时编译器JIT会把运行频繁的代码编译成与本地平台相关的机器码,并进行各种层次的优化 Client Compiler: 会进行局部性的优化,分三阶段:第一阶段,一个平台独立的前端将字节码构造成一种高级中间代码表示HIR,HIR使用静态单分配(SSA)的形式来代表代码值,在字节码上做方法内联,常量传播等基础优化;第二阶段,从HIR中产生低级中间代码,在这之前会做空值检查消除,范围检查消除等。第三阶段,在LIR上分配寄存器,并在LIR上做窥孔优化,最后产生机器码 Server Compiler: 会执行无用代码消除、循环展开、循环表达式外提、消除公共子表达式、常量传播、基本块重排序、范围检测消除、空值检查消除,另外还能根据解释器或Client Compiler提供的性能监控信息,进行一些不稳定的激进优化,比如守护内联、分支频率预测等
  • 几种经典的优化技术:
  1. 公共子表达式消除 如果一个表达式E已经计算过,并且从先前的计算到现在E中所有变量的值都没有发生变化,那么E的这次出现就成为公共子表达式
  2. 数组范围检查消除 编译期就判断数组是否在合理的范围内,如果在,那就可以在循环中把数组的上下界检查消除。另外还有隐式异常处理,虚拟机会注册一个Segment Fault信号的异常处理器,但如果代码经常为空,消耗时间比判空慢,但虚拟机会根据运行期收集到的信息选择使用判空还是隐式异常处理
  3. 方法内联 一可以给“公共子表达式消除”等其他优化技术提供基础。虚方法即多态情况下,如果要确定是否能内联,虚拟机需要向“类型继承关系分析”器查询,如果只有一个版本,那可以进行内联,但还会留一个“逃生门”,这种内联称为“守护内联”,如果继承关系发生变化,虚拟机会通过“逃生门”退回到解释状态执行,或重新编译。 如果是多版本方法,虚拟机会通过“内联缓存”,在第一次调用的时候将目标方法版本缓存起来,下次调用的时候检查版本是否一致,如果不一致就会取消内联,查找虚拟方法表进行方法分派
  4. 逃逸分析 分析对象动态作用域,对象是否作为调用参数传递到其他方法中(方法逃逸),是否有被其他线程访问(线程逃逸)。如果没有以上情况,虚拟机会做一些高效优化:栈上分配、同步消除(去掉同步措施)、标量替换(将对象成员变量恢复到原始类型)
Serial、Parallel、CMS、G1收集器特点
  • Serial 单线程收集器,在进行垃圾收集时,必须暂停所有的工作线程直到结束,该收集器停顿时间长,-XX:+UseSerialGC 年轻代使用串行垃圾收集器,-XX:+UseSerialOldGC 老年代使用串行垃圾收集器
  • Parallel 采用多线程来扫描并压缩堆,停顿时间短,回收效率高,-XX:+UseParNewGC 使用并发标记扫描垃圾回收器;能够提高应用吞吐率 JDK8 默认使用:年轻代UseParallelGC,老年代UseParallelOldGC
  • CMS 基于“标记-清除”算法,一共分初始标记、并发标记、重新标记、并发清除,并发重置5个阶段;能够降低STW,提高用户体验 在初始标记、重新标记阶段需要STW,并且CMS收集器占用CPU资源较多,无法处理浮动垃圾 并发重置阶段重新初始化CMS数据结构和数据,为下次垃圾回收做准备 在并发标记和并发清理阶段可能会出现垃圾回收还没执行完,垃圾回收又被触发的情况,此时会发生“concurrent mode failure”,垃圾收集器进入STW,用serial old 进行回收。 (-XX:CMSInitiatingOccupancyFraction 调整老年代占用多少触发回收;-XX:+UseCMSCompactAtFullCollection 默认开启,在即将触发FullGC前对内存碎片进行整理;-XX:CMSFullGCsBeforeCompaction设置执行多少次不压缩的FullGC后,来一次带压缩的的碎片整理)
  • G1 可以跟用户程序并发进行垃圾收集;分代收集,将堆划分成多个大小相等的独立Region区域;空间整合,默认就会进行内存整理;可预测的停顿,G1跟踪各个Region的回收获得的空间大小和回收所需要的经验值,维护一个优先列表;
G1 垃圾收集分类

YoungGC:

YoungGC并不是在现有的Eden区放满了就马上触发,G1会计算现有的Eden区回收大概要多久时间,如果回收时间远小于参数-XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数-XX:MaxGCPauseMills设定的值,就会触发Young GC

MixedGC

不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中的存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GC

Full GC

停止系统程序,然后采用单线程进行标记、清理、和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程非常耗时。(Shenandoah已经优化成多线程收集,Shenandoah可以认为是G1的升级版本)

ZGC

ZGC 是JDK11 中加入的低延迟垃圾收集器,没有采用分代算法,清理过程大致分为:并发标记、并发预备重分配、并发重分配、并发重映射


坚持专研Java,一条路走到黑。持续更新地址 语雀:https://www.yuque.com/itsaysay/mzsmvg GitHub: https://github.com/jujunchen/Java-interview-question

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JVM运行时内存区域划分
  • OOM,及SOE的示例、原因,排查方法
  • 如何判断对象可以回收或存活
  • 哪些对象可以作为GC ROOT 对象
  • 常见的GC算法
  • 常见的JVM性能监测分析工具
  • JVM优化
  • 什么时候会触发FullGC
  • 类加载器有几种
  • 什么是双亲委派模型?双亲委派模型的破坏
  • 类的生命周期
  • 强引用、软引用、弱引用、虚引用
  • 编译器会对指令做哪些优化?
  • Serial、Parallel、CMS、G1收集器特点
  • G1 垃圾收集分类
  • ZGC
相关产品与服务
应用性能监控
应用性能监控(Application Performance Management,APM)是一款应用性能管理平台,基于实时多语言应用探针全量采集技术,为您提供分布式性能分析和故障自检能力。APM 协助您在复杂的业务系统里快速定位性能问题,降低 MTTR(平均故障恢复时间),实时了解并追踪应用性能,提升用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档