JVM53问

1 入门部分
1.1 为什么要学习 JVM
第一:为了面试。(企业招聘战略在升级,业务和技术问的越来越身)
第二:为了更好的理解 JAVA
第三:为了更好的解决线上问题。
1) 实现线上软件升级。 ( 热替换 )
2) 更好防止内存泄漏,提高内存的有效使用率。
3) 更好提高系统的吞吐量。
1.2 你了解哪些 JVM 产品?
Oracle 公司的 HotSpot
IBM 公司的 J9
阿里公司的 TaobaoVM
1.3 JVM 的构成有哪几部分?
第一:类加载子系统 ( 负责将类读到内存,校验类的合法性,对类进行初始化 )
第二:运行时数据区 ( 负责存储类信息,对象信息,执行逻辑 )
第三:执行引擎 ( 负责从指定地址对应的内存中数据然后解释执行以及 GC 操作 )
第四:本地库接口 ( 负责实现 JAVA 语言与其它编程语言之间的协同 )
2 类加载部分
2.1 你知道哪些类加载器?
第一: BootStrapClassLoader
第二: ExtClassLoader
第三: AppClassLoader
第四:自定义 ClassLoader
2.2 为什么需要多个类加载器?
每个类加载器都有自己的加载职责,负责从不同位置加载我们所需要的类,同时可以
基于需求进行懒加载 ( 按需加载 ) ,例如:
1) 加载基础类库 ( 核心类库 )
2) 扩展类库。
3) 三方类( MyBatis Spring,….. )。
4) 自己的类
2.3 什么是双亲委派类加载模型?
所谓双亲委派模型可以简单理解为向上询问、向下委托。当我们的类在被加载时,首先会询
问类加载器对象的 parent 对象 ( 两者之间不是继承关系 ) ,是否已经加载过此类,假如当前
parent 没有加载过此类,则会继续向上询问它的 parent ,依次递归。如果当前父加载器可
以完成类加载则直接加载,假如不可以则委托给下一层类加载器去加载(可以理解为逐层分
配任务)。
2.4 双亲委派方式加载类有什么优势、劣势?
通过双亲委派类加载机制,保证同一个类只能被加载一次,同时也是对类资源的一种保护。
例 如 我 们 自 己 也 写 了 一 个 java.lang.Object 类 , 为 了 保 证 Java 官 方 的
java.lang.Object 类加载后不再加载我们的 Object 就可以使用双亲委派机制。但是这里
也有一个缺陷,例如我们同一个 JVM 下有多个项目,但是不同项目中有包名类名都相同的
( 类中的内容是不同的 ) ,此时只能有一个项目中的类会被加载,其它项目则无法加载。还
有这种双亲委派模型可能会因为向上询问和向下委托,多少会影响一些性能。
2.5 描述一下类加载时候的基本步骤是怎样的?
第一:查找类 ( 例如通过指定路径 + 类全名找到指定类 )
第二:读取类 ( 通过字节输入流读取类到内存,并将类信息存储到字节数组 )
第三 : 对字节数组中的信息流进行校验分析以及初始化并将其结构内容存储到方法区。
第四 : 创建字节码对象 (java.lang.Class) ,基于此对象封装类信息的引用,基于这些引
用获取方法区类信息。
2.6 什么情况下会触发类的加载?
我们可以将类的加载分为显式加载和隐式加载,显式加载是通过类加载器的 loadClass
法或 Class 类的 forName 方法
直接对类进行加载。隐式加载一般指构建对象、访问类中属性或方法时触发的类加载。
2.7 类加载时静态代码块一定会执行吗?
不一定,静态代码块是否会执行,取决于类加载时,是否会执行类初始化。
2.8 如何理解类的主动加载和被动加载?
主动加载:访问本类成员或方法时触发的加载。
被动加载:访问本类 ( 当前类 ) 对应的父类属性时,本类 ( 当前类 ) 属于被动加载。被动加载不
会触发当前类的初始化。
2.9 为什么要自己定义类加载器,如何定义?
当系统提供的类加载器不能完全满足我们需求时,我们可以考虑自定义类加载器,例如:
1) 指定加载源头? ( 系统提供的类加载器已经确定了从哪些位置加载对应类,假如我们的
类不在指定范围内呢? )
2) 保证类安全? ( 可以在类编译时对字节码进行加密,类加载时对字节码进行解密 )
3) 打破双亲委派模型? ( 同一个 JVM 下有多个项目时,假如不同项目中有相同名字的类,
这些类都需要加载。 )
2.10
内存中一个类的字节码对象可以有多个吗?
可以,即使是同一个类,但是它的类加载器不同,生成的字节码对象也不会相同。
3 JVM 运行内存部分
3.1 JVM 运行内存是如何划分的?
JVM 运行时内存有方法区 (Method Area) 、堆区 (Heap) Java 方法栈 (Stack) 、本地方法
栈、程序计数器 ( 寄存器 )
3.2 JVM 中的程序计数器用于做什么?
Java 中每个线程都有一个程序计数器,为线程私有,用于记录程序执行时的字节码指令地
址。
3.3 JVM 虚拟机栈的结构是怎样的?
Java 中每个线程有一个虚拟机栈 (Java 方法栈 ), 每个方法的执行和退出会对应着一次入栈
(Push) 和出栈 (Pop) 操作。这个栈中的元素为一个一个的栈帧对象,这个栈帧有如下几部分
构成:
1) 操作数栈 ( 用于执行运算,例如两个变量值的加减 )
2) 局部变量表 ( 用于存储方法内的局部变量,对于实例方法,局部变量表的第 0 个位置为
this)
3) 方法返回值 ( 记录调用方法的返回值 )
4) 动态链接 ( 方法中可以调用其它方法,如何找到要调用的方法? )
5) 其它信息
3.4 JVM 虚拟机栈中局部变量表的作用是什么?
局部变量标底层实现是一个数组,用于存储方法内的局部变量。对于 main 方法而言,方法
中的 args 这个变量会存储在局部量表下标为 0 的位置。对于实例方法,局部变量下标为 0
位置存储的是 this
3.5 JVM 虚拟机栈中操作数栈的做用是什么?
最核心的作用是进行计算。 JVM 的执行引擎可以基于程序计数器中指令的地址找到具体指
令,然后执行。在执行这些指令时,可以将指令对应的数据放到操作数栈、也可以从操作栈
将数据取出存储到局部变量表,还可以将局部变量表中的数据取出,进行计算,将计算的结
果再存储到操作数栈中。
3.6 JVM 堆的构成是怎样的?
JVM 堆主要用于存储我们创建 Java 对象,从由年轻代 (Young ) 和老年代(
Old 区)构
成,年轻代又分伊甸园区和两个幸存区。
3.7 Java 对象分配内存的过程是怎样的?
1) 编译器通过逃逸分析( JDK8 已默认开启),确定对象是在栈上分配还是在堆上分配。
2) 如果是在堆上分配,则首先检测是否可在 TLAB Thread Local Allocation Buffer
上直接分配。
3) 如果 TLAB 上无法直接分配则在 Eden 加锁区 (CAS 算法进行加锁 ) 进行分配 ( 线程共享
)
4) 如果 Eden 区无法存储对象,则执行 Yong GC Minor Collection )。
5) 如果 Yong GC 之后 Eden 区仍然不足以存储对象,则直接分配在老年代。
3.8 JVM 年轻代幸存区设置的比较小会有什么问题?
伊甸园区被回收时,对象要拷贝到幸存区,假如幸存区比较小,拷贝的对象比较大,对象就
会直接存储到老年代,这样会增加老年代 GC 的频率。而分代回收的思想就会被弱化。
3.9 JVM 年轻代伊甸园区设置的比例比较小会有什么问题?
我们程序中新创建的对象,大部分要存储到伊甸园区,假如伊甸园设置的比较小,会增加 GC
的频率,可能会导致 STW 的时间边长,影响系统性能。
3.10JVM 堆内存为什么要分成年轻代和老年代?
为了更好的实现垃圾回收,减少 GC 时长、提高其执行效率。
3.11 项目中最大堆和初始堆的大小为什么推荐设置为一样的?
避免程序运行过程中,因对象多少或 GC 后内存发生了变化而调整堆大小,带来的更大系统
开销。在很多大厂的开发规范中都推荐初始堆和最大堆的大小是一样的。 ( 例如阿里的开发
手册 )
3.12 什么情况下对象会存储到老年代?
第一:创建的对象比较大,年轻代没有空间存储。
第二:经过多次 GC ,没有被回收的对象年龄在增加,默认 15 岁后会移动老年代。
3.13Java 中所有的对象创建都是在堆上分配内存的?
随着技术的升级,这个说法现在不准确了。
3.14 如何理解 JVM 方法区以及它的构成是怎样的?
方法区( Method Area )是 JVM 中的一种逻辑上规范,不同 JDK 对规范的落地会有不同,
例如在 JDK8 HotSport 虚拟机中称之为 Metaspace 。方法区主要用于存储已被虚拟机加
载的类信息、常 量、静态变量、即时编译后的代码等数据。
3.15JDK8 Hotsport 虚拟机的方法区内存在哪里?
JVM 堆外内存,严格来讲属于操作系统的一部分内存,也可以通过参数设置具体大小,假如
没有设置,可以无限增大,直到操作系统内存不足。
3.16 什么是逃逸分析以及可以解决什么问题?
逃逸分析一种数据分析算法,基于此算法可以检测对象是否发生了逃逸,未逃逸的小对象可
以分配栈上,也可以进行标量替换,还可以实现锁消除。总之,可以有效减少 Java 对象在
堆内存中的分配,可以减少线程阻塞,提高其执行效率。
3.17 如何理解对象的标量替换,为什么要进行标量替换?
将未逃逸的小对象直接打散分配到栈上,减少堆中对象的创建次数。堆中对象创建的少了,
GC 的频率就会降低, GC 频率降低了,系统正常业务的执行效率就会提高。
3.18 什么是内存溢出以及导致内存溢出的原因?
内 存 中 剩 余 的 内 存 不 足 以 分 配 给 新 的 内 存 请 求 , 此 时 就 会 出 现 内 存 溢 出
OutOfMemoryError )。内存溢出可能直接导致系统崩溃。导致内存溢出的原因可能会有
如下几种:
 创建的对象太大导致堆内存溢出 ( 内存中没有连续的内存空间可以存储你这个大对象 )
创建的对象太多导致堆内存溢出 ( 对象创建的太多,又不能及时回收这些对象 )
方法出现了无限递归调用导致栈内存溢出 ( 每次方法的调用都会对应这个一个栈帧对象
的创建,同时将栈帧入栈 )
方法区内存空间不足导致内存溢出。 ( 将如内存中不断的加载新的类,类越来越多,此
时可能出现内存溢出 )
出现大量的内存泄漏
3.19 什么是内存泄漏以及导致内存泄漏的原因?
程序运行时,动态分配的内存空间,在使用完毕后未得到释放,结果导致一直占用着内存单
元,直到程序运行结束。这个现象称之为内存泄漏。导致内存泄漏的原因可能有如下几点:
大量使用静态变量 ( 静态变量与程序生命周期一样 )
IO/ 连接资源用完没关闭 ( 记得执行 close 操作 )
内部类的使用方式存在问题 ( 实力内部类或默认引用外部类对象 )
缓存 (Cache) 应用不当 ( 尽量不要使用强引用 )
ThreadLocal 应用不当 ( 用完记得执行 remove 操作 )
3.20JAVA 中的四大引用类型有什么特点?
Java 中为了更好控制对象的生命周期,提高对象对内存的敏感度,设计了四种类型的引用。
按其在内存中的生命力强弱,可分为强引用、软引用、弱引用、虚引用。其中, " 强引用 "
用的对象生命力最强,其它引用引用的对象生命力依次递减。 JVM GC 系统被触发时,会
因对象引用的不同,执行不同的对象回收逻辑。
3.21 项目中的哪些地方用到了缓存?
1) 数据库内置的缓存? ( 例如 mysql 的查询缓存 )
2) 数据层缓存 ( 一般由持久层框架提供,例如 MyBatis)
3) 业务层缓存 ( 基于 map 等实现的本缓存,分布式缓存 - 例如 redis)
4) CPU 缓存 ( 高速缓冲区 )
5) 浏览器内置缓存?
3.22 假如让你设计一个缓存你会考虑哪些问题?
1) 存储结构 ( 使用什么结构存储数据效率会更高 ?- 散列表 )
2) 淘汰算法 ( 缓存容量有限 -LRU/FIFO/LFU)
3) 任务调度 ( 定期刷新缓存,缓存失效时间 )
4) 并发安全 ( 缓存并发访问时的线程安全 )
5) 日志记录 ( 缓存是否命中,命中率是多少 )
6) 序列化 ( 存对象时序列化、取对象时反序列化 )
4 字节码增强部分
4.1 什么是字节码?
符合 JVM 虚拟机规范的操作指令,可以被 JVM 执行引擎解释执行。
4.2 为何要学习字节码?
更好的理解 JAVA 代码,例如对于 Integer a=100 ,你是怎么知道 100 如何封装为 Integer
类型的,你如何知道注解本质上也是一个接口的,你怎么知道你写的枚举类型默认都继承
Enum ,你如何知道 synchronized 是如何进行加锁的等。当我们掌握了字节码的基本特征
后,就可以直接基于字节码对类功能进行增强,同时还可以创作出类似 JAVA 语言的编程语
言(例如 Scala 语言),只要这个语言编译完成后生成的字节码符合 JVM 规范即可。
4.3 如何解读字节码内容?
我们解读字节码时通常会借助如下几种方式:
方式一:借助 Hex-Editor 插件可以查看字节码的 16 进制形式
方式二:借助 javap 指令直接查看字节码指令
方式三:借助 jclasslib 插件查看字节码指令
4.4 字节码内容由哪几部分构成?
魔数 + 版本号 + 常量池 + 类访问标识 + 父类引用 + 接口数 + 成员变量信息 + 方法信息 + 其它属性
4.5 什么是字节码增强?
在类加载或类运行时对类的字节码进行修改或生成新的字节码,这个过程我们称之为字节码
增强。
4.6 为什么要进行字节码增强?
通过这种方式可以实现类功能的增强,同时还可以简化部分重复代码的编写。例如 AOP
设计、热部署等技术实现都用到字节码增强技术。
4.7 你了解哪些字节码增强技术?
ASM 技术、 Javassist 技术等,我们可以基于这些技术修改类的字节码,创建新的接口、
类、添加属性、方法都可以。
4.8 什么是热替换以及如何实现?
热替换我们可以理解为一种在线升级技术,就是在服务运行过程中,不重启服务就可以完成
系统的在线升级。目前在 java 中实现热替换(热部署),可以基于 Java Agent 技术,此
技术可以侵入正在运行的 JVM 应用程序,并借助 Asm javassist 技术修改或扩展目标类
型,并通过新的目标类型字节码替换正在运行的 JVM 字节码,以达到热部署的目的 ( 在线升
)
5 JVM 垃圾回收部分
5.1 何为 GC
GC Garbage Collection )称之为垃圾回收,是对内存中的垃圾对象采用一定的算法进行内存
回收的一个动作。
5.2 为什么要 GC
深入理解 GC 的工作机制,可以帮你写出更好的 Java 应用(例如避免内存泄漏,提高运行
效率),提高开发效率。
5.3 如何判定对象是否为垃圾?
引用计数法(出现循环引用,对象无法回收,可考虑弱引用、软引用);
可达性分析法(从 GC root 引用查找对象,假如访问不到则认为不可达)
5.4 你知道哪些 GC 算法?
标记清除、标记复制、标记整理算法
5.5 JVM 中有哪些垃圾回收器?
串行( Serial )、并行 (Parallel) 、并发 (CMS) G1( 收集器 )
5.6 如何查看 JVM 默认的垃圾收集器?
-XX:+PrintCommandLineFlags -version
5.7 说出几个常用的 JVM 配置参数?
1 )堆栈配置相关
-Xmx3550m : 最大堆大小为 3550m
-Xms3550m : 设置初始堆大小为 3550m
-Xmn2g : 设置年轻代大小为 2g
-Xss128k : 每个线程的堆栈大小为 128k
-XX:NewRatio=4: 设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去
持久 代)。
-XX:SurvivorRatio=4 : 设置年轻代中 Eden 区与 Survivor 区的大小比值。设置为 4
则两个 Survivor 区与一个 Eden 区的比值为 2:4 ,一个 Survivor 区占整个年轻代的 1/6
-XX:MaxTenuringThreshold=0 : 设置垃圾最大年龄。如果设置为 0 的话,则年轻代对象
不 经过 Survivor 区,直接进入年老代。
2 )垃圾收集器相关
-XX:+UseParallelGC : 选择垃圾收集器为并行收集器。
-XX:ParallelGCThreads=20 : 配置并行收集器的线程数
-XX:+UseConcMarkSweepGC : 设置年老代为并发收集。
-XX:CMSFullGCsBeforeCompaction :由于并发收集器不对内存空间进行压缩、整理,所
以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次 GC 以后对内
存 空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection : 打开对年老代的压缩。可能会影响性能,但是
可以消除碎片
3 )辅助信息相关 -XX:+PrintGC 输出形式
5.8 JAVA 中的堆区为什么要分代?
因为 GC 过程都触发 STW(stop the world), 也就说可能要暂停正常业务的执行,影响执行
效率。如果能够想办法缩短一次 GC 的时长,那我们是否可以只收集其中的一部分内存区域。
基于这样的一种原因就产生分代设计思想。
5.9 服务频繁 fullgc younggc 次数较少,可能原因?
1. 经常有超过大对象阈值的对象进入老年代 , 可以通过 -XX:PretenureSizeThreshold
置,大于这个值的参数直接在老年代分配。
2. 老年代参数设置不当, -XX:CMSInitiatingOccupancyFaction=92 设置不合理(阈值达
到多少才进行一次 CMS 垃圾回),导致频繁 FULLGC
3.FULLGC 之后没有整理老年代内存碎片,导致没有连续可用的内存地址,进入恶性循环,
导致频繁老年代 GC -XX:CMSFullGCsBeforeCompaction 可以设置
4. 新生代过小,或者 e 区和 s 区比例不当,对象通过动态年龄判断机制频繁进入老年代。
5. 不合理使用 System.gc() ,造成频繁的 FullGC -XX:+DisableExplicitGC 这个参数
可以禁用 System.gc().
6. 存在内存泄露,老年代中驻扎着大量不可回收的对象,一定程度上缩小了老年代的大小,
造成对象一进入老年代就触发 FULLGC
7.Meatspace 不够用引发 fullgc, 甚至无限 fullgc, 这类问题常见于 tomcat 热部署,以
及使用反射不当。
5.10 你知道哪些 JVM 小工具?
Jps
jps 主要用来输出 JVM 中运行的进程状态信息。语法格式如下:
jps [options] [hostid]
-q 不输出类名、Jar 名和传入 main 方法的参数
-m 输出传入 main 方法的参数
-l 输出 main 类或 Jar 的全限名
-v 输出传入 JVM 的参数
Jstack
jstack 主要用来查看某个 Java 进程内的线程堆栈信息。语法格式如下:
jstack [option] pid
jstack [option] executable core
jstack [option] [server-id@]remote-hostname-or-ip
jmap
jmap 导出堆内存,然后使用 jhat 来进行分析, jmap 语法格式如下:
jmap [option] pid
jmap [option] executable core
jmap [option] [server-id@]remote-hostname-or-ip
使用 jmap -heap pid 查看进程堆内存使用情况
jstat
jstat JVM 统计监测工具,看看各个区内存和 GC 的情况。
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
例如:
jstat -gc 21711 250 4
vmid Java 虚拟机 ID ,在 Linux/Unix 系统上一般就是进程 ID interval 是采样时
间间隔。 count 是采样数目。比如下面输出的是 GC 信息,采样时间间隔为 250ms
采样数为 4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值