【Java八股文总结】之JVM

文章目录

JVM

一、JVM内存区域

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、运行时数据区域

在这里插入图片描述
在这里插入图片描述
线程私有: 程序计数器、虚拟机栈、本地方法栈
线程共享: 堆、方法区、直接内存(非运行时数据区的一部分)
JDK 1.8同JDK 1.7比,最大的差别就是:元数据区取代了永久代。元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。

1、程序计数器

在这里插入图片描述

2、java虚拟机栈

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Q:栈可能出现的两种错误

在这里插入图片描述

3、本地方法栈

在这里插入图片描述

4、堆

在这里插入图片描述
在这里插入图片描述

Q:堆中会出现的错误

在这里插入图片描述

Q:堆中对象的分配过程
  1. new 的对象先放在 Eden 区,大小有限制。
  2. 如果创建新对象时,Eden 空间填满了,就会触发 Minor GC,将 Eden 不再被其他对象引用的对象进行销毁,再加载新的对象放到 Eden 区,特别注意的是 Survivor 区满了是不会触发 Minor GC 的,而是 Eden 空间填满了,Minor GC 才顺便清理 Survivor 区。
  3. 将 Eden 中剩余的对象移到 Survivor0 区。
  4. 再次触发垃圾回收,此时上次 Survivor 下来的,放在 Survivor0 区的,如果没有回收,就会放到 Survivor1 区。
  5. 再次经历垃圾回收,又会将幸存者重新放回 Survivor0 区,依次类推。
  6. 默认是 15 次的循环,超过 15 次,则会将幸存者区幸存下来的转去老年区,jvm 参数设置次数 : -XX:MaxTenuringThreshold=N 进行设置。
  7. 频繁在新生区收集,很少在养老区收集,几乎不在永久区/元空间搜集。
5、方法区

在这里插入图片描述
在这里插入图片描述

Q:堆和栈的区别?

在这里插入图片描述

2、为什么要将永久代(PermGen)替换为元空间(MetaSpace)呢?

在这里插入图片描述

1、方法区的常用参数有?

在这里插入图片描述

2、运行时常量池

在这里插入图片描述

3、字符串常量池

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、jdk1.7 为什么将字符串常量池移到堆中?

在这里插入图片描述

5、直接内存

在这里插入图片描述

3、对象创建过程

①类加载(判断类是否被加载、解析、初始化过) --> ②分配内存(分配内存的方式) --> ③初始化零值 --> ④设置对象头 --> ⑤执行init方法
在这里插入图片描述
在这里插入图片描述

Q:JVM创建对象,如何保证线程安全性?
  • 采用 CAS分配重试 的方式来保证更新操作的原子性。
  • 每个线程 在Java堆中预先分配一小块内存 ,也就是本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),要分配内存的线程,先在本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。
4、Java内存分配方式

在这里插入图片描述

5、Java对象内存布局

在这里插入图片描述
在这里插入图片描述

Q:对象怎么访问定位?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
HotSpot虚拟机主要使用 直接指针 来进行对象访问。

6、JVM中对象及常量、局部变量、全局变量的存储位置
  • 局部变量
    基本数据类型:变量名和变量值存储在 方法栈 中。
    引用数据类型:变量值存储在 方法栈 中(存储的是堆中对象的内存地址),所指向的对象是存储在堆内存中(如new出来的对象)。

  • 全局变量
    基本数据类型:变量名和变量值存储在 内存中。
    引用数据类型:变量名存储的是所引用对象的内存地址,变量名和变量值存储在 内存中。

7、Java虚拟机的生命周期

