下面分别介绍了新生代和老年代的不同收集器及其相关子类型,并附有示例代码和说明,感兴趣的朋友可以参考一下。
在 Java 虚拟机(JVM)的世界里,内存模型是其核心架构之一,它决定了 Java 程序如何存储和管理数据,深刻影响着程序的性能和稳定性。了解 JVM 内存模型,对于优化 Java 应用、排查内存相关问题至关重要。
类加载器子系统在 JVM 中扮演着数据 “搬运工” 的角色,负责将字节码文件加载到 JVM 中,并进行一系列处理,确保其能被 JVM 正确执行。
public class StaticInit {
static {
System.out.println("Static block is executed");
}
static int num = 10;
}
当StaticInit类初始化时,静态代码块先执行,然后num被赋值为 10。
双亲委派机制是类加载器的核心机制,它的工作流程就像一个严谨的 “任务分配链”。
常见的类加载器有以下几种:
双亲委派机制保障了 Java 核心类库的安全性和一致性。例如,java.lang.Object类在任何应用中都由启动类加载器加载,避免了不同类加载器加载出不同版本的Object类而引发混乱。
双亲委派机制的实现主要依赖于ClassLoader类中的loadClass方法。下面是简化后的源代码示例:
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 先检查该类是否已被加载
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent!= null) {
// 父类加载器不为空,委托父类加载
c = parent.loadClass(name, false);
} else {
// 父类加载器为空,说明到了启动类加载器,尝试由其加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类加载器加载失败,子类加载器自己尝试加载
}
if (c == null) {
long t1 = System.nanoTime();
// 子类加载器自己加载类
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
从代码可知,loadClass方法首先检查类是否已加载,若未加载,则按双亲委派规则,先委托父类加载器加载。若父类加载器加载失败(抛出ClassNotFoundException异常),子类加载器才调用自己的findClass方法尝试加载。
本地方法库是 JVM 中存放用 C、C++ 等语言编写的本地方法的代码库。当 Java 程序调用本地方法时,JVM 通过本地接口库找到对应的实现。例如,System.currentTimeMillis()方法获取当前时间,实际是调用了本地 C 或 C++ 代码,因为底层操作系统提供了更高效的时间获取机制,通过本地方法可直接利用这些底层功能。
本地接口库是 Java 与本地方法库之间的桥梁,提供了 Java 代码调用本地方法,以及本地方法访问 Java 对象和数据的机制。JNI(Java Native Interface)是最常用的本地接口,通过它,Java 代码能调用 C、C++ 编写的函数,还能在 Java 和本地代码间传递基本类型、对象等数据。比如,在 Java 程序中调用 C++ 编写的图像处理库,就可通过 JNI 实现交互。
执行引擎是 JVM 的 “动力核心”,负责执行字节码指令。
即时编译器(JIT,Just - In - Time Compiler)是执行引擎的重要部分,它在运行时将字节码编译成机器码,提升程序执行效率。JIT 编译器主要有两种类型:
垃圾收集是执行引擎的另一重要功能,负责回收不再使用的内存空间。JVM 中有多种垃圾收集算法:
运行时数据区是 JVM 运行时使用的内存区域,包含以下几个部分:
本地方法栈与虚拟机栈类似,不过它是为执行本地方法服务的。主要用于存储本地方法的局部变量表、操作数栈、动态连接、方法出口等信息。当 Java 程序调用本地方法时,JVM 会在本地方法栈中为该方法创建一个栈帧,存储方法执行过程中的各种数据。比如调用 C++ 编写的本地方法时,JVM 会在本地方法栈为其分配栈帧,保存参数、局部变量等信息。
程序计数器是一块较小的线程私有内存空间。每个线程都有自己的程序计数器,它记录当前线程执行的字节码指令地址。如果线程执行的是 Java 方法,计数器记录虚拟机字节码指令地址;若执行的是本地方法,计数器值为空(Undefined)。例如,线程执行循环语句时,程序计数器不断更新,指向循环体中当前要执行的字节码指令,确保线程按顺序正确执行代码。
虚拟机栈也是线程私有的,描述 Java 方法执行的内存模型。每个方法执行时都会创建一个栈帧,栈帧包含以下部分:
在 JDK 1.8 之前,方法区用于存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,是各个线程共享的内存区域。JDK 1.8 之后,方法区的实现发生变化,将永久代替换为元空间(Metaspace)。元空间不在 JVM 的堆内存中,而是使用本地内存(Native Memory)。这样做主要是为了解决永久代容易出现的内存溢出问题,因为元空间大小只受限于本地内存大小,不像永久代受限于 JVM 的堆内存大小。比如在使用大量动态生成类的应用场景中,如 Spring 框架的动态代理机制,若使用永久代,很容易因不断生成新类导致永久代内存溢出,而元空间则可避免这种情况。
堆是 JVM 中最大的内存区域,被所有线程共享,主要用于存储对象实例和数组。堆又可细分为老年代和新生代。
通过调整-Xms(初始堆大小)和-Xmx(最大堆大小)参数,可根据应用程序实际需求合理分配堆内存。若初始堆大小设置过小,可能导致频繁垃圾回收,影响性能;若最大堆大小设置过大,会浪费内存资源,且垃圾回收时间更长。例如,对于内存需求大的服务器端应用,可适当增大-Xmx的值,如-Xmx2g,表示最大堆大小为 2GB。
不同的垃圾收集器适用于不同应用场景:
使用 JConsole、VisualVM 等工具,可实时监控 JVM 内存使用情况,包括堆内存、方法区等区域的使用情况,以及垃圾回收的频率和时间等。通过分析这些数据,能发现内存泄漏、频繁垃圾回收等问题,并针对性地优化。例如,通过 VisualVM 的可视化界面,能清晰看到堆内存的增长趋势、垃圾回收的次数和耗时等信息,帮助找出性能瓶颈。
垃圾收集器的核心工作是识别出内存中不再被使用的对象(即垃圾对象),并回收它们所占用的内存空间。为了实现这一目标,垃圾收集器通常采用两种主要的算法思想:引用计数法和可达性分析算法。
新生代垃圾收集器:
// 假设这是一个简单的对象类
class SimpleObject {
// 一些属性和方法省略
}
public class SerialCollectorExample {
public static void main(String[] args) {
// 模拟创建一些对象
SimpleObject obj1 = new SimpleObject();
SimpleObject obj2 = new SimpleObject();
// 假设这里有一个方法来模拟垃圾回收
serialCollect();
}
private static void serialCollect() {
// 这里简单模拟标记哪些对象是垃圾(实际更复杂)
boolean isObj1Garbage = true;
boolean isObj2Garbage = false;
if (isObj1Garbage) {
// 回收obj1占用的内存(实际JVM中是通过特定机制)
obj1 = null;
}
if (isObj2Garbage) {
obj2 = null;
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 假设这是一个简单的对象类
class SimpleObject {
// 一些属性和方法省略
}
public class ParNewCollectorExample {
public static void main(String[] args) {
// 模拟创建一些对象
SimpleObject[] objects = new SimpleObject[100];
for (int i = 0; i < objects.length; i++) {
objects[i] = new SimpleObject();
}
// 使用线程池模拟多线程垃圾回收
ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 0; i < objects.length; i++) {
int finalI = i;
executorService.submit(() -> {
// 这里简单模拟判断对象是否为垃圾(实际更复杂)
boolean isGarbage = Math.random() > 0.5;
if (isGarbage) {
objects[finalI] = null;
}
});
}
executorService.shutdown();
}
}
// 假设这是一个简单的对象类
class SimpleObject {
// 一些属性和方法省略
}
public class ParallelScavengeCollectorExample {
public static void main(String[] args) {
// 模拟创建大量对象
for (int i = 0; i < 1000000; i++) {
SimpleObject obj = new SimpleObject();
// 这里省略对象的使用和可能变为垃圾的过程
}
// 这里假设通过调整参数(实际在JVM启动时设置)
// 如 -XX:MaxGCPauseMillis=100 -XX:GCTimeRatio=99
// 来影响垃圾回收策略,以达到高吞吐量
}
}
老年代垃圾收集器:
// 假设这是一个简单的对象类
class SimpleObject {
// 一些属性和方法省略
}
public class SerialOldCollectorExample {
public static void main(String[] args) {
// 模拟创建一些对象
SimpleObject[] objects = new SimpleObject[10];
for (int i = 0; i < objects.length; i++) {
objects[i] = new SimpleObject();
}
// 模拟标记 - 整理过程
markAndSweep(objects);
}
private static void markAndSweep(SimpleObject[] objects) {
// 简单模拟标记哪些对象是垃圾(实际更复杂)
boolean[] isGarbage = new boolean[objects.length];
for (int i = 0; i < objects.length; i++) {
isGarbage[i] = Math.random() > 0.5;
}
// 模拟整理过程,将存活对象向一端移动
int lastNonGarbageIndex = 0;
for (int i = 0; i < objects.length; i++) {
if (!isGarbage[i]) {
objects[lastNonGarbageIndex++] = objects[i];
}
}
// 清理端边界以外的内存(这里简单设置为null)
for (int i = lastNonGarbageIndex; i < objects.length; i++) {
objects[i] = null;
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 假设这是一个简单的对象类
class SimpleObject {
// 一些属性和方法省略
}
public class ParallelOldCollectorExample {
public static void main(String[] args) {
// 模拟创建大量对象
SimpleObject[] objects = new SimpleObject[1000];
for (int i = 0; i < objects.length; i++) {
objects[i] = new SimpleObject();
}
// 使用线程池模拟多线程标记 - 整理
ExecutorService executorService = Executors.newFixedThreadPool(4);
boolean[] isGarbage = new boolean[objects.length];
for (int i = 0; i < objects.length; i++) {
int finalI = i;
executorService.submit(() -> {
// 简单模拟判断对象是否为垃圾(实际更复杂)
isGarbage[finalI] = Math.random() > 0.5;
});
}
executorService.shutdown();
// 模拟整理过程,将存活对象向一端移动
int lastNonGarbageIndex = 0;
for (int i = 0; i < objects.length; i++) {
if (!isGarbage[i]) {
objects[lastNonGarbageIndex++] = objects[i];
}
}
// 清理端边界以外的内存(这里简单设置为null)
for (int i = lastNonGarbageIndex; i < objects.length; i++) {
objects[i] = null;
}
}
}
// 假设这是一个简单的对象类
class SimpleObject {
// 一些属性和方法省略
}
public class CMSCollectorExample {
public static void main(String[] args) {
// 模拟创建一些对象
SimpleObject[] objects = new SimpleObject[100];
for (int i = 0; i < objects.length; i++) {
objects[i] = new SimpleObject();
}
// 模拟CMS收集器的工作阶段
cmsCollect(objects);
}
private static void cmsCollect(SimpleObject[] objects) {
// 初始标记(简单模拟)
boolean[] isGarbage = new boolean[objects.length];
for (int i = 0; i < 10; i++) {
isGarbage[i] = true;
}
// 并发标记(这里简单模拟并发,实际是多线程操作)
for (int i = 10; i < objects.length; i++) {
isGarbage[i] = Math.random() > 0.5;
}
// 重新标记(简单模拟)
for (int i = 0; i < objects.length; i++) {
if (Math.random() > 0.9) {
isGarbage[i] = true;
}
}
// 并发清除(简单模拟)
for (int i = 0; i < objects.length; i++) {
if (isGarbage[i]) {
objects[i] = null;
}
}
}
}
// 假设这是一个简单的对象类
class SimpleObject {
// 一些属性和方法省略
}
public class G1CollectorExample {
public static void main(String[] args) {
// 模拟堆内存划分为多个Region
SimpleObject[][][] regions = new SimpleObject[10][10][10];
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
for (int k = 0; k < 10; k++) {
regions[i][j][k] = new SimpleObject();
}
}
}
// 模拟G1收集器优先回收垃圾最多的Region
int maxGarbageRegionIndex = 0;
int maxGarbageCount = 0;
for (int i = 0; i < 10; i++) {
int garbageCount = 0;
for (int j = 0; j < 10; j++) {
for (int k = 0; k < 10; k++) {
if (Math.random() > 0.5) {
garbageCount++;
}
}
}
if (garbageCount > maxGarbageCount) {
maxGarbageCount = garbageCount;
maxGarbageRegionIndex = i;
}
}
// 模拟回收垃圾最多的Region
for (int j = 0; j < 10; j++) {
for (int k = 0; k < 10; k++) {
regions[maxGarbageRegionIndex][j][k] = null;
}
}
}
}
通过深入了解垃圾收集器的工作原理、分类特点以及选择调优策略,我们可以更好地优化 JVM 的内存管理,提高 Java 应用程序的性能和稳定性。