[八股速成]JVM篇

前言

我之前整理过JVM超详细八股笔记:https://www.nowcoder.com/discuss/593844277646471168?sourceSSR=search,但是说实话因为这份这份八股资料过于详细,内容过于充实,给背记带来了很大的挑战,所以我准备再出一系列帖子,内容就是我根据自己的面试经历和网上的面经,去筛选八股里面哪些是最常被问到的问题把它们整理出来,这样也能省去大家自己整理和筛选的时间大家可以在面试前一两个小时快速把这一系列最常问八股的帖子拿出来看看,临时抱佛脚的效果应该很好。后面这系列帖子我会放入专栏https://www.nowcoder.com/creation/manager/columnDetail/0ybvLm,欢迎大家订阅。最后我想说,速成虽好,但是还是建议有时间就去看看我详细的八股笔记帖子。

想要学习Java冲实习或冲春招的,我能助你一臂之力,我之前整理了高质量可速成的魔改外卖项目话术和7000字轮子项目话术,还有超全超精品八股大全专栏怎么写简历怎么包装实习经历,怎么0基础速成冲春招和实习等等等等精品帖子,大家可以去看看我的精品文章汇总帖子:https://www.nowcoder.com/discuss/721704696242536448?sourceSSR=users

我的八股大全专栏(15w人学习,超千人订阅,牛客最受欢迎最高质量java八股专栏,多一句没有,少一句不行)https://www.nowcoder.com/creation/manager/columnDetail/j8ZZk0

1.常问问题

1.new一个对象到销毁的过程/java对象创建过程?

在Java中,从创建(new)一个对象到销毁的过程大致可以分为以下几个步骤:

  • 检查类是否加载:对象的实际创建是通过new关键字实现的。当JVM遇到new关键字时,会检查类是否已经加载到运行时数据区的方法区,如果没有,则先进行类的加载和初始化。
  • 分配内存空间:当使用new关键字创建一个对象时,JVM会在运行时数据区的堆中为该对象分配相应的内存空间。
  • 初始化对象:JVM会调用相应的构造方法来初始化对象,给对象的成员变量赋予初始值。
  • 使用对象:在程序运行过程中,可以通过对象引用来访问和操作对象的成员变量和方法。
  • 对象不再被引用:当对象不再被任何变量或数据结构引用时,它就成为垃圾对象,等待垃圾回收器进行回收。
  • 垃圾回收:JVM的垃圾回收器会定期检查堆内存中的垃圾对象,并自动回收它们所占用的内存空间。垃圾回收的具体时机和策略取决于JVM的垃圾回收算法。
  • 内存空间释放:当垃圾对象被回收后,它们占用的内存空间会被释放,以便重新分配给新创建的对象。

需要注意的是,JVM的垃圾回收机制会自动处理对象的销毁和内存空间的释放,程序员无需手动进行这些操作。但是,为了提高程序性能和避免内存泄漏,程序员应该养成良好的编程习惯,及时释放不再使用的对象引用。

2.java程序运行流程是什么?(编译+运行)

运行流程:

(-1) 编写java代码,文件后缀名为.java

(0)通过java编译器(如javac)将java源代码编译成.class字节码文件

(1)类加载器(ClassLoader)将 class 字节码文件加载到内存中(运行时数据区),但是字节码文件是JVM定义的一套指令集规范,并不能直接交给底层操作系统去执行

(2)特定的命令解释器(执行引擎)将class字节码翻译成特定的操作系统指令集交给 CPU 去执行

(3)此时可能需要调用其他语言的本地库接口(Native Method Library)来实现整个程序的功能

3.类加载执行过程

Class 字节码文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢?

类加载的全过程,即加载、验证、准备、解析和初始化这五个阶段。

  • 加载:查找和导入Class字节码文件通过一个类的全限定名来获取定义此类的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
  • 验证:保证加载类的准确性这一阶段的目的是确保Class文件的字节流包含的信息符合《Java虚拟机规范》的所有约束要求,从而保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
  • 准备:为类变量(即静态变量)分配内存并设置类变量初始值需要注意的是,这时候进行内存分配的仅包括类变量,而不包括实例变量
  • 解析:把类中的符号引用转换为直接引用解析阶段主要是将常量池内的符号引用转换为直接引用。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载。符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式 的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目 标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布局可以各不相同,但是它们 能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java虚拟机规范》的Class 文件格式中。直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接 定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实 例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中 存在。
  • 初始化:执行类的构造器方法初始化阶段是执行类的构造器方法的过程。这个方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并而来的。若该类具有父类,JVM会保证父类的先执行,然后才执行子类的。

4.说下java的编译和运行2阶段

在编译阶段中,Java源代码通过前端编译器转换成字节码文件,即.class文件。而在运行阶段,这些字节码文件会被Java虚拟机(JVM)加载并执行。

编译时类型和运行时类型

  • 编译时类型(Compile-time Type)

编译时类型是指在编译阶段确定的对象类型。这是由代码中的声明决定的,例如变量声明、方法参数或返回类型的声明。编译器使用这些类型来执行类型检查,确保代码符合Java语言的语法规则和类型系统规则。如果代码违反了这些规则,编译器将生成错误,并且程序无法编译成功。

  • 运行时类型(Run-time Type)

运行时类型是指对象在程序实际执行时的实际类型。在运行时,对象的真实类型可能与编译时类型不同,尤其是在使用继承和多态的情况下。例如,如果你有一个父类类型的引用指向一个子类实例,那么该引用的编译时类型是父类,但其运行时类型是子类。

Animal animal = new Dog(); // 或者 new Cat()

在这个例子中:

  • animal编译时类型Animal,因为这就是它在代码中被声明的类型。
  • animal运行时类型可能是 Dog 或者 Cat,具体取决于创建时传入的是哪个子类的实例。

2.JVM组成

1.JVM是什么(实现java跨平台)

JVM(Java虚拟机)是Java跨平台的关键。在程序运行前,Java源代码(.java)需要经过编译器编译成字节码(.class)。在程序运行时,JVM负责将字节码翻译成特定平台下的机器码并运行,也就是说,只要在不同的平台上安装对应的JVM,就可以运行字节码文件。同一份Java源代码在不同的平台上运行,它不需要做任何的改变,并且只需要编译一次。而编译好的字节码,是通过JVM这个中间的“桥梁”实现跨平台的,JVM是与平台相关的软件,它能将统一的字节码翻译成该平台的机器码

好处:

  • 一次编写,到处运行
  • 自动内存管理,垃圾回收机制

JVM怎么实现一次编写,到处运行?

JVM(Java虚拟机)通过实现一次编写,到处运行的机制,使得Java程序可以在不同平台上运行。具体实现方式如下:

  1. Java源代码:首先,将Java源代码编译成字节码文件(.class文件)。字节码是一种中间代码,介于源代码和机器码之间,具有平台无关性。
  2. 字节码文件:字节码文件可以在任何安装了JVM的平台上运行。JVM负责将字节码文件解释执行或者即时编译成本地机器码。
  3. JVM:JVM是Java程序的运行环境,它负责加载字节码文件、解释执行字节码或者将字节码即时编译成本地机器码。不同的操作系统和硬件平台上有不同的JVM实现,如Windows、Linux、macOS等。
  4. 跨平台支持:由于JVM的存在,Java程序可以在不同的操作系统和硬件平台上运行,实现了一次编写,到处运行的目标。

2.JVM由哪些部分组成

  • ClassLoader(类加载器):负责加载字节码文件(即 class 文件)到运行时数据区,class 文件在文件开头有特定的文件标示,并且ClassLoader 只负责class 文件的加载,至于它是否可以运行,则由 Execution Engine 决定。
  • Runtime Data Area(运行时数据区,即java内存):是存放java内存相关数据的,分为五部分:Stack(虚拟机栈),Heap(堆),MethodArea(方法区),PC Register(程序计数器),Native Method Stack(本地方法栈)。几乎所有的关于 Java 内存方面的问题,都是集中在这块。
  • Execution Engine(执行引擎):Class 文件被加载后,会把指令和数据信息放入内存中,Execution Engine 则负责把这些命令解释给操作系统,即将 JVM 指令集翻译为操作系统指令集。
  • Native Method Library(本地库接口):负责调用本地接口的。他的作用是调用不同语言的本地接口给 JAVA 用

3.运行时数据区(JVM内存)

1.运行时数据区包含了哪几个部分?

运行时数据区包含了堆、方法区、虚拟机栈、本地方法栈、程序计数器这几部分,每个功能作用不一样

  • 堆:Java堆是线程共享的区域,主要用于存储new出来的对象实例(包括Class对象:每个类在加载到JVM的方法区时都会产生一个相应的Class对象)。在这里分配对象实例的内存空间,它是垃圾收集器管理的主要区域,通过-Xmx和-Xms参数可以调整堆的大小。堆内存的合理分配和释放对于Java程序的性能至关重要。堆是Java虚拟机所管理的内存中最大的一块,用于存放所有类实例和数组对象。Java堆可以细分为新生代(Young Generation)和老年代(Old Generation),其中新生代又可以进一步细分为Eden空间和两个Survivor空间(S0, S1)。空间大小比eden:survivor:survivor=8:1:1对象的创建几乎都在堆上进行,而垃圾回收也主要是在堆上进行。
  • 方法区:方法区同样是线程共享的区域,用于存储已被虚拟机加载的类的元数据信息。方法区存储的信息较为持久,通常不会被频繁地创建和销毁。方法区也称为非堆区,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 虚拟机栈和本地方法栈:都是线程私有的,用于存储方法调用的相关信息(用于存储局部变量表、操作数栈、动态链接、方法出口等信息。),前者服务于Java方法,后者服务于本地方法虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
  • **程序计数器(PC寄存器):**每个线程私有, 用于存储当前线程执行的字节码指令的行号。JVM工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。每个线程拥有一个独立的程序计数器,用于指示当前线程所执行的字节码指令的位置。如果执行的是本地(Native)方法,那么程序计数器的值为空(Undefined)。

2.Java堆(线程共享)

1.什么是java堆?

:Java堆是线程共享的区域,主要用于存储new出来的对象实例(包括Class对象:每个类在加载到JVM的方法区时都会产生一个相应的Class对象)。在这里分配对象实例的内存空间,它是垃圾收集器管理的主要区域,通过-Xmx和-Xms参数可以调整堆的大小。堆内存的合理分配和释放对于Java程序的性能至关重要。

  • 堆是Java虚拟机所管理的内存中最大的一块,用于存放所有类实例和数组对象。
  • Java堆可以细分为新生代(Young Generation)和老年代(Old Generation),方法区,其中新生代又可以进一步细分为Eden空间和两个Survivor空间(S0, S1)。
  • 对象的创建几乎都在堆上进行,而垃圾回收也主要是在堆上进行。

在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:

  1. 新生代
  2. 老生代
  3. 永久代

JDK 8 版本之后 永久代已被 Metaspace(元空间) 取代,元空间使用的是本地内存。

2.讲一下新生代、老年代、永久代的区别?

  • 新生代主要用来存放新生的对象。新生代又被进一步划分为 Eden区 和 Survivor区,Survivor 区由 From Survivor 和 To Survivor 组成。新生代通常采用年轻代垃圾回收算法,如复制算法,能够高效地回收生命周期短的对象这里主要存放新创建的对象,以及那些经过几次垃圾回收后仍然存活的对象。年轻代的垃圾回收频率较高,因为大部分对象在这里很快就被回收。新生代内又分三个区:一个Eden区,两个Survivor区(S0、S1,又称From Survivor、To Survivor),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足晋升到老年代条件的对象将被复制到另外一个Survivor区。对象每经历一次复制,年龄加1,达到晋升年龄阈值后,转移到老年代
  • 老年代主要存放应用中生命周期长的内存对象。老年代通常采用标记-清除或标记-整理算法,适合回收生命周期较长的对象。经过多次年轻代垃圾回收后仍然存活的对象会被提升到老年代。老年代的垃圾回收频率较低,但每次回收可能需要更长时间。
  • 永久代指的是永久保存区域。主要存放类的元数据,包括类的定义信息、运行时常量池、字段和方法的数据等。。在Java8中,永久代已经被移除,取而代之的是一个称之为“元数据区”(元空间)的区域。元空间和永久代类似,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存的限制。

3.Java对象如何从新生代转移到老年代?

Java对象从新生代晋升到老年代的四种方式是:

  • 年龄晋升:每个对象都有一个年龄计数器,当对象经过一次Minor GC后仍然存活,则该对象的年龄加一。一旦对象的年龄达到预设的阈值(默认通常是15),它就会被移动到老年代。 虚拟机给每个对象定义了一个对象年龄(Age)计数器,存储在对象头中。对象通常在Eden区里诞生,如果经过第一次MinorGC(当 Eden 区空间不够时,发起 Minor GC。)后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor FROM空间中,并且将其对象年龄设为1岁。每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次Minor GC 时,From 与 To 职责对换**对象在Survivor区中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
  • 大对象直接进入老年代:如果一个对象的大小超过新生代的一半,那么它会被直接分配到老年代。
  • 永久代对象晋升到老年代:永久代中的对象在垃圾回收时可能会被晋升到老年代。
  • 动态对象年龄判定如果Survivor区中某个年龄段的对象总大小超过了Survivor区的一半(可通过-XX:TargetSurvivorRatio调整比例),那么年龄大于或等于该年龄段的所有对象下次想进入survivor区都将直接晋升至老年代。
  • survivor区空间不够:如果在进行了一次Minor GC后,Survivor区不足以容纳上一次GC后存活下来的对象,那么这些对象也会被移动到老年代。

4. 类加载器ClassLodar

1.什么是类加载器,类加载器有哪些?

类加载器

JVM只会运行二进制文件,而类加载器(ClassLoader)的主要作用就是将字节码文件加载到JVM运行时数据区中,从而让Java程序能够启动起来。现有的类加载器基本上都是java.lang.ClassLoader的子类,该类的只要职责就是用于将指定的类找到或生成对应的字节码文件,同时类加载器还会负责加载程序所需要的资源

类加载器种类

类加载器根据各自加载范围的不同,划分为四种类加载器:

  • 启动类加载器(BootStrap ClassLoader):该类并不继承ClassLoader类,其是由C++编写实现。用于加载JAVA_HOME/jre/lib目录下的类库。
  • 扩展类加载器(ExtClassLoader):该类是ClassLoader的子类,主要加载JAVA_HOME/jre/lib/ext目录中的类库。加载扩展的 jar 包
  • 应用类加载器(AppClassLoader):该类是ClassLoader的子类,主要用于加载classPath下的类,也就是加载开发者自己编写的Java类。
  • 自定义类加载器:开发者自定义类继承ClassLoader,实现自定义类加载规则。

类加载器的体系并不是“继承”体系,而是委派体系,类加载器首先会到自己的parent中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次。

2.什么是双亲委派模型?

类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载呢?这就需要提到双亲委派模型了。

在Java中,类加载器负责将.class文件加载到JVM中,使之成为可运行的类。Java的类加载机制采用双亲委派模型来保证类的唯一性和安全性。

在这个模型中,每个类加载器都有一个父加载器,形成了一个层次结构。位于最顶层的是启动类加载器(Bootstrap ClassLoader),它没有父加载器,由JVM自身实现。下面是扩展类加载器(Extension ClassLoader),再下面是应用程序类加载器(Application ClassLoader),也被称为系统类加载器,它是用户自定义类加载器的默认父加载器。

每个类加载器在尝试加载一个类时,会首先检查请求加载的类型是否已经被加载过,若没有将这个任务委托给它的父加载器去完成。只有当父加载器无法找到并加载请求的类时,子加载器才会尝试自己去加载该类。

3.打破双亲委派模型方法

可以自己定义一个类加载器,要继承 ClassLoader ,重写loadClass()方法来自定义类加载行为。然而,更常见的是只重写findClass()方法,因为在loadClass()方法中有现成的双亲委派逻辑,你只需要覆盖查找类的部分即可。

5.GC垃圾收回

1.STW(stop the world)

什么是STW?

  • stop the world指的是GC事件发生过程中,会产生应用程序的停顿。**停顿产生时整个应用程序线程都会被暂停,用户线程也会被暂停,**没有任何响应, 有点像卡死的感觉,这个停顿称为STW。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。
  • 被STW中断的应用程序线程会在完成GC之后恢复,频繁中断会让用户感觉像是网速不快造成电影卡带一样, 所以我们需要减少STW的发生。
  • STW事件和采用哪款GC无关,所有的GC都有这个事件。
  • 哪怕是G1也不能完全避免stop-the-world情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能地缩短了暂停时间。

2.对象什么时候回收?如何判断一个对象是否可以回收?

当Java对象没有任何引用指向它,它会被垃圾回收。即使存在软引用、弱引用或虚引用,如果所有的强引用都被断开,对象也会变为可回收状态。

如果要定位什么是垃圾,有两种方式来确定,第一个是引用计数法,第二个是可达性分析算法

  • 1.引用计数算法

给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。

两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。

正因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法。

  • 2.可达性分析算法从一系列称为GC Roots的根集合对象出发,通过引用链向下搜索,如果一个对象与GC Roots不可达,则该对象可被回收。

3. JVM 垃圾回收算法有哪些?(常问)

1.标记清除算法(老年代)

标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除

1.根据可达性分析算法得出的垃圾进行标记

2.对这些标记为可回收的内容进行垃圾回收

标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收。

同样,标记清除算法也是有缺点的:

  • 效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。
  • 重要)通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。

2.复制算法(用于年青代GC)

​ 复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将存活的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。

优点:

  • 在垃圾对象多的情况下,效率较高
  • 清理后,内存无碎片

缺点:

  • 分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低

3.标记整理算法

​ 标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的直接清理可回收对象,而是将存活对象都向内存另一端移动,然后清理边界以外的垃圾,从而解决了碎片化的问题。

具体步骤:

1)标记垃圾。

2)需要清除向右边走,不需要清除的向左边走。

3)清除边界以外的垃圾。

优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。

与复制算法对比:复制算法标记完就复制,但标记整理算法得等把所有存活对象都标记完毕,再进行整理

4. 分代收集算法

它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。

一般将堆分为新生代和老年代。

  • 新生代使用:复制算法
  • 老年代使用:标记 - 清除 或者 标记 - 整理 算法

为什么新生代和老年代要采用不同的回收算法?

  • 新生代对象生命周期短回收频率高:新生代主要存储新创建的对象,这些对象的生命周期通常较短。由于大部分对象在经过几次垃圾回收后就不再使用,因此适合使用复制算法进行快速回收。新生代的回收频率较高,需要一种能够快速处理大量短期对象的算法。复制算法通过将存货对象复制到另一半空间,然后清空这半块空间,这种分块处理方式能够迅速回收内存。
  • 老年代对象生命周期长,回收频率低:由于老年代中的对象生命周期较长,垃圾回收的频率相对较低。标记-清除算法适用于老年代,因为它可以有效处理大对象,并且内存碎片的问题可以通过定期的标记-整理来解决。

6.OOM

1.内存泄漏和内存溢出有什么区别?

  • 内存泄漏(memory leak):内存泄漏指程序运行过程中分配内存给临时变量,用完之后却没有被GC 回收,始终占用着内存,既不能被使用也不能分配给其他程序,于是就发生了内存泄漏。
  • 内存溢出(out of memory):简单地说内存溢出就是指程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存,于是就发生了内存溢出。

2.使用 ThreadLocal 造成内存泄露(常问)

每个线程都有⼀个ThreadLocalMap类型的名为Threadlocals的变量,它是存键值对的数组,key是ThreaLocal对象的弱引用,value是强引用类型。垃圾回收的时候会⾃动回收key,而value的回收取决于Thread对象的生命周期。一般会通过线程池的方式复用线程节省资源,这也就导致了线程对象的生命周期比较长,,随着任务的执行,value就有可能越来越多且无法释放,最终导致内存泄漏。

解决⽅法:每次使⽤完ThreadLocal就调⽤它的remove()⽅法,手动将对应的键值对删除,从⽽避免内存泄漏

7. JVM调优

1.java内存泄露的排查思路?

原因:

如果线程请求分配的栈容量超过java虚拟机栈允许的最大容量的时候,java虚拟机将抛出一个StackOverFlowError异常

如果java虚拟机栈可以动态拓展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成拓展,或者在建立新线程的时候没有足够的内存去创建对应的虚拟机栈,那java虚拟机将会抛出一个OutOfMemoryError异常

如果一次加载的类太多,元空间内存不足,则会报OutOfMemoryError: Metaspace

内存泄露的排查思路

第一呢可以通过jmap指定打印他的内存快照 dump文件,不过有的情况打印不了,我们会设置vm参数让程序自动生成dump文件

第二,可以通过工具去分析 dump文件,jdk自带的VisualVM就可以分析

第三,通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题

第四,找到对应的代码,通过阅读上下文的情况,进行修复即可

2.CPU飙高排查方案与思路?

1.使用top命令查看占用cpu的情况

2.通过top命令查看后,可以查看是哪一个进程占用cpu较高,上图所示的进程为:30978

3.查看当前线程中的进程信息

ps H -eo pid,tid,%cpu | grep 40940

pid 进行id

tid 进程中的线程id

% cpu使用率

4.可以根据线程 id 找到有问题的线程,进一步定位到问题代码的源码行号

执行命令

jstack 30978   此处是进程id

#jvm##面试时最害怕被问到的问题##八股##java##牛客创作赏金赛#
全部评论
收藏了感谢大佬
点赞 回复 分享
发布于 今天 11:21 湖北
收藏了感谢大佬
点赞 回复 分享
发布于 今天 17:20 北京

相关推荐

评论
7
26
分享

创作者周榜

更多
牛客网
牛客企业服务