Java虚拟机:执行Java程序。程序开始执行时它才运行,程序结束时它就停止。你在同一台机器上运行三个程序,就会有三个运行中的Java虚拟机。Java虚拟机总是开始于一个main()方法,这个方法必须是公有、返回void、只接受一个字符串数组。在程序执行时,你必须给Java虚拟机指明这个包或main()方法的类名。 Main()方法是程序的起点,它被执行的线程初始化为程序的初始线程。程序中其他的线程都由它来启动。Java中的线程分为两种:守护线程(daemon)和普通线程(non-daemon)。守护线程是Java虚拟机自己使用的线程,比如负责垃圾收集GC的线程就是一个守护线程。当然,你也可以把自己的程序用setDeamon设置为守护线程。包含Main()方法的初始线程不是守护线程。只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止。如果有足够的权限,你可以调用exit()方法终止程序。

二、JVM 垃圾回收

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、对象死亡的判断方法

在这里插入图片描述
在这里插入图片描述

2、哪些对象可以作为GC Roots 呢?

在这里插入图片描述

3、常见的引用类型(强软弱虚)

在这里插入图片描述
在这里插入图片描述

Q:如何判断一个常量是废弃变量?如何判断一个类是无用的类?

在这里插入图片描述

4、垃圾回收算法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5、垃圾回收器

在这里插入图片描述
注意:

  1. 注重吞吐量以及CPU资源情况下,优先选用Parallel Scavenge和Parallel Old收集器。
  2. CMS全称Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器。
  3. G1(garbage frist)优先回收垃圾存放最多的分区,垃圾回收暂停时间短,同时也能维持较好的吞吐量,还解决了CMS的浮动垃圾问题、内存碎片问题
  4. 吞吐量:CPU用于运行代码的时间与CPU总消耗时间的比值。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

CMS收集器

在这里插入图片描述
在这里插入图片描述

G1

在这里插入图片描述

Q:有了CMS,为什么还要引入G1?

优点: CMS最主要的优点在名字上已经体现出来——并发收集、低停顿
缺点: CMS同样有三个明显的缺点。

  1. 标记算法会导致内存碎片比较多。
  2. CMS的并发能力比较依赖于CPU资源,并发回收时垃圾收集线程可能会抢占用户线程的资源,导致用户程序性能下降。
  3. 并发清除阶段,用户线程依然在运行,会产生所谓的“浮动垃圾”(Floating Garbage),本次垃圾收集无法处理浮动垃圾,必须到下一次垃圾收集才能处理。如果浮动垃圾太多,会触发新的垃圾回收,导致性能降低。
    G1主要解决了内存碎片过多的问题。
Q:垃圾回收器负责的3个任务

①分配内存;②确保被引用对象的内存不被错误的回收;③回收不在被引用的对象的内存空间。

6、Minor GC/Young GC、Major GC/Old GC、Mixed GC、Full GC 都是什么意思?
  • 部分收集 (Partial GC): 指目标不是完整收集整个Java堆的垃圾收集,其中又分为:

1、新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。当Eden区没有足够的空间时,就会触发Young GC来清理新生代。
2、老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。
3、混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。

  • 整堆收集(Full GC): 收集整个Java堆和方法区的垃圾收集。
Q:Minor GC的触发条件?

① eden区满的时候,会触发Minor GC。
② 新创建的对象大小大于Eden区所剩下的空间大小时,会触发Minor GC。

Q:什么时候会触发Full GC ?

在这里插入图片描述

Q:对象什么时候进入老年代?

在这里插入图片描述

  • 长期存活的对象将进入老年代
    在对象的对象头信息中存储着对象的迭代年龄,迭代年龄会在每次YoungGC之后对象的移区操作中增加,每一次移区年龄加一。当这个年龄达到15(默认)后,这个对象将会被移入老年代。
    可以通过这个参数设置这个年龄值。-XX:MaxTenuringThreshold
  • 大对象直接进入老年代
    有一些占用大量连续内存空间的对象在被加载就会直接进入老年代。这样的大对象一般是一些数组,长字符串之类的。
    HotSpot虚拟机提供了这个参数来设置。-XX:PretenureSizeThreshold
  • 动态对象年龄判定
    为了能更好地适应不同程序的内存状况,HotSpot虚拟机并不是永远要求对象的年龄必须达到-XX:MaxTenuringThreshold才能晋升老年代,如果在 Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
  • 空间分配担保
    假如在Young GC之后,新生代仍然有大量对象存活,就需要老年代进行分配担保,把Survivor无法容纳的对象直接送入老年代。

