八股—7.JVM 八股编绳教程
1. JVM组成
1.1 JVM由哪些部分组成?运行流程?(讲一下JVM)⭐️
难易程度:☆☆☆ 出现频率:☆☆☆☆
Java Virtual Machine:Java 虚拟机,Java程序的运行环境(java二进制字节码的运行环境) 好处:一次编写,到处运行;自动内存管理,垃圾回收机制
程序运行之前,需要先通过编译器将 Java 源代码文件编译成 Java 字节码文件;
程序运行时,JVM 会对字节码文件进行逐行解释,翻译成机器码指令,并交给对应的操作 体系去执行。
好处:一次编写,到处运行;自动内存管理,垃圾回收机制
JVM <—> 操作 体系(windows、linux)<—> 计算机硬件(cpu、内存条) java跨平台是因JVM屏蔽了操作 体系的差异,真正运行代码的不是操作 体系
JVM 主要由四个部分组成: 运行流程: Java 编译器(javac)将 Java 代码转换为字节码(.class 文件)
1. 类加载器(ClassLoader)
负责加载 .class 文件,将 Java 字节码加载到内存中,并交给 JVM 执行
2. 运行时数据区(Runtime Data Area)
管理JVM使用的内存。主要包括:
技巧区(Method Area):存储类的元数据、常量、静态变量等。
堆(Heap):存储所有对象和数组,垃圾回收器主要回收堆中的对象。
栈(Stack):每个线程都有一个栈,用于存储局部变量、 技巧调用等信息。
程序计数器(PC Register):每个线程有一个程序计数器,指示当前线程正在执行的字节码指令地址。
本地 技巧栈(Native Method Stack):支持本地 技巧的调用(通过 JNI)。 其中 技巧区和堆是线程共享的,虚拟机栈、本地 技巧栈和程序计数器是线程私有的。
3. 执行引擎(Execution Engine)
负责执行字节码,包含:
解释器:逐条解释执行字节码。
JIT 编译器:将热点代码编译为机器码, 进步执行效率。
垃圾回收器:回收堆中的不再使用的对象,释放内存。
4. 本地库接口(Native Method Library)
允许 Java 程序通过 java本地接口JNI(Java Native Inte ce)调用本地 技巧(如 C/C++ 编写的代码),与底层 体系或硬件交互。
1.2 何是程序计数器?
难易程度:☆☆☆ 出现频率:☆☆☆☆
程序计数器:线程私有的,每个线程一份,内部保存字节码的行号。用于记录正在执行的字节码指令的地址。 每个线程都有自己的程序计数器,确保线程切换时能够继续执行未完成的任务。
1.3 你能给我详细的介绍Java堆吗?⭐️⭐️
难易程度:☆☆☆ 出现频率:☆☆☆☆
Java堆是 JVM 中用于存储所有对象和数组的内存区域。线程共享的区域。当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。
它被分为:
年轻代(存储新创建的对象),被划分为三部分: Eden区:大多数新对象的分配区域; S0 和 S1(两个 大致严格相同的Survivor区):Eden 空间经过 GC 后存活下来的对象会被移到其中一个 Survivor 区域; 老年代:在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到老年代区间。 永久代:JDK 7 及之前,JVM 的 技巧区(也称永久代),保存的类信息、静态变量、常量、编译后的代码; 元空间:JDK 8 及之后,永久代被 Metaspace(元空间)取代,移除了永久代,把数据存储到了本地内存的元空间中,且其 大致不再受 JVM 堆的限制,防止内存溢出。
1.4 何是虚拟机栈
难易程度:☆☆☆ 出现频率:☆☆☆☆
Java Virtual chine Stacks (java 虚拟机栈)
每个线程在 JVM 中私有的一块内存区域,称为虚拟机栈,先进后出,用于存储 技巧的局部变量和 技巧调用信息;
每个栈由多个栈帧(frame)组成,当线程执行 技巧时,为该 技巧分配一个栈帧(Stack Frame);
每个线程只能有一个活动栈帧,对应着当前正在执行的那个 技巧;
垃圾回收是否涉及栈内存?
垃圾回收主要指就是堆内存, 栈内存中不会有垃圾回收的概念, 由于栈内存是由 JVM 自动管理的, 技巧执行完成时,栈帧弹栈,内存就会释放;
栈内存分配越大越好吗?
未必,默认的栈内存通常为1024k; 栈内存过大会导致线程数变少,例如,机器总内存为512m,目前能活动的线程数则为512个,如果把栈内存改为2048k,那么能活动的线程数减半;
技巧内的局部变量是否线程安全?
技巧内的局部变量 本身是线程安全的, 由于它们存储在每个线程独立的栈中,不会被其他线程共享。 但如果局部变量是引用类型, 并且该引用指向的对象逃离了 技巧 影响范围(例如被返回或传递到外部),则需要考虑该对象的线程安全性。 如果对象是可变的, 并且被多个线程访问,可能会引发线程安全 难题。
栈内存溢出情况(StackOverflowError)
栈帧过多导致栈内存溢出; 典型 难题:递归调用会在栈中创建新的栈帧,如果递归深度过大,可能会导致栈空间耗尽,从而抛出
栈帧过大导致栈内存溢出
堆栈的区别是 何?
栈内存用来存储局部变量和 技巧调用,但堆内存是用来存储Java对象和数组的。 堆会GC垃圾回收,而栈不会; 栈内存是线程私有的,而堆内存是线程共有的; 两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常 栈空间不足:java.lang.StackOverFlowError 堆空间不足:java.lang.OutOfMemoryError
1.5 运行时数据区中包含哪些区域? ⭐️⭐️⭐️⭐️⭐️
JVM 运行时数据区包括 技巧区、堆、栈、程序计数器和本地 技巧栈。
技巧区存储类的元数据、常量池、静态变量和 JIT 编译后的代码。 类的结构信息:每个类的信息,如类名、父类名、接口、 技巧、字段的名称和描述等。常量池:存储常量值,如字符串常量、类常量等。 静态变量:属于类的变量,而不是某个实例的变量。 JIT编译后的代码是指 Jvm在运行时将热点代码从字节码编译为本地机器代码。 技巧区在 JDK 7 之前被称为 “永久代”,从 JDK 8 开始,永久代被移除,改为使用元空间 堆是 JVM 中最大的内存区域,负责存储所有的对象实例和数组,并进行垃圾回收。 虚拟机栈存储每个线程的局部变量、 技巧调用信息和返回地址等。 程序计数器是每个线程私有的,用于存储当前线程正在执行的字节码指令的地址。 本地 技巧栈支持 JNI 本地 技巧调用,线程私有。 专门为本地 技巧调用而设计。它用于执行本地代码时所需的栈空间。
在 JDK 1.8 时 JVM 的内存结构主要有两点不同,
一个是 技巧区(Method Area)在 JDK 1.8 被替换为元空间(Metaspace),且元空间使用本地内存。 另一个是运行时常量池(Runtime Constant Pool)在 JDK 1.7 属于 技巧区的一部分,而在 JDK 1.8 变成元空间的一部分。
哪些线程私有?哪些线程共享?
线程私有的:线程计数器、虚拟机栈、本地 技巧栈 线程共享的:堆、 技巧区
哪些区域可能会出现 OutOfMemoryError?
堆(Heap): 当堆内存不足时,会抛出OutOfMemoryError。 技巧区(Method Area): 当 技巧区内存不足时,会抛出OutOfMemoryError,通常是 由于加载的类太多或常量池中的数据过多。
1.6 能不能解释一下 技巧区?
难易程度:☆☆☆ 出现频率:☆☆☆
技巧区 是 JVM 运行时数据区的一部分,主要用于存储类的信息、常量、静态变量以及 JIT 编译后的代码。 在 JDK 7 之前,这部分内存称为永久代(PermGen),而在 JDK 8 以后,永久代被移除,取而代之的是元空间(Metaspace),它位于本地内存中,不再受堆内存限制。
虚拟机启动的时候创建,关闭虚拟机时释放。
技巧区的内存由 JVM 管理,并在类卸载时进行垃圾回收。
如果 技巧区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspac。
技巧区和永久代以及元空间是 何关系呢? ⭐️
技巧区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是 技巧区, 技巧区是java虚拟机规范中的一个概念,而永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中 技巧区的两种实现方式。 永久代是 JDK 1.8 之前的 技巧区实现,JDK 1.8 及以后 技巧区的实现变成了元空间。
元空间和 技巧区的区别?
元空间(Metaspace)和 技巧区的主要区别在于:
内存存储位置: 技巧区在JVM的堆内存中,而元空间使用的是本地内存(Native Memory)。
内存管理: 技巧区受JVM管理,可能导致OutOfMemoryError;元空间由操作 体系管理,避免了这个 难题。
JVM版本差异: 技巧区在JDK 7及之前存在,JDK 8后被元空间取代。
内存溢出: 技巧区可能 由于空间不足引发OutOfMemoryError,而元空间则避免了这个 难题, 由于它不依赖堆内存。
元空间有 何 影响? 何故要有元空间?
元空间(Metaspace)的 影响是存储JVM类的元数据,它在JDK 8及之后的版本中取代了之前的永久代(PermGen)。 与永久代不同,元空间不再使用堆内存,而是直接使用本地内存(Native Memory)。这是为了 进步性能和避免永久代内存溢出的风险。
主要有两个 缘故:
解决内存溢出 难题:JDK 7 及之前的永久代(PermGen)存储类元数据,容易出现 OutOfMemoryError。而元空间(Metaspace)使用本地内存,仅受 体系内存限制,避免了该 难题。
性能优化:元空间使用本地内存而非堆内存存储类元数据,提升了类加载性能,减少了垃圾回收(GC)的压力, 由于元空间的回收由 JVM 和操作 体系共同管理,无需频繁触发 GC。
Java对象的创建 经过?⭐️⭐️⭐️⭐️⭐️
类加载检查,当程序执行到 new 指令时,JVM 会先检查对应的类是否已经被加载、解析和初始化过。如果类尚未加载,JVM 会按照类加载机制(加载、验证、准备、解析、初始化)完成类的加载 经过。这一步确保了类的元信息(如字段、 技巧等)已经准备好,为后续的对象创建奠定基础。 内存的分配,JVM 会为新对象分配内存空间。对象所需的内存 大致在类加载完成后就可以确定,因此分配内存的 经过就是从堆中划分一块连续的空间,主要有两种方式:
①一种是通过指针碰撞,如果堆中的内存是规整的(已使用和空闲区域之间有明确分界),JVM 可以通过移动指针来分配内存。 ②另一种是通过空闲列表,如果堆中的内存是碎片化的,JVM 会维护一个空闲列表,记录可用的内存块,并从中分配合适的区域。 除了这些之后,为了保证多线程环境下的安全性,JVM 还会采用两种策略避免内存分配冲突,一种是通过 CAS 操作尝试更新分配指针,如果失败则重试;另一种是每个线程在堆中预先分配一小块专属区域,避免线程间的竞争。
零值初始化,JVM 会对分配的内存空间进行初始化,将其所有字段设置为零值(如 int 为 0,boolean 为 false,引用类型为 null)。这一步确保了对象的实例字段在未显式赋值前有一个默认值,从而避免未初始化的变量被访问。 设置对象头,其中包含Mark Word、Klass Pointer和数组长度。Mark Word 用于存储对象的哈希码、GC 分代年龄、锁 情形标志等信息。Klass Pointer 指向对象所属类的元数据(即 Person.class 的地址)。 执行构造 技巧,用<init> 技巧完成对象的初始化。构造 技巧会根据代码逻辑对对象的字段进行赋值,并调用父类的构造 技巧完成继承链的初始化。这一步完成后,对象才真正可用。
Java 创建对象的四种常见方式
new 关键字(最常见)
使用 new 关键字可以直接创建对象,并调用 无参或有参构造 技巧 进行初始化。
反射
反射机制可以在运行时动态创建对象。
clone() 技巧(对象克隆)
clone() 技巧用于创建一个相同内容的新对象,不会调用构造 技巧。需要实现 Cloneable 接口,并重写 clone() 技巧。
反序列化(Serializable)
反序列化可以将存储或传输的对象数据恢复成 Java 对象,不会调用构造 技巧。
对象访问定位的两种方式知道吗?优缺点?⭐️
对象访问定位是 JVM 中通过引用变量找到实际对象的 经过。在 Java 中,有两种主要的对象访问定位方式:句柄和直接指针。
句柄访问:引用变量存储的是句柄的地址,句柄再指向对象。
优点:GC 时对象移动不影响引用,稳定性高;实例和类型数据分离,便于管理。 缺点:访问慢(两次跳转),内存占用大(需要句柄池)。
直接指针访问:引用变量直接存储对象地址。
优点:访问快(一次跳转),内存占用小。 缺点:GC 时需要修改所有引用,开销大。
1.7 介绍一下运行时常量池?
常量池
可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、 技巧名、参数类型、字面量等信息
运行时常量池
常量池是 .class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为 诚恳地址
1.8 你听过直接内存吗?
难易程度:☆☆☆ 出现频率:☆☆☆
它不受 JVM 内存回收管理,是虚拟机的 体系内存; 常见于在 NIO 中使用直接内存,不需要在堆中开辟空间进行数据的拷贝,jvm可以直接操作直接内存,从而使数据读写传输更快。但分配回收成本较高。
使用传统的IO的 时刻要比NIO操作的 时刻长了很多,也就说NIO的读性能更好。
这个是跟我们的JVM的直接内存是有一定关系,如下图,传统阻塞IO的数据传输流程和NIO传输数据的流程。
2. 类加载器
2.1 何是类加载器,类加载器有哪些?
难易程度:☆☆☆☆ 出现频率:☆☆☆
JVM只会运行二进制文件,而类加载器(ClassLoader)的主要 影响就是将.class字节码文件加载到JVM内存,生成对应的Class对象,供程序使用。
启动类加载器(BootStrap ClassLoader): 该类并不继承ClassLoader类,其是由C++编写实现。用于加载J A_HOME/jre/lib目录下的类库。
扩展类加载器(ExtClassLoader): 该类是ClassLoader的子类,主要加载J A_HOME/jre/lib/ext目录中的类库。
应用类加载器(AppClassLoader): 该类是ClassLoader的子类,主要用于加载classPath下的类,也就是加载开发者自己编写的Java类。
自定义类加载器: 开发者自定义类继承ClassLoader,实现自定义类加载 制度。
类加载器的体系并不是“继承”体系,而是委派体系,类加载器首先会到自己的parent中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次
2.2 何是双亲委派模型?⭐️⭐️⭐️⭐️
难易程度:☆☆☆☆ 出现频率:☆☆☆☆
双亲委派模型要求类加载器在加载某一个类时,先委托父加载器尝试加载。 如果父加载器可以完成类加载任务,就返回成功; 只有父加载器无法加载时,子加载器才会加载。
2.3 JVM 何故采用双亲委派机制?(好处)⭐️
避免类的重复加载:父加载器加载的类,子加载器无需重复加载。
保证核心类库的安全性:为了安全,保证类库API不会被修改。如 java.lang包下的类只能由 启动类加载器Bootstrap ClassLoader 加载,防止被篡改。
2.4 说一下类的 生活周期?
一个类从被加载到虚拟机内存中开始,到从内存中卸载,它的整个 生活周期包括了: 加载、验证、准备、解析、初始化、使用和卸载这7个阶段。 其中,验证、准备和解析这三个部分统称为连接(linking)。
2.5 说一下类装载的执行 经过?⭐️⭐️⭐️⭐️⭐️
难易程度:☆☆☆☆☆ 出现频率:☆☆☆
类装载 经过包括三个阶段:载入、连接和初始化,连接细分为 验证、准备、解析,这是标准的 JVM 类装载流程。
加载(Loading):通过类加载器找到 .class 文件读取到内存,生成 Class 对象。 连接(Linking): 验证:检查字节码是否合法,防止恶意代码破坏 JVM; 准备:为类的静态变量分配内存并设置默认初始值,但不执行赋值逻辑; 解析:将常量池中的 符号引用(如类名、 技巧名)转为 直接引用(内存地址)。 初始化(Initialization):执行类的静态代码块和静态变量赋值。
在准备阶段,静态变量已经被赋过默认初始值了,在初始化阶段,静态变量将被赋值为代码期望赋的值。比如说 static int a = 1;,在准备阶段,a 的值为 0,在初始化阶段,a 的值为 1
类装载完成后的阶段:加载完成后,类进入‘使用阶段’。当 Class 对象不再被引用时,可能触发‘卸载’。
使用:JVM 通过 Class 对象创建实例、调用 技巧,进入正常运行阶段。 卸载:当 Class 对象不再被引用时,由 GC 回收,但 JVM 核心类(如 java.lang.*)不会被卸载。
3. 垃圾回收
3.1 简述Java垃圾回收机制?(GC是 何? 何故要GC)
GC(Garbage Collection,垃圾回收)是 Java 中自动管理内存的机制,负责回收不再使用的对象,以释放内存空间。 垃圾回收是 Java 程序员不需要显式管理内存的一大优势,它由 JVM 自动进行。
GC 的主要目的是: 自动管理内存:程序运行 经过中会创建大量的对象,但一些对象在使用完后不再被引用。手动管理这些对象的内存释放非常繁琐且容易出错; 防止内存泄漏:如果不及时释放无用对象的内存, 体系的可用内存会越来越少,最终可能导致 OutOfMemoryError; 避免内存溢出:GC 机制能够保证内存不会 由于长期积累未回收的对象而耗尽。
3.2 对象 何时候候可以被垃圾器回收? 怎样判断对象是否死亡?⭐️
难易程度:☆☆☆☆ 出现频率:☆☆☆☆
如果一个或多个对象没有任何的引用指向它了,那么这个对象现在就是垃圾,如果定位了垃圾,则有可能会被垃圾回收器回收。
如果要定位 何是垃圾,有两种方式来确定, 1. 引用计数法:通过计数引用的数量,当引用为 0 时回收。但不能处理循环引用 这种 技巧通过给每个对象维护一个引用计数器。当有一个新的引用指向该对象时,引用计数加 1;当引用离开时,计数减 1。 2. 可达性分析算法:通过检查对象是否从根对象可达,无法访问的对象可以回收。是 Java 使用的主要 技巧。
根对象是那些肯定不能当做垃圾回收的对象,就可以当做根对象 根对象包含:虚拟机栈中引用的对象;静态变量;活动线程的引用;JNI 引用的对象。
3.3 JVM 垃圾回收算法有哪些?⭐️⭐️⭐️⭐️⭐️
难易程度:☆☆☆ 出现频率:☆☆☆☆
Java 中的垃圾回收器采用不同的算法来回收不再使用的对象。
标记-清除算法:简单,效率高,但有内存碎片; 从根对象开始,遍历所有可以访问到的对象,并标记它们。 遍历所有对象,清除那些未被标记的对象,即不可达的对象。 标记-整理算法:解决了碎片 难题,但整理 经过较慢,效率低; 与标记-清除算法相同,标记所有可达的对象。 清除不可达对象后,将存活的对象移动到内存的一端,消除内存碎片。 算法:无碎片,内存整理高效,但内存利用率低; 将堆内存分为两部分,每次只使用其中一部分。当一部分用完时,垃圾回收器将存活的对象 到另一部分,并清空当前使用的部分。 分代收集算法
3.4 分代收集算法
分代收集算法-堆的区域划分
java8时,堆被分为了两份:新生代和老年代【1:2】,在java7时,还存在一个永久
对于新生代,内部又被分为了三个区域。 伊甸园 Eden 区,幸存者区 survivor (分成 from 和 to )【8:1:1】
分代收集算法- 职业机制
新创建的对象,都会先分配到eden区 当伊甸园内存不足,标记伊甸园与 from(现阶段没有)的存活对象 存活对象采用 算法 到 to 中, 完后,伊甸园和 from 内存都得到释放(即清空
经过一段 时刻后伊甸园的内存又出现不足,标记eden区域to区存活的对象,将存活的对象 到from区
又来了一批数据
当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升)
何故要用垃圾分代回收?
分代收集的主要目的是 进步垃圾回收效率,减少GC的停顿 时刻。其基本 想法是将堆内存分成多个区域(通常是年轻代、老年代和持久代),根据对象的存活 时刻来进行不同的回收策略。
年轻代(Young Generation):存放刚创建的对象。 由于大部分对象都是短命的,年轻代回收主要采用 算法,回收效率高,能够快速回收短 生活周期的对象。
老年代(Old Generation):存放长 时刻存活的对象。老年代对象较少变化,回收时使用标记-清除或标记-整理算法,回收速度相对较慢,但减少了频繁的垃圾回收。
持久代(Per nent Generation)(JDK 7及之前)/元空间(Metaspace)(JDK 8及之后):存放类信息等元数据。虽然它不涉及对象的存活与否,但也通过分代管理避免频繁回收。
分代收集的优势:
高效回收:通过将对象按存活 时刻分代,年轻代的回收可以频繁进行,而老年代则较少回收,这样可以显著 进步回收效率。
减少停顿 时刻:年轻代采用 算法,效率高且停顿 时刻短,减少了GC对应用程序性能的影响。
MinorGC、 Mixed GC 、 FullGC的区别是 何?
MinorGC(年轻代垃圾回收) 只回收年轻代,频繁且代价较低,停顿 时刻短。 对象会在年轻代中被回收,短命对象被清理,存活的对象晋升到老年代。 由于年轻代内存较小,回收的代价通常较低,停顿 时刻短,回收效率高。
Mixed GC(混合垃圾回收) 回收年轻代 + 部分老年代,G1 回收器特有,减少了老年代Full GC的频率,停顿 时刻比Minor GC长,但比Full GC短。 Mixed GC会回收年轻代的对象,同时也回收老年代的一部分。具体回收哪些老年代区域,由JVM根据内存压力和区域分布决定。 这种回收方式能减少老年代的Full GC触发次数。 Mixed GC比Minor GC涉及的内存范围大,因此会导致更长的停顿 时刻。相比Full GC,它的停顿 时刻仍然较短,但比Minor GC长。
FullGC(完全垃圾回收) 回收整个堆(年轻代、老年代、永久代/元空间),停顿 时刻最长(STW),对性能影响最大,应该尽量避免。 Full GC会检查整个堆内存,进行垃圾回收。它不仅回收年轻代,还回收老年代和永久代(或元空间)。这 一个全局性的回收 经过,因此会占用更多的 时刻,导致较长的停顿
STW(Stop-The-World):暂停所有应用程序线程,等待垃圾回收的完成
3.5 说下 JVM 有哪些垃圾回收器?⭐️⭐️⭐️⭐️
难易程度:☆☆☆☆ 出现频率:☆☆☆☆
在jvm中,实现了多种垃圾收集器,包括:
串行垃圾收集器
并行垃圾收集器
CMS(并发)垃圾收集器
G1垃圾收集器
串行垃圾收集器
Serial和Serial Old串行垃圾收集器,是指使用单线程进行垃圾回收,堆内存较小,适合个人电脑
Serial 影响于新生代,采用 算法
Serial Old 影响于老年代,采用标记-整理算法
垃圾回收时,只有一个线程在 职业, 并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成。
并行垃圾收集器
Parallel New和Parallel Old 一个并行垃圾回收器,JDK8默认使用此垃圾回收器
Parallel New 影响于新生代,采用 算法
Parallel Old 影响于老年代,采用标记-整理算法
垃圾回收时,多个线程在 职业, 并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成。
CMS(并发)垃圾收集器
CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器, 该回收器是针对老年代垃圾回收的,是一款以获取最短回收停顿 时刻为目标的收集器, 停顿 时刻短,用户体验就好。其最大特点是在进行垃圾回收时,应用仍然能正常运行。
3.6 详细聊一下G1垃圾回收器
难易程度:☆☆☆☆ 出现频率:☆☆☆☆
G1 在 JDK 1.7 时引入,在 JDK 9 时取代 CMS 成为默认的垃圾收集器。 G1 把 Java 堆划分为多个 大致相等的独立区域Region,每个区域都可以扮演新生代或老年代的角色。 同时,G1 还有一个专门为大对象设计的 Region,叫 Humongous 区。 这种区域化管理使得 G1 可以更灵活地进行垃圾收集,只回收部分区域而不是整个新生代或老年代。 G1 GC的设计目标是能够在大内存环境中提供高吞吐量,同时尽量减少垃圾回收的停顿 时刻。
如果并发失败(即回收速度赶不上创建新对象速度),会触发 Full GC。
大对象的判定 制度是,如果一个大对象超过了一个 Region 大致的 50%,比如每个 Region 是 2M,只要一个对象超过了 1M,就会被放入 Humongous 中。
G1 收集器的运行 经过大致可划分为这 几许步骤:
初始标记: 这一步会触发一次短暂停顿(Stop-The-World,STW),标记从 GC Roots 可以直接引用的对象,即标记所有直接可达的活跃对象。 并发标记,这一阶段与应用并发运行,标记堆中所有可达的对象。 这个阶段可能会持续较长 时刻,具体 时刻取决于堆的 大致和对象数量。 混合收集,在并发标记完成后,G1 会计算出哪些区域的回收 价格最高,即哪些区域包含最多垃圾。 接着,它优先回收这些区域,包括部分年轻代区域和老年代区域。 通过选择回收成本低而收益高的区域,G1 进步了回收效率,并尽量减少了停顿 时刻。 可预测的停顿,在垃圾回收期间,G1仍然需要进行停顿,但它提供了预测机制。用户可以通过JVM启动参数指定期望的最大停顿 时刻,G1会尽量在此 时刻内完成垃圾回收,以确保应用性能。
3.7 JDK中有几种引用类型?分别的特点是 何?⭐️
在Java中,引用类型有不同的级别,它们控制着对象的 生活周期以及垃圾回收的行为。强引用、软引用、弱引用和虚引用都是Java中引用对象的方式,它们的区别主要体现在垃圾回收器回收对象的时机和条件上。
强引用(Strong Reference)
最常见的引用类型,只要强引用存在,对象就不会被垃圾回收。 只有当引用被显式置为null或者没有任何引用指向该对象时,垃圾回收器才会回收它 Object obj = new Object(); // 强引用 obj = null; // 显式置为 null,对象才可能被 GC 若强引用对象过多,可能导致内存泄漏或 OOM。
软引用(Soft Reference)
软引用用于描述那些在内存充足时不应回收、但在内存不足时可以回收的对象。 需要配合SoftReference使用 SoftReference<Object> softRef = new SoftReference<>(new Object());
弱引用(Weak Reference)
弱引用比软引用更弱,每次垃圾回收时都会被回收,无论内存是否充足。 需要配合WeakReference使用 WeakReference<Object> weakRef = new WeakReference<>(new Object());
虚引用(Phantom Reference)
虚引用是最弱的引用类型,必须与引用队列(ReferenceQueue)配合使用。 虚引用的对象被回收时,会将虚引用加入引用队列,由Reference Handler线程调用虚引用相关 技巧来释放直接内存。 虚引用的主要 影响是用来监控对象被回收的 情形。
4. JVM 操作(调优)
4.1 JVM 调优的参数可以在 何处设置参数值?
难易程度:☆☆ 出现频率:☆☆☆
我们当时的项目是springboot项目,可以在项目启动的时候,java -jar中加入参数就行了
tomcat的设置vm参数
war包部署在tomcat中设置 修改TOMCAT_HOME/bin/catalina.sh文件 J A_OPTS=”-Xms512m -Xmx1024m”
linux下是.sh 小编觉得,windows是.bat 小编觉得
springboot项目jar文件启动
jar包部署在启动参数设置 通常在linux 体系下直接加参数启动springboot项目 java -Xms512m -Xmx1024m
nohup java -Xms512m -Xmx1024m -jar xxxx.jar –spring.profiles.active=prod & nohup : 用于在 体系后台不挂断地运行命令,退出终端不会影响程序的运行 参数 & :让命令在后台执行,终端退出后命令仍旧执行。
4.2 用的 JVM 调优的参数都有哪些?
难易程度:☆☆☆ 出现频率:☆☆☆☆
嗯,这些参数是比较多的。我记得当时我们设置过:…。具体的指令记不太清楚。
设置堆内存 大致 -Xms:初始堆 大致 -Xmx:最大堆 大致 设置年轻代中Eden区和两个Survivor区的 大致比例 -XX:NewSize=n:设置年轻代 大致 -XX:NewRatio=n:设置年轻代和年老代的比值。如:n 为 3 表示年轻代和年老代比值为 1:3,年轻代占总和的 1/4 -XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的比值。如 n=3 表示 Eden 占 3, Survivor 占 2,一个 Survivor 区占整个年轻代的 1/5 设置使用哪种垃圾回收器 -XX:+UseSerialGC:设置串行收集器 -XX:+UseParallelGC:设置并行收集器 -XX:+UseParalledlOldGC:设置并行老年代收集器 -XX:+UseConcMarkSweepGC:设置并发收集器 年轻代晋升老年代阈值 虚拟机栈的设置
4.3 说一下 JVM 调优的工具?
难易程度:☆☆☆☆ 出现频率:☆☆☆☆
嗯,我们一般都是使用jdk自带的一些工具,比如
命令工具
jps
输出JVM中运行的进程 情形信息。 jps
jstack
查看java进程内线程的堆栈信息。 jstack [option] <pid>
j p
用于生成堆转存快照
j p [options] pid 内存映像信息
j p -heap pid 显示Java堆的信息
j p -dump:for t=b,file=heap.hprof pid
for t=b表示以hprof二进制格式转储Java堆的内存
file=<filename>用于指定快照dump文件的文件名。
jhat
用于分析j p生成的堆转存快照(一般不推荐使用,而是使用Ecplise Memory Analyzer)
jstat
是JVM统计监测工具。可以用来显示垃圾回收信息、类加载信息、新生代统计信息等。
拓展资料垃圾回收统计:jstat -gcutil pid
可视化工具
jconsole
JDK 自带的监控工具, 用于对jvm的内存,线程,类 的监控 打开方式:java 安装目录 bin目录下 直接启动 jconsole.exe 就行
VisualVM:故障处理工具
能够监控线程,内存情况,查看 技巧的CPU 时刻和内存中的对 象,已被GC的对象,反向查看分配的堆栈 打开方式:java 安装目录 bin目录下 直接启动 jvisualvm.exe就行
4.4 java内存泄露的排查思路?
难易程度:☆☆☆☆ 出现频率:☆☆☆☆
内存泄漏通常是指堆内存,通常是指一些大对象不被回收的情况。
通过j p或设置jvm参数获取堆内存快照dump 通过工具,VisualVM去分析dump文件,VisualVM可以加载离线的dump文件 通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了 难题 找到对应的代码,通过阅读上下文的情况,进行修复即可
4.5 CPU飙高排查方案与思路?
难易程度:☆☆☆☆ 出现频率:☆☆☆☆
使用top命令查看占用cpu的情况 通过top命令查看后,可以查看是哪一个进程占用cpu较高,记录这个进程id 使用ps命令查看进程中的线程信息,看看 何者线程的cpu占用较高 使用jstack命令查看进程中哪些线程出现了 难题,最终定位 难题代码的行号