面试笔记@JVM

说一下 jvm 的主要组成部分?及其作用?

类加载器(ClassLoader)
运行时数据区(Runtime Data Area)
执行引擎(Execution Engine)
本地库接口(Native Interface)

组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

说一说JVM的内存区域

Java 虚拟机在执行 Java 程序的过程中会把他所管理的内存划分为若干个不同的数据区域:

  • 程序计数器:可以看作是当前线程所执行的字节码文件(class)的行号指示器,它会记录执行痕迹,是每个线程私有的
  • 方法区:主要存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据,该区域是被线程共享的,很少发生垃圾回收
  • 栈:栈是运行时创建的,是线程私有的,生命周期与线程相同,存储声明的变量
  • 本地方法栈:为 native 方法服务,native 方法是一种由非 java 语言实现的 java 方法,与 java 环境外交互,如可以用本地方法与操作系统交互
  • 堆:堆是所有线程共享的一块内存,是在 java 虚拟机启动时创建的,几乎所有对象实例都在此创建,所以经常发生垃圾回收操作

JDK8 之前,Hotspot 中方法区的实现是永久代(Perm)

JDK8 开始使用元空间(Metaspace),以前永久代所有内容的字符串常量移至堆内存,其他内容移至元空间,元空间直接在本地内存分配。

JDK8为什么要使用元空间取代永久代?

永久代是 HotSpot VM 对方法区的实现,JDK 8 将其移除的部分原因如下:

  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出
  • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
  • 将 HotSpot 与 JRockit 进行整合,JRockit 是没有永久代的

对象在哪块内存分配?

对象(数组可以理解为对象的一种)在堆内存分配

某些对象没有逃逸出方法,可能被优化为在栈上分配

Java中类加载过程是什么样的?

类加载的步骤为,加载 -> 验证 -> 准备 -> 解析 -> 初始化。

1、加载:

  • 获取类的二进制字节流
  • 将字节流代表的静态存储结构转化为方法区运行时数据结构
  • 在堆中生成class字节码对象

2、验证:连接过程的第一步,确保 class 文件的字节流中的信息符合当前 JVM 的要求,不会危害 JVM 的安全

3、准备:为类的静态变量分配内存并将其初始化为默认值

4、解析:JVM 将常量池内符号引用替换成直接引用的过程

5、初始化:执行类构造器的初始化的过程

对象创建过程是什么样的?

对象在 JVM 中的创建过程如下:

  • JVM 会先去方法区找有没有所创建对象的类存在,有就可以创建对象了,没有则把该类加载到方法区
  • 在创建类的对象时,首先会先去堆内存中分配空间
  • 当空间分配完后,加载对象中所有的非静态成员变量到该空间下
  • 所有的非静态成员变量加载完成之后,对所有的非静态成员进行默认初始化
  • 所有的非静态成员默认初始化完成之后,调用相应的构造方法到栈中
  • 在栈中执行构造函数时,先执行隐式,再执行构造方法中书写的代码
  • 执行顺序:静态代码块,非静态代码块,构造方法
  • 当整个构造方法全部执行完,此对象创建完成,并把堆内存中分配的空间地址赋给对象名

方法区内存溢出怎么处理?

在 Java 虚拟机中,方法区是可供各线程共享的运行时内存区域。

在不同的 JDK 版本中,方法区中存储的数据是不一样的:

  • JDK 1.7 之前的版本,运行时常量池是方法区的一个部分,同时方法区里面存储了类的元数据信息、静态变量、即时编译器编译后的代码等。
  • JDK 1.7 开始,JVM 已经将运行时常量池从方法区中移了出来,在堆中开辟了一块区域存放常量池。

永久代就是 HotSpot VM 对虚拟机规范中方法区的一种实现方式,永久代和方法区的关系就像 Java 中类和接口的关系。

HotSpot VM 机在 JDK 1.8 取消了永久代,改为元空间,类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大。

JDK 1.7 及之前的版本,启动时需要加载的类过多、运行时动态生成的类过多会造成方法区 OOM;JDK 1.7 之前常量池里的常量过多也会造成方法区 OOM。HotSpot VM 可以调大 -XX:MaxPermSize 参数值。

JDK 1.8,-XX:MaxMetaspaceSize 可以调整元空间最大的内存。

JVM常量池

http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/

谈谈动态年龄判断

这里涉及到 -XX:TargetSurvivorRatio 参数,Survivor 区的目标使用率默认 50,即 Survivor 区对象目标使用率为 50%。
Survivor 区年龄从小到大的累加和 > (Survivor 区内存大小 * 这个目标使用率)时,停止累加,比较MaxTenuringThreshold和累加和里的最大年龄,取小的,大于或等于这个年龄的对象直接进入老年代。

需要考虑参数 -XX:MaxTenuringThreshold 晋升年龄最大阈值

运行时栈帧包含哪些结构?

  • 局部变量表
  • 操作数栈
  • 动态连接
  • 返回地址
  • 附加信息

哪些是 GC Roots?

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
  • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
  • 所有被同步锁(synchronized关键字)持有的对象。
  • 反映 Java 虚拟机内部情况的 JMXBean、JVMTI中注册的回调、本地代码缓存等。

强引用、软引用、弱引用、虚引用是什么,有什么区别?

  • 强引用,就是普通的对象引用关系,如 String s = new String("ConstXiong")
  • 软引用,用于维护一些可有可无的对象。只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。SoftReference 实现
  • 弱引用,相比软引用来说,要更加无用一些,它拥有更短的生命周期,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。WeakReference 实现
  • 虚引用是一种形同虚设的引用,在现实场景中用的不是很多,它主要用来跟踪对象被垃圾回收的活动。PhantomReference 实现

工作中常用的 JVM 配置参数有哪些?

Java 8 为例

日志

  • -XX:+PrintFlagsFinal,打印JVM所有参数的值
  • -XX:+PrintGC,打印GC信息
  • -XX:+PrintGCDetails,打印GC详细信息
  • -XX:+PrintGCTimeStamps,打印GC的时间戳
  • -Xloggc:filename,设置GC log文件的位置
  • -XX:+PrintTenuringDistribution,查看熬过收集后剩余对象的年龄分布信息

内存设置

  • -Xms,设置堆的初始化内存大小
  • -Xmx,设置堆的最大内存
  • -Xmn,设置新生代内存大小
  • -Xss,设置线程栈大小
  • -XX:NewRatio,新生代与老年代比值
  • -XX:SurvivorRatio,新生代中Eden区与两个Survivor区的比值,默认为8,即Eden:Survivor:Survivor=8:1:1
  • -XX:MaxTenuringThreshold,从年轻代到老年代,最大晋升年龄。CMS 下默认为 6,G1 下默认为 15
  • -XX:MetaspaceSize,设置元空间的大小,第一次超过将触发 GC
  • -XX:MaxMetaspaceSize,元空间最大值
  • -XX:MaxDirectMemorySize,用于设置直接内存的最大值,限制通过 DirectByteBuffer 申请的内存
  • -XX:ReservedCodeCacheSize,用于设置 JIT 编译后的代码存放区大小,如果观察到这个值有限制,可以适当调大,一般够用即可

设置垃圾收集相关

  • -XX:+UseSerialGC,设置串行收集器
  • -XX:+UseParallelGC,设置并行收集器
  • -XX:+UseConcMarkSweepGC,使用CMS收集器
  • -XX:ParallelGCThreads,设置Parallel GC的线程数
  • -XX:MaxGCPauseMillis,GC最大暂停时间 ms
  • -XX:+UseG1GC,使用G1垃圾收集器

CMS 垃圾回收器相关

  • -XX:+UseCMSInitiatingOccupancyOnly
  • -XX:CMSInitiatingOccupancyFraction,与前者配合使用,指定MajorGC的发生时机
  • -XX:+ExplicitGCInvokesConcurrent,代码调用 System.gc() 开始并行 FullGC,建议加上这个参数
  • -XX:+CMSScavengeBeforeRemark,表示开启或关闭在 CMS 重新标记阶段之前的清除(YGC)尝试,它可以降低 remark 时间,建议加上
  • -XX:+ParallelRefProcEnabled,可以用来并行处理 Reference,以加快处理速度,缩短耗时

G1 垃圾回收器相关

  • -XX:MaxGCPauseMillis,用于设置目标停顿时间,G1 会尽力达成
  • -XX:G1HeapRegionSize,用于设置小堆区大小,建议保持默认
  • -XX:InitiatingHeapOccupancyPercent,表示当整个堆内存使用达到一定比例(默认是 45%),并发标记阶段就会被启动
  • -XX:ConcGCThreads,表示并发垃圾收集器使用的线程数量,默认值随 JVM 运行的平台不同而变动,不建议修改

你有哪些手段来排查 OOM 的问题?

  • 增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发生时自动 dump 堆内存信息到指定目录

  • 同时 jstat 查看监控 JVM 的内存和 GC 情况,先观察问题大概出在什么区域

  • 使用 MAT 工具载入到 dump 文件,分析大对象的占用情况,比如 HashMap 做缓存未清理,时间长了就会内存溢出,可以把改为弱引用 

什么样的对象会被当做垃圾回收?

当一个对象的地址没有变量去引用时,该对象就会成为垃圾对象,垃圾回收器在空闲的时候会对其进行内存清理回收

如何检验对象是否被回收?

可以重写 Object 类中的 finalize 方法,这个方法在垃圾收集器执行的时候,被收集器自动调用执行的

怎样通知垃圾收集器回收对象?

可以调用 System 类的静态方法 gc(),通知垃圾收集器去清理垃圾,但不能保证收集动作立即执行,具体的执行时间取决于垃圾收集的算法

谈谈你知道的垃圾回收算法

判断对象是否可回收的算法有两种:

  • Reference Counting GC,引用计数算法

  • Tracing GC,可达性分析算法

JVM 各厂商基本都是用的 Tracing GC 实现
大部分垃圾收集器遵从了分代收集(Generational Collection)理论。
针对新生代与老年代回收垃圾内存的特点,提出了 3 种不同的算法:

1、标记-清除算法(Mark-Sweep)

标记需回收对象,统一回收;或标记存活对象,回收未标记对象。
缺点:

  • 大量对象需要标记与清除时,效率不高
  • 标记、清除产生的大量不连续内存碎片,导致无法分配大对象

2、标记-复制算法(Mark-Copy)

可用内存等分两块,使用其中一块 A,用完将存活的对象复制到另外一块 B,一次性清空 A,然后改分配新对象到 B,如此循环。

缺点:

  • 不适合大量对象不可回收的情况,换句话说就是仅适合大量对象可回收,少量对象需复制的区域
  • 只能使用内存容量的一半,浪费较多内存空间

3、标记-整理算法(Mark-Compact)

标记存活的对象,统一移到内存区域的一边,清空占用内存边界以外的内存。
缺点:

  • 移动大量存活对象并更新引用,需暂停程序运行

谈谈你知道的垃圾收集器

https://baijiahao.baidu.com/s?id=1631969612675104722

搞懂双亲委派模型

https://blog.csdn.net/u013568373/article/details/93995246

列举一些你知道的打破双亲委派机制的例子。为什么要打破?

https://www.javanav.com/interview/06c0996e82d74c69bc9d20822150b592.html

JIT 是什么?

https://www.javanav.com/interview/3098295c6a0042d2bce823ca7ae8f482.html

说一下垃圾分代收集的过程

https://www.javanav.com/interview/dd95fd987d644f34a9311b93356921e6.html

safepoint

安全点(safepoint)在HotSpot中是一个核心的技术点,所谓安全点,指的是代码执行过程中被选择出来的一些位置,当JVM需要执行一些要STW(Stop The World)的操作的时候,这些位置用于线程进入这些位置并等待系统执行完成STW操作

什么是逃逸分析?

分析对象动态作用域

  • 当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;
  • 被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;
  • 从不逃逸

如果能证明一个对象不会逃逸到方法或线程之外,或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可能为这个对象实例采取不同程度的优化,如栈上分配、标量替换、同步消除。

MinorGC、MajorGC、FullGC 什么时候发生?

https://www.javanav.com/interview/1fa2cc4b9ca04742b6d32e38e986aa70.html

可以描述一下 class 文件的结构吗?

https://www.javanav.com/interview/e86daf4a37a049b79e050ac63343e43f.html

Java内存模型

https://blog.csdn.net/sillyzhangye/article/details/106111797

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值