三、类加载过程

1、类的生命周期

在这里插入图片描述

2、类加载过程

在这里插入图片描述
一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去自定义类加载器去控制字节流的获取方式(重写一个类加载器的loadclass()方法)。数组类型不通过类加载器创建,它由Java虚拟机直接创建。
所有的类都由类加载器加载,加载的作用就是将.class文件加载到内存。

1、加载

在这里插入图片描述

2、验证

在这里插入图片描述

3、准备

在这里插入图片描述

4、解析

在这里插入图片描述

5、初始化

在这里插入图片描述

Q:初始化开始的时机

在这里插入图片描述

6、卸载

在这里插入图片描述

注意: 接口加载过程与类加载过程稍有不同。当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,当真正用到父接口的时候才会初始化。

3、Java中的类加载器

JVM类加载器分为两类:JVM自带的类加载器和自定义类加载器。
在这里插入图片描述

Q:为什么要自定义类加载器?

① 隔离加载类;② 修改类加载的方式;③ 扩展加载源;④ 防止源码泄漏。

Q:如何实现自定义类加载器?

继承抽象类java.lang.ClassLoader类的方式,实现自己的类加载器。

4、双亲委派模型

每一个类都有一个对应它的类加载器。系统中的ClassLoader在协同工作的时候会默认使用双亲委派模型。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派给父类加载器的loadClass()处理,因此所有的请求最终都应该传送到顶层的启动类加载器BootstrapClassLoader中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器BootstrapClassLoader作为父类加载器。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Q:双亲委派模型实现步骤?

①首先判断该类是否已经被加载;
②该类未被加载,同时父类不为空,交给父类加载;
③如果父类为空,交给启动类加载器Bootstrap ClassLoader加载;
④如果类还是无法被加载到,则触发findclass,抛出ClassNotFoundException(findclass这个方法当前只有一个语句,就是抛出ClassNotFoundException),如果想自己实现类加载器的话,可以继承ClassLoader后重写findclass方法,加载对应的类)。

Q:双亲委派模型的好处?

双亲委派模型 保证了Java程序的稳定运行,可以避免类的重复加载(JVM区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类★★★),也保证了Java的核心API不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object类的话,那么程序运行的时候,系统就会出现多个不同的Object类。

Q:如何判断两个Class对象是否是一个类?

在JVM中表示两个Class对象是否为同一个类存在两个必要条件:

  • 类的 完整类名必须一致 ,包括包名。
  • 加载这个类的类加载器 ClassLoader (指ClassLoader实例对象)必须相同。

换句话说,在JVM中,即使这两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的

Q:不想使用双亲委派模型怎么解决?

自定义加载器的话,需要继承ClassLoader。如果我们 不想打破双亲委派模型 ,就重写ClassLoader类中的findclass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果 想打破双亲委派模型 则需要重写loadClass()方法

Q:3次打破双亲委派机制的案例?
  • 第一次破坏
    第一次被破坏发生在双亲委派模型出现之前——即JDK 1.2之前。
  • 第二次破坏
    第二次被破坏是由这个模型自身的缺陷导致的,如果有基础类型又要调用回用户的代码,那该怎么办呢?如JDBC。
  • 第三次破坏
    第三次被破坏是由于用户对程序动态性的追求而导致的,例如代码热替换(Hot Swap)、模块热部署(Hot Deployment)等。
Q:如何实现热部署?

想要实现热部署可以分以下三个步骤:

  1. 销毁原来的自定义ClassLoader
  2. 更新class类文件
  3. 创建新的ClassLoader去加载更新后的class类文件。
    到此,一个热部署的功能就这样实现了。
5、Tomcat的类加载机制?

在这里插入图片描述

Tomcat破坏了双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。每一个WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。

四、JVM调优参数

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、JVM调优

1、常用的命令行性能监控和故障处理工具?
  • 操作系统工具
    top:显示系统整体资源使用情况。
    vmstat:监控内存和 CPU。
    iostat:监控 IO 使用。
    netstat:监控网络使用。
  • JDK性能监控工具
    jps:虚拟机进程查看。
    jstat:虚拟机运行时信息查看。
    jinfo:虚拟机配置查看。
    jmap:内存映像(导出)。
    jhat:堆转储快照分析。
    jstack:Java 堆栈跟踪。
    jcmd:实现上面除了jstat外所有命令的功能。
2、JVM可视化的性能监控和故障处理工具有?

JConsole、VisualVM、Java Mission Control(前3个为JDK自带)、MAT(Java堆内存分析工具)、GChisto(GC日志分析工具)、GCViewer(GC日志分析工具)、JProfiler(性能分析工具)、arthas(阿里开源诊断工具)

3、线上服务CPU占用过高怎么排查?

问题分析:CPU高一定是某个程序长期占用了CPU资源。
在这里插入图片描述

  1. 先找出是哪个进程占用CPU高:top 列出系统各个进程的资源占用情况。
  2. 然后根据找到对应进程里哪个线程占用CPU高:top -Hp 进程id 列出对应进程里面的线程占用资源情况。
  3. 找到对应线程id后,再打印出对应线程的堆栈信息:
    printf "%x\n" PID 把线程id转换为16进制。
    jstack PID 打印出进程的所有线程信息,从打印出来的线程信息中找到上一步转换为16进制的线程id对应的线程信息。
  4. 最后根据线程的堆栈信息定位到具体业务方法,从代码逻辑中找到问题所在。
    查看是否有线程长时间的watting或blocked,如果线程长期处于watting状态下,关注watting on xxxxxx,说明线程在等待这把锁,然后根据锁的地址找到持有锁的线程。
4、内存飙升怎么排查?

分析:内存飚高如果是发生在java进程上,一般是因为创建了大量对象所导致,持续飚高说明垃圾回收跟不上对象创建的速度,或者内存泄露导致对象无法回收。

  1. 先观察垃圾回收的情况
    jstat -gc PID 1000 查看GC次数、时间等信息,每隔一秒打印一次。
    jmap -histo PID | head -20 查看堆内存占用空间最大的前20个对象类型,可初步查看是哪个对象占用了内存。
    如果每次GC次数频繁,而且每次回收的内存空间也正常,那说明是因为对象创建速度快导致内存一直占用很高;如果每次回收的内存非常少,那么很可能是因为内存泄露导致内存一直无法被回收。
  2. 导出堆内存文件快照
    jmap -dump:live,format=b,file=/home/myheapdump.hprof PID dump 堆内存信息到文件。
  3. 使用visualVM对dump文件进行离线分析,找到占用内存高的对象,再找到创建该对象的业务代码位置,从代码和业务场景中定位具体问题。
5、频繁的年轻代垃圾回收怎么办?

优化Minor GC频繁问题:通常情况下,由于新生代空间较小,Eden区很快被填满,就会导致频繁Minor GC,因此可以通过 增大新生代空间-Xmn来降低 Minor GC的频率

6、频繁的整堆垃圾回收(full GC)怎么办?

Full GC的排查思路大概如下:
1. 清楚从程序角度,有哪些原因导致full GC?
· 大对象:系统一次性加载了过多数据到内存中(比如SQL查询未做分页),导致大对象进入了老年代。
· 内存泄漏:频繁创建了大量对象,但是无法被回收(比如IO对象使用完后未调用close方法释放资源),先引发full GC,最后导致OOM.
· 程序频繁生成一些长生命周期的对象,当这些对象的存活年龄超过分代年龄时便会进入老年代,最后引发full GC。
· 程序BUG
· 代码中显式调用了gc方法,包括自己的代码甚至框架中的代码。
· JVM参数设置问题:包括总内存大小、新生代和老年代的大小、Eden区和S区的大小、元空间大小、垃圾回收算法等等。

2. 清楚排查问题时能使用哪些工具?
JDK 的自带工具,包括jmap、jstat等常用命令:

# 查看堆内存各区域的使用率以及GC情况
jstat -gcutil -h20 pid 1000
# 查看堆内存中的存活对象,并按空间排序
jmap -histo pid | head -n20
# dump堆内存文件
jmap -dump:format=b,file=heap pid

可视化的堆内存分析工具:JVisualVM、MAT等

3. 排查指南
· 查看监控,以了解出现问题的时间点以及当前full GC的频率(可对比正常情况看频率是否正常)
· 了解该时间点之前有没有程序上线、基础组件升级等情况。
· 了解JVM的参数设置,包括:堆空间各个区域的大小设置,新生代和老年代分别采用了哪些垃圾收集器,然后分析JVM参数设置是否合理。
· 再对步骤1中列出的可能原因做排除法,其中元空间被打满、内存泄漏、代码显式调用gc方法比较容易排查。
· 针对大对象或者长生命周期对象导致的full GC,可通过 jmap -histo 命令并结合dump堆内存文件作进一步分析,需要先定位到可疑对象。
· 通过可疑对象定位到具体代码再次分析,这时候要结合GC原理和JVM参数设置,弄清楚可疑对象是否满足了进入到老年代的条件才能下结论。

7、内存泄漏和内存溢出如何排查?

内存泄漏是内在病源,外在病症表现可能有:(内存泄漏可能导致内存溢出,排查步骤和内存泄漏类似)

  • 应用程序长时间连续运行时性能严重下降。
  • CPU使用率飙升,甚至到100% 。
  • 频繁Full GC,各种报警,例如接口超时报警等。
  • 应用程序抛出OutOfMemoryError错误。
  • 应用程序偶尔会耗尽连接对象。

严重内存泄漏往往伴随频繁的Full GC,所以分析排查内存泄漏问题首先还得从查看Full GC入手。主要有以下操作步骤:

  1. 使用 jps 查看运行的 Java 进程 ID
  2. 使用 top -p [pid] 查看进程使用 CPU 和 MEM 的情况
  3. 使用 top -Hp [pid] 查看进程下的所有线程占 CPU 和 MEM 的情况
  4. 将线程 ID 转换为 16 进制:printf "%x\n" [pid],输出的值就是线程栈信息中的 nid。
  5. 抓取线程栈:jstack 29452 > 29452.txt,可以多抓几次做个对比。
  6. 使用 jstat -gcutil [pid] 5000 10 每隔 5 秒输出 GC 信息,输出 10 次,查看 YGC 和 Full GC 次数。通常会出现 YGC 不增加或增加缓慢,而 Full GC 增加很快。或使用 jstat -gccause [pid] 5000 ,同样是输出 GC 摘要信息。或使用 jmap -heap [pid] 查看堆的摘要信息,关注老年代内存使用是否达到阀值,若达到阀值就会执行 Full GC。
  7. 如果发现 Full GC 次数太多,就很大概率存在内存泄漏了
  8. 使用 jmap -histo:live [pid] 输出每个类的对象数量,内存大小(字节单位)及全限定类名。
  9. 生成 dump 文件,借助工具分析哪个对象非常多,基本就能定位到问题在那了。
  10. dump 文件分析
    可以使用 jhat 命令分析:jhat -port 8000 29471.dump,浏览器访问 jhat 服务,端口是 8000。或使用第三方式具分析的,如 JProfiler 也是个图形化工具,GCViewer 工具。Eclipse 或以使用 MAT 工具查看。或使用在线分析平台 GCEasy。
  11. 在 dump 文析结果中查找存在大量的对象,再查对其的引用。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值