JVM组成
JVM由那些部分组成,运行流程是什么?
是Java运行时的核心组件,负责将Java字节码转换为机器码并执行,主要有以下组成部分:
- 类加载器:
- 运行时数据区
- 执行引擎
- 本地库接口
执行流程:
- 类加载器将Java代码转换为字节码
- 运行时数据区被划分出来,为各个区域分配内存
- 执行引擎执行字节码,将字节码解释为机器码(中间可能会用到JIT优化)
- 再让机器码(底层系统指令)交由CPU执行。
- 在执行的过程当中涉及的不同数据会存储在运行时数据区的不同的区域(栈、堆)。如果涉及到本地方法调用,JVM会调用相应的本地方法库
你能详细说一下 JVM 运行时数据区吗?
- 方法区:用于存储类的结构信息。如类的字段,方法信息,运行时常量池
- 堆:用来存储对象的实例和数组。是垃圾回收的主要区域
- 栈:执行方法调用的内存区域,存储局部变量、操作数栈等信息。在每个方法执行时都会创建一个栈帧,然后入栈,方法完成后出栈
- 本地方法栈:用于执行本地方法
- 程序计数器:用于记录当前线程执行的字节码行数。
你再介绍一下程序计数器的作用?
程序计数器的作用主要体现在多线程任务当中。
- 程序计数器是JVM中比较小的一块内存区域,在多线程任务当中每个线程都有自己的程序计数器
- 在同一时刻,一个处理器只能处理一个线程,若当前正在执行的线程被分配的时间片用完后,处理器会执行其他的线程
- 当处理器再次执行到这个线程时,该线程的程序计数器记录了上次执行结束时,执行到的指令行号,继续接着该行号执行指令即可。
你能给我详细的介绍Java堆吗?
堆是JVM中最大的一块内存区域,主要由于存储对象实例和数组等信息。堆的内存结构如下
- 堆可以被分为新生代、老年代、永久代(8之前)、元空间(8之后)
- 新生代:用于存储新创建的对象,主要包含一个eden区和两个严格相等的幸存者区s0、s1。在同一时刻只有一个幸存者区使用另一个供垃圾回收时复制对象使用。当eden区满时,GC会将eden区幸存的对象存放到幸存者区当中,当进行多次GC后,还存在的对象会将其加入到老年代当中
- 老年代保存的是声明周期较长的对象
- 永久代和元空间:都是存储的是类的名称、字段、静态属性等信息。但是在Java8后为了解决永久代内存溢出问题。将存储在JVM堆中永久代的相关数据转移到了本地内存进行存储。
能不能介绍一下本地方法栈?
- 是Java虚拟机当中用于执行本地方法的内存区域
- 本地方法栈类似于Java虚拟机栈,但是它们的作用不同。Java虚拟机栈用于执行Java方法的调用和返回,而本地方法栈用于执行本地方法的调用和返回。
能不能介绍一下虚拟机栈?
- 虚拟机栈是Java虚拟机中用于执行方法调用的内存区域。
- 每个方法在执行的时候都会创建对应的栈帧,栈帧中存储的是方法的局部变量、返回地址、操作数栈等信息。在方法执行完毕后栈帧会出栈。
- 虚拟机栈是线程私有的
能不能介绍一下方法区?
- 方法区是JVM中用于存储类的静态变量、结构信息、常量等数据的一片内存区域
介绍方法区、老年代、元空间?
- 这三个定义在上诉都介绍过,首先方法区其实是一个概念,它表示的是我要在内存中开辟一块区域用来存储类的元信息等数据。但是怎么实现呢?
- 在Java8之前,通过永久代进行实现,但是永久代会出现内存溢出的问题
- 在Java8之后,通过元空间进行实现,通过将类的元信息等数据存储在本地内存上来解决该问题。
能介绍一下直接内存吗?
- 直接内存是一种由操作系统管理的堆外内存,不受虚拟机管理。
- 它通常用于实现高性能的I/O操作和大规模数据处理,提高了Java程序的性能和效率。
说一下堆和栈的区别?
- 堆中存放的是Java对象和数组等数据、栈是执行本地方法的区域,存储的一般是局部变量、返回地址等信息
- 堆会被垃圾回收、栈不会
- 堆内存是线程共享的、而栈内存是线程私有的
类加载器
什么是类加载器,类加载器有哪些?
- 类加载器就是将字节码文件加载到JVM当中
- 启动类加载器:加载Java核心库文件
- 拓展类加载器:加载Java扩展类库(ext目录下的jar包)
- 应用类加载器:加载应用程序的类。
- 自定义加载器:加载特定位置的类文件
说一下类加(装)载的执行过程?
- 加载:通过类的全限定名来获取字节码文件
- 验证:对字节码进行校验
- 准备:为类的静态变量分配内存空间并将其初始化为默认值。这些静态变量分配到方法区中
- 解析:将符号引用转换为直接引用
- 初始化:对类的静态变量、静态代码块进行初始化操作
- 使用:JVM从方法入口开始执行代码
- 卸载:执行完毕后释放内存空间
什么是双亲委派机制?
- 它是类加载器的一种工作机制,当一个类加载器接受到一个类加载的请求时,它不会立刻对其进行加载,而是将这个请求上交给其父类加载器
- 只有在父类无法对这个类进行加载时,子类才对这个类进行加载
JVM为什么采用双亲委派机制
- 避免一个类被重复加载
- 提高安全性
垃圾回收
简述一下垃圾回收机制?
- 是JVM自动管理内存的一种机制,用于回收不再被程序使用到的内存空间。
- 主要用来防止内存溢出
对象什么时候被回收?
引用计数法:
- 一个对象被引用一次,就在其对象头上加一次引用,如果一个对象的引用次数为0就将其回收
- 缺点:无法解决循环引用的问题
可达性分析算法:
- 该算法通过遍历对象引用关系图,从根节点(GCRoot)开始,标记所有可以从根节点到达的对象,而没有被标记的对象则被视为垃圾。
常用的垃圾回收算法?
标记-清除算法:
- 标记不再被引用的算法,然后对其进行清除
- 缺点:会产生垃圾
复制算法:
- 将内存空间分为两份,每次只使用一份,在垃圾回收时,将存活的对象复制到另一份当中,然后情况该内存空间。
- 缺点:不适合垃圾较多的情况
标记整理算法:
- 标记垃圾,将存活的对象移动一段,需要清理的对象移动到另一端进行清理
分代回收算法:
- 在Java8中,堆被分为两份,一个是新生代,一个是老年代,其内存比例为1:2,而新生代由eden区和两个大小相等的s0、s1幸存者区8:1:1组成
- 当创建一个对象时,首先将其分配到Eden园区,当Eden区满后,会触发YoungGC,然后将Eden区存活的对象转移到s0区,并将其年龄加1,清空Eden区
- 当再一次触发YoungGC时,将Eden区和s0中存活的对象的年龄加1并放到s1区中,并清空Eden区和s0
- 若一个对象的年龄到达15后,会将其转移到老年代当中
- 当老年代满后,会触发FullGC,FullGC会回收新生代和老年代。
强引用、软引用、弱引用、虚引用的区别?
- 强引用:表示一个对象处于有用且必须的状态,若一个对象被强引用,则GC不会被回收
- 软引用:表示一个对象处于有用非必须的状态,当内存充足时被软引用的对象不会被回收,但是若内存不足就会回收该部分内存
- 弱引用:表示一个对象处于可能有用且非必须的状态。无论内存是否充足,垃圾回收器发现有弱引用的对象,就会回收该对象
- 虚引用:表示一个处于无用的对象。任何时候都可能被回收
说一下JVM有哪些垃圾回收器?
串行垃圾回收器:
- 串行垃圾回收器是一个单线程垃圾回收器,适合单核处理器或者内存较小的环境
- 在进行垃圾回收时,会暂停所有的工作线程,直到垃圾回收完成
并行垃圾回收器:
- 使用多线程来进行垃圾回收,来提高垃圾回收的效率,适合多核处理器
- Java8默认的回收器
并发标记清除垃圾回收器:
- 是一款以获取最短停顿时间的垃圾回收器。
- 主要针对老年代,最大特点就是在进行垃圾回收时,应用仍正常运行
G1垃圾回收器:
- Java9及之后引入的垃圾回收器,这个垃圾回收器需要重点掌握
- 首先将堆内存划分为多个区域,每一个区域都可以作为Eden区,s区、old区
- 初始时所有的区域都是空的,挑选一些区域作为Eden区来存储对象,然后将Eden区垃圾回收时幸存的对象,采用复制算法加入到S区当中
- 之后,若Eden区内存不足后,再进行垃圾回收将,将幸存对象复制到新的s区中,然后将老对象晋升到老年代当中
- 当老年代占用内存过多,超过45%。会对老年代中的垃圾进行并发标记,这时无需暂停用户线程。(注意在并发阶段时,如果回收速度赶不上新对象创建的速度会触发FullGC)
- 标记完成后,不会对所有的老年代进行回收,而是优先把存活对象较少的老年代给回收了
- 然后进入下一轮的新生代回收、并发标记、混合收集。
MinorGC、MaiorGC、FullGC分别是什么?
- MinorGC:发生在新生代的垃圾回收,暂停时间较短
- MaiorGC:发生在老年代的垃圾回收。当老年代内存不足时,首先会尝试触发MinorGC,当内存还是不足时,才会触发MaiorGC
- FullGC:新生代+老年代完整垃圾回收,暂停时间长,要尽量避免
JVM调优
JVM调优的参数在哪里设置?
- 命令行参数:启动Java程序时,可以使用Java命令行加参数的形势进行设置
- 环境变量:可以在环境变量当中设置JAVA_OPTS来指定调优参数
JVM调优的参数有哪些?
- 设置堆的大小:-Xms(初始堆内存大小)或者-Xmx(最大堆内存大小)
- 设置Eden区和两个幸存者S区的比例
- 可以设置使用那种垃圾回收器
在调试JVM时都使用什么工具?
- jps:输出进程状态信息
- jstack:查看进程内指定线程的堆栈信息。有助于帮助诊断死锁
- jmap:生成堆内存存储快照。可以帮助分析内存泄漏
- visualvm:可视化工具,用来监视和分析java应用程序的性能和内存使用情况
假如项目中产生了java内存泄露,你说一下你的排查思路?
- 首先通过jmap打印内存存储快照生成dump文件
- 然后通过jdk自带的可视化工具VisuualVM对该文件进行分析
- 通过查看堆的相关信息,对发生内存泄漏的代码进行定位
- 找到对应代码进行修复即可
说服务器CPU持续飙高,你的排查方案与思路?
- 首先使用top命令来查看cpu使用情况。记录cpu使用较高的进程id
- 通过ps查看该进程中线程的使用情况,找到cpu使用较高的线程
- 最后用通过Jstack打印该线程的堆栈信息,从而来确定代码的行号