面试题 1 .简述Java堆的结构?什么是堆中的永久代(Perm Gen space)?
JVM整体结构及内存模型
试题回答参考思路:
1、堆结构
JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。
堆内存是由存活和死亡的对象组成的,存活的对象是应用可以访问的,不会被垃圾回收。
死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。
2、永久代(Perm Gen space)
永久代主要存在类定义,字节码,和常量等很少会变更的信息。并且永久代不会发生垃圾回收,如果永久代满了或者超过了临界值,会触发完全垃圾回收(Full Gc)
- 永久代中一般包含:
- 类的方法(字节码…)
- 类名(Sring对象)
- .class文件读到的常量信息
- class对象相关的对象列表和类型列表 (e.g., 方法对象的array)
- JVM创建的内部对象
- JIT编译器优化用的信息
而在java8中,已经移除了永久代,新加了一个叫做元数据区的native内存区。
3、元空间
元空间和永久代类似,都是对JVM中规范中方法的实现。
不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
因此,默认情况下,元空间的大小仅受本地内存的限制。
类的元数据放入native memory,字符串池和类的静态变量放入java堆中。这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。
4、采用元空间而不用永久代的原因
为了解决永久代的OOM问题,元数据和class对象存放在永久代中,容易出现性能问题和内存溢出。
类及方法的信息等比较难确定其大小,因此对于永久代大小指定比较困难,大小容易出现永久代溢出,太大容易导致老年代溢出(堆内存不变,此消彼长)。
永久代会为GC带来不必要的复杂度,并且回收效率偏低。
最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。 这是大佬写的, [7701页的BAT大佬写的刷题笔记,让我offer拿到手软]
面试题 2 . 如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
当对象的所有引用被置为null时,这个对象确实成为了垃圾收集(GC)的候选对象,但垃圾收集器并不会立即释放这个对象所占用的内存。
在Java和许多其他管理内存的编程语言中,垃圾收集的执行并不是立即发生的,它依赖于垃圾收集器的算法和策略以及当前内存使用情况。
垃圾收集器通常在以下几种情况下运行:
- 内存不足:当程序运行过程中可用内存不足时,垃圾收集器会被触发运行以回收无用的对象,从而释放内存。
- 系统空闲:在系统不那么忙碌时,垃圾收集器可能会运行,以优化内存的使用和系统的响应。
- 程序员请求:在某些语言中,程序员可以显式请求进行垃圾收集,虽然这不一定立即触发GC,但它可以增加GC运行的可能性。
因此,即使对象的引用被置为null,也不能保证垃圾收集器会立即运行来清理这些对象。实际上,具体的释放时间取决于垃圾收集器的实现细节和当前的内存状况。
面试题 3 . 描述行( serial )收集器和吞吐量( throughput )收集器的区别 ?
首先,行收集器,也就是Serial GC,它是一个单线程的垃圾收集器。如下图:
这意味着它在执行垃圾收集时,会暂停所有其他应用线程,也就是我们常说的“Stop-the-World”暂停。
行收集器因为其简单和单线程的特性,非常适合于单核处理器或内存资源相对较小的环境中。
它通常用在客户端应用或者较小的服务器上。
然后是吞吐量收集器,也称为Throughput GC或Parallel GC,它使用多个线程来并行地进行垃圾收集。
这种方式可以有效地提高垃圾收集的速度,尤其是在多核处理器的环境下。
吞吐量收集器的目标是最大化应用的运行时间比例,减少垃圾收集占用的时间,从而提高整体的应用吞吐量。
这使得它非常适合那些对吞吐量要求较高的后端服务和大数据处理系统。
总的来说,选择哪种收集器,主要取决于你的应用场景和具体需求。如果是在资源有限或单核CPU的环境下,行收集器可能是个更好的选择。而在多核心和需要高吞吐量的环境下,吞吐量收集器则更加合适。
面试题 4:在Java中对象什么时候可以被垃圾回收?
在Java中,一个对象是否可以被垃圾回收主要取决于是否还有活跃的引用指向这个对象。
具体来说,有几个条件可以使对象成为垃圾回收的候选者:
- 无引用指向:当一个对象不再有任何活跃的引用指向它时,它就可以被垃圾回收。这是最常见的情况,比如当对象的引用被显式设置为null,或者引用所在的作用域结束时。
- 循环引用:即使是两个或多个对象相互引用,但它们不再被其他活跃的引用或根对象引用时,这些对象也可以被视为垃圾并回收。Java的垃圾收集器可以识别这种情况。
- 从可达性分析:Java使用可达性分析(Reachability Analysis)来确定对象是否存活。在这种分析中,一系列称为“根”(roots)的对象被用作起点,这通常包括活跃线程的栈帧中的本地变量、静态字段和特定的方法区域。从这些根出发,GC将标记所有可以访问到的对象。未被标记的对象,即那些从根节点出发无法到达的对象,被认为是可以回收的。
- 弱引用和软引用:Java还提供了弱引用(WeakReference)和软引用(SoftReference)等机制,通过这些引用类型引用的对象可以在JVM需要内存时被垃圾回收器回收。弱引用的对象在垃圾收集时总是会被回收,而软引用的对象则在内存不足时会被回收。
因此,总结来说,一个Java对象在没有任何有效的引用指向它,或者只通过弱引用或软引用被访问时,就可以被视为垃圾并可能在下一次垃圾收集时被清理掉。这个过程依赖于垃圾收集器的具体实现和触发条件。
最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。 这是大佬写的, [7701页的BAT大佬写的刷题笔记,让我offer拿到手软]
面试题5:JVM的永久代中会发生垃圾回收么?
是的,在JVM的早期版本中,永久代是确实存在的,并且确实会发生垃圾回收。
永久代主要用来存储JVM加载的类信息、常量以及方法数据。尽管永久代中的对象通常有较长的生命周期,但它仍然可能涉及垃圾回收,特别是在类卸载的时候或者清理常量池中不再使用的数据时。
这种垃圾回收通常发生在进行全局垃圾收集(Full GC)的时候。
不过,需要注意的是,类的卸载和常量池的清理并不是特别频繁,因为它们的生命周期通常跟应用程序的生命周期一样长。
另外,从Java 8开始,Oracle的JVM不再使用永久代的概念了,转而使用元空间(Metaspace)来替代。
元空间不再使用JVM堆的一部分,而是直接使用本地内存。
在元空间中,仍然会发生垃圾回收,主要是为了清理不再使用的类元数据。
这种变化主要是为了提高性能以及更灵活地管理内存。
所以,如果我们谈论的是Java 8及以后的版本,那么应该关注元空间的垃圾回收,而不是永久代。
面试题 6 . 阐述什么是分布式垃圾回收( DGC )?它是如何工作的?
DGC叫做分布式垃圾回收。RMI使用DGC来做自动垃圾回收。
因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。
分布式垃圾回收的工作原理基本上是通过跟踪网络中对象的引用来实现的。主要有以下几个步骤:
- 引用跟踪:DGC系统会监控和记录每个对象在不同节点间的引用情况。每当一个节点对远程对象建立或解除引用时,这种改变会被记录下来。
- 引用计数:DGC常用的一种方法是引用计数。每个对象会有一个与之相关的计数器,记录着对该对象的引用次数。当某个节点创建对另一个节点上对象的引用时,该对象的引用计数增加;当引用被释放时,引用计数减少。
- 回收判定:当对象的引用计数归零时,意味着没有任何活跃的引用指向这个对象,此时该对象可以被回收。
- 资源回收:一旦确定对象可以被回收,相关的垃圾回收操作会被触发,释放占用的资源。
- 周期性检查:由于引用计数可能存在循环引用的问题,DGC系统通常会周期性地执行全局垃圾回收算法来检查和回收那些因为循环引用而未被清理的对象。
分布式垃圾回收面临的主要挑战是确保效率和准确性,特别是在大规模分布式系统中。延迟、网络分区和节点故障都可能影响DGC的准确性和性能。因此,实现一个高效且可靠的DGC机制是一个具有挑战性的任务,通常需要在系统设计阶段就进行细致的规划和测试。
面试题 7 . 请描述什么是Java虚拟机?
1:Java虚拟机(JVM)一种用于计算机设备的规范,可用不同的方式(软件或硬件)加以实现。编译虚拟机的指令集与编译微处理器的指令集非常类似。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。
Java虚拟机(JVM)是可运行Java代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行。
2:Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现。Java虚拟机有自己想象中的硬件,如处理器、堆栈、寄存器等,还具有相应的指令系统。
3:Java虚拟机规范定义了一个抽象的——而非实际的——机器或处理器。这个规范描述了一个指令集,一组寄存器,一个堆栈,一个“垃圾堆”,和一个方法区。一旦一个Java虚拟机在给定的平台上运行,任何Java程序(编译之后的程序,称作字节码)都能在这个平台上运行。Java虚拟机(JVM)可以以一次一条指令的方式来解释字节码(把它映射到实际的处理器指令),或者字节码也可以由实际处理器中称作just-in-time的编译器进行进一步的编译。
面试题 8 . 解释静态变量在什么时候加载?编译期还是运行期?
静态变量是类级别的变量,在Java中,它们随类一起加载。关于静态变量何时加载的问题,我们可以这样解释:
静态变量在运行期被加载和初始化。具体来说,它们是在类被Java虚拟机加载到内存中后、类被第一次引用时初始化的。这个过程是在运行时发生的,而非编译期。
完整的Java对象创建过程如下:
让我详细解释一下这个过程:
- 类加载:首先,当Java程序运行时,Java虚拟机(JVM)通过类加载器加载包含静态变量的类。这一步涉及到查找字节码并从类的全路径名生成一个Class对象。
- 链接:加载后,类进入链接阶段,这个阶段验证类中的字节码,为静态变量分配内存,并且可能将变量设置为默认值,如null、0或false。
- 初始化:在链接阶段之后,类的初始化阶段开始。在这个阶段,静态变量按照它们在代码中出现的顺序显式初始化,如果它们有初始化表达式的话。此外,如果有静态初始化块(static block),它也会在这个时候执行。
这个初始化过程确保在任何静态方法或静态变量被访问之前,所有的静态变量已经被设置为指定的初始值。因此,虽然类的结构和静态变量的声明在编译期被确定并存储在类的字节码中,实际上它们的加载和初始化则发生在运行期。这种机制确保了Java类和对象的初始化顺序得到正确的管理和执行。
面试题 9 . JVM自身会维护缓存吗?
在Java虚拟机(JVM)中,确实有几种机制可以被视为内部的“缓存”功能,这些功能主要用于提高程序执行的效率和性能。
以下是几个例子:
- 方法区(或元空间):虽然方法区本身不是传统意义上的缓存,但它存储了类的结构信息,包括运行时常量池、字段和方法数据,以及方法的代码。这可以看作是一种缓存机制,因为它避免了每次执行时重新加载和解析类文件。
- 字符串池(String Intern Pool):JVM为了优化字符串存储和重用,维护了一个特殊的字符串池。当使用字面量创建字符串时,JVM首先检查池中是否已存在相同的字符串。如果存在,就返回现有的字符串引用,这样可以节省内存并提高性能。
- 即时编译器(JIT)的代码缓存:JIT编译器在运行时将热点代码(即频繁执行的代码)编译成本地机器代码,以提高程序的执行速度。这些编译后的代码被存储在一个特定的缓存区域中,从而在后续的执行中可以直接使用,避免了重复的解释执行开销。
- 堆中的缓存结构:某些应用可能会在JVM堆内显式创建缓存(如应用级的数据缓存),用于存储经常访问的数据对象。虽然这不是JVM自身维护的缓存,但是它是在JVM的管理下进行的。
- Biased Locking:这是JVM用于优化对象锁的一种机制。通过偏向锁,JVM可以减少无竞争访问同步块时的锁开销,通过在对象头中缓存线程ID来实现。
- 类加载器缓存:类加载器在加载类时,会缓存类信息,这样相同的类在下次加载时可以直接使用已缓存的数据,加快了类加载的速度。
面试题 10 . 请详细列举 JVM有哪些参数,分别如何设置与调优 ?
对于8G内存,我们一般是分配4G内存给JVM,正常的JVM参数配置如下:
-Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8
调优的话要具体场景具体分析,可以通过监控确认FullGC频率和次数,通过dump文件分析大对象等手段。
可以从代码层面优化或者是调整年轻代、老年代、幸存区的内存大小,来达到调优的目的
最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。 这是大佬写的, [7701页的BAT大佬写的刷题笔记,让我offer拿到手软]
面试题 11 .简述Java体系中的堆和栈的概念和运行原理 ?
在 Java 中,堆和栈分别表示两种不同的内存分配方式,各自用于存储不同类型的数据和具有不同的管理机制。
栈
- 概念:栈(Stack)是线程的运行内存,按照先进后出的原则存储局部变量、方法参数和调用信息。每个线程都有自己独立的栈内存,栈中的每一帧代表一次方法调用。
- 运行原理:
- 方法调用时,栈中创建新的帧(stack frame)来保存局部变量、操作数栈和其他信息。
- 方法执行完毕后,该方法对应的栈帧被弹出并释放内存。
- 栈空间通常较小,主要用于存储基本数据类型、对象引用和局部变量。
- 存取速度快,仅次于寄存器
- 栈数据可以共享
- 栈的特性
- 栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3;
int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着 处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。
这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a 值的改变不会影响到b的值。
要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利 于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
- 栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
堆
- 概念:堆(Heap)是 JVM 中用于存放对象和数组的共享内存区域,由所有线程共享。它的大小一般较大,用于动态分配内存。
- 运行原理:
- 对象在堆中动态分配,由垃圾收集器负责管理其生命周期。
- JVM 的垃圾回收机制定期扫描堆,释放无用对象的内存。
- 根据不同的垃圾回收算法,堆可以划分为不同区域,如新生代、老年代和永久代(或元空间)。
- 存取速度慢(由于需要在运行时动态分配内存)
总结:
- 栈用于方法调用过程中对局部变量和方法信息的临时存储,效率高但容量有限。
- 堆用于存放动态分配的对象和数组,并由垃圾回收器管理内存释放。
面试题 12 .64 位 JVM 中,int 的长度是多数?
在 64 位 JVM 中,int 数据类型的长度依然是 32 位(4 字节)。这在 Java 语言规范中有明确规定,不受运行环境的 32 位或 64 位体系结构的影响。
Java 基础数据类型的长度是固定的,独立于硬件架构:
- byte: 8 位(1 字节)
- short: 16 位(2 字节)
- int: 32 位(4 字节)
- long: 64 位(8 字节)
- char: 16 位(2 字节)
- float: 32 位(4 字节)
- double: 64 位(8 字节)
因此,在 64 位 JVM 中,int 仍然保持 32 位(4 字节)。
面试题 13 .Serial 与 Parallel GC之间的不同之处?
在 Java 虚拟机 (JVM) 中,Serial 垃圾收集器和 Parallel 垃圾收集器是两种不同的垃圾收集策略,它们的主要区别在于并发性和目标使用场景。
Serial 垃圾收集器
新生代采用复制算法,老年代采用标记-整理算法
- 工作方式:
- 使用单线程的方式进行垃圾收集。
- 在执行垃圾收集时会暂停所有应用线程(“Stop-the-World”)。
- 对于新生代,采用标记-复制(mark-copy)算法;对于老年代,采用标记-整理(mark-compact)算法。
- 优点:
- 实现简单且容易调试。
- 对于单核/双核机器、低内存应用等小型应用的性能较好。
- 缺点:
- 因为是单线程,所以垃圾收集期间应用会完全暂停,对于多核/高并发的应用效率较低。
Parallel 垃圾收集器
新生代采用复制算法,老年代采用标记-整理算法
- 工作方式:
- 使用多线程并行执行垃圾收集工作。
- 新生代和老年代都能并行处理,使用多线程标记、复制和整理对象。
- 也需要在执行垃圾收集时暂停应用线程(“Stop-the-World”)。
- 优点:
- 在多核机器上可以充分利用多核能力,加快垃圾收集速度。
- 可通过设置线程数进行调优。
- 缺点:
- 并行算法增加了一定的复杂性。
- 在应用负载较低或是内核较少的情况下,性能提升不明显。
总结
- 并发:Serial GC 适用于单线程执行垃圾收集,而 Parallel GC 能够利用多线程并行执行。
- 适用场景:Serial GC 适合内存小、核心数少的应用;Parallel GC 更适合多核、高内存的应用场景。
面试题 14 .简述 JVM 选项 -XX:+UseCompressedOops 有什么作用?
JVM 选项 -XX:+UseCompressedOops 主要用于 64 位 JVM 上的堆内存优化,简称为“压缩 OOP(普通对象指针)”
背景
- 在 64 位 JVM 中,指针的长度为 64 位(8 字节),这导致对象引用的大小也为 8 字节,比 32 位 JVM 中的 4 字节增加了一倍。
- 对象指针的增加会使得堆内存中的对象大小增加,导致对内存的使用效率降低。
64为虚拟机对象头如下:
压缩 OOP
- -XX:+UseCompressedOops 选项会启用压缩的普通对象指针(OOPs),将对象引用压缩为 32 位。
- 通过在堆内存地址中添加偏移量和对齐方式,实现了 32 位的对象引用可以映射到更大的地址空间,从而在不影响指针计算性能的情况下压缩指针大小。
作用
- 内存占用减少:由于每个对象引用占用的内存减少了一半,启用压缩 OOP 后,堆内存利用率明显提高。
- 性能提升:内存占用的降低可使更多对象保持在缓存中,从而降低内存访问延迟,提高程序运行效率。
适用场景
- 一般适用于大多数 64 位 JVM 上的应用程序,特别是当应用程序需要分配大量对象并且堆大小相对较大时。
面试题 15 .怎样通过 Java 程序来判断 JVM 是 32 位 还是 64 位?
在 Java 中,可以通过检查系统属性 os.arch 来确定 JVM 是 32 位还是 64 位。该属性表示操作系统的体系结构。一般来说,如果属性值包含“64”,则表示是 64 位 JVM,否则是 32 位。
例如:
public class JVMArchitecture {
public static void main(String[] args) {
String architecture = System.getProperty("os.arch");
System.out.println("JVM Architecture: " + architecture);
if (architecture.contains("64")) {
System.out.println("This is a 64-bit JVM.");
} else {
System.out.println("This is a 32-bit JVM.");
}
}
}
说明:
- System.getProperty(“os.arch”) 获取当前操作系统架构的系统属性。
- 判断返回值中是否包含“64”来区分 32 位或 64 位。
- 注意该属性值还可能与操作系统的体系结构有关,在虚拟机明确为 64 位的情况下,仍可能返回“32”或其他值。
面试题 16 .32 位 JVM 和 64 位 JVM 的最大堆内存分别是多少?
在 Java 中,JVM 最大堆内存的限制取决于虚拟机架构(32 位或 64 位)、操作系统和硬件资源
一般情况下,32 位和 64 位 JVM 的最大堆内存如下:
32 位 JVM
- 由于 32 位地址空间的限制,理论上最大堆内存为 4GB。
- 实际上,由于系统库、栈和其他内存占用,32 位 JVM 通常只能使用 1.5GB 到 3GB 左右的堆内存,具体取决于操作系统和 JVM 的实现。
64 位 JVM
- 64 位架构的地址空间理论上可支持数百 TB 的内存。
- 实际最大堆大小主要由物理内存和操作系统的限制决定。
- 通常来说,64 位 JVM 最大堆内存可以设置到数十 GB 或更高。
总结
- 32 位 JVM 由于地址空间的限制,最大堆内存较小,适用于内存需求较低的应用。
- 64 位 JVM 允许更大的堆内存,适合内存需求较大的应用。
最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。 这是大佬写的, [7701页的BAT大佬写的刷题笔记,让我offer拿到手软]
面试题 17 .解释 Java 堆空间及 GC?
Java 堆空间(Heap)是 JVM 的一部分内存区域,用于存储由程序动态分配的所有对象。
它在 JVM 启动时创建,所有线程共享。垃圾收集器(GC)会在堆空间中自动管理内存,定期清理不再使用的对象。
Java 堆空间
- 新生代(Young Generation):
- 新生代存储新创建的对象。
- 通常细分为三个区域:伊甸园区(Eden Space)和两个幸存者区(Survivor Space)。
- 对象在伊甸园区创建,并在达到某些条件后移动到幸存者区。
- 老年代(Old Generation):
- 老年代存储生命周期较长的对象。
- 新生代经过多次垃圾收集后幸存下来的对象会被晋升到老年代。
- 永久代/元空间(Permanent Generation / Metaspace):
- 在早期版本的 Java 中,永久代存储类元数据。
- 从 Java 8 开始,永久代被移除并替换为元空间,存储在 JVM 本地内存中而不是堆中。
Java 垃圾收集(GC)
垃圾收集器是负责自动管理堆空间的内存分配和回收的组件。它的目标是移除不再被使用的对象,释放内存以供其他对象使用。常见的垃圾收集算法和收集器包括:
- 标记-清除算法:
- 首先标记不再使用的对象,然后清除它们。
- 可能导致堆空间碎片化。
- 标记-整理算法:
- 在标记阶段之后,将存活的对象整理到堆的一端,再清除剩余的无用空间。
- 标记-复制算法:
- 将新生代分为两个区域,存活的对象从一个区域复制到另一个,原始区域被清空
- 垃圾收集器:
- Serial GC:单线程,适用于较小的应用。
- Parallel GC:多线程,适用于多核机器。
- CMS(Concurrent Mark-Sweep)GC:低停顿时间,适合交互式应用。
- G1(Garbage-First)GC:适用于大型堆和高性能应用,替代 CMS。
- **ZGC:**JDK11引入的一款新的垃圾收集器,旨在提供较低的停顿时间,同时尽可能提升垃圾收集效率
面试题 18 .解释能保证 GC 执行吗?
在 Java 中,不能绝对保证垃圾收集(GC)在特定时间执行。
垃圾收集的行为由 JVM 控制,并根据具体的内存需求和配置参数自动决定何时触发。
原因和解释:
- 自动管理:
- Java 使用自动内存管理系统,开发者不需要手动释放内存。
- 垃圾收集器在 JVM 认为合适的时机才会启动。通常,当堆内存不足或达到某个阈值时,GC 就会执行。
- 多种垃圾收集器:
- 不同的垃圾收集器有不同的策略和算法,会影响垃圾收集的时机和频率。例如,Serial GC、Parallel GC、CMS、G1 和 ZGC 等都在其特定情况下会以不同方式工作。
- System.gc() 提示:
- System.gc() 或 Runtime.getRuntime().gc() 提供一种“建议性”调用,提示 JVM 执行 GC。
- 但是这只是建议,JVM 有权忽略它,具体是否执行取决于垃圾收集器的策略和当前内存状态。
虽然我们可以通过配置或提示来增加垃圾收集的机会,但无法严格控制它在特定时间执行。
JVM 会根据应用程序的运行状况和当前的内存使用情况来自动决定最佳的垃圾收集时间。
面试题 19 .怎么获取 Java 程序使用的内存?堆使用的百分比?
在 Java 中,可以使用 Runtime 类或 Java Management Extensions (JMX) 来获取 Java 程序使用的内存和堆内存使用的百分比。
通过 Runtime 类
Runtime 类提供了获取 JVM 内存使用信息的方法
例如:
public class MemoryUsageExample {
public static void main(String[] args) {
// 获取当前 JVM 的运行时实例
Runtime runtime = Runtime.getRuntime();
// 获取最大可用内存
long maxMemory = runtime.maxMemory();
// 获取当前已分配的总内存
long totalMemory = runtime.totalMemory();
// 获取当前空闲内存
long freeMemory = runtime.freeMemory();
// 计算已使用的内存
long usedMemory = totalMemory - freeMemory;
// 打印信息
System.out.printf("最大堆内存: %.2f MB%n", maxMemory / (1024.0 * 1024));
System.out.printf("已分配的总堆内存: %.2f MB%n", totalMemory / (1024.0 * 1024));
System.out.printf("已使用的堆内存: %.2f MB%n", usedMemory / (1024.0 * 1024));
System.out.printf("堆内存使用百分比: %.2f%%%n", (usedMemory * 100.0) / maxMemory);
}
}
通过 JMX
Java Management Extensions (JMX) 提供了更高级的内存管理信息。可以使用 MemoryMXBean 获取详细的堆和非堆内存使用情况:
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
public class JMXMemoryUsageExample {
public static void main(String[] args) {
// 获取 JVM 的内存管理 MXBean
MemoryMXBean memoryMXBean =