1. 概述

本文将介绍如何通过 Maven 运行 Java 代码时传递 JVM 参数。我们先探讨如何全局应用这些参数,然后重点说明如何为特定 Maven 插件配置参数。

2. 设置全局 JVM 参数

首先介绍两种为 Maven 主进程设置 JVM 参数的技巧。

2.1. 使用命令行

要通过 JVM 参数运行 Java Maven 项目,需设置 MAVEN_OPTS 环境变量。 该变量包含 JVM 启动时使用的参数,允许我们传递额外选项:

$ export MAVEN_OPTS="-Xms256m -Xmx512m"

本例中,我们通过 MAVEN_OPTS 定义了最小和最大堆内存大小。随后可通过以下命令执行构建:

$ mvn clean install

这些参数将应用于构建的主进程。

2.2. 使用 jvm.config 文件

自动设置全局 JVM 参数的替代方案是定义 jvm.config 文件。该文件必须位于项目根目录的 .mvn 文件夹中。文件内容即为要应用的 JVM 参数。例如,要模拟之前使用的命令行配置,配置文件内容应为:

-Xms256m -Xmx512m

3. 为特定插件设置 JVM 参数

Maven 插件可能 fork 新的 JVM 进程来执行任务,因此全局参数设置对它们无效。每个插件都有自己设置参数的方式,但大多数配置类似。我们将展示三个常用插件的示例:spring-boot Maven 插件、surefire 插件和 failsafe 插件。

3.1. 示例搭建

我们将搭建一个基础 Spring 项目,但要求代码仅在设置特定 JVM 参数时才能运行。

首先编写服务类

@Service
class MyService {
    int getLength() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        ArrayList<String> arr = new ArrayList<>();
        Field sizeField = ArrayList.class.getDeclaredField("size");
        sizeField.setAccessible(true);
        return (int) sizeField.get(arr);
    }
}

Java 9 引入的模块系统对反射访问增加了更多限制。这段代码在 Java 8 中可编译运行,但在 Java 9 及更高版本中需要添加 --add-open JVM 选项来开放 java-base 模块的 java-util 包的反射权限。

现在添加一个简单的控制器类

@RestController
class MyController {
    private final MyService myService;

    public MyController(MyService myService) {
        this.myService = myService;
    }

    @GetMapping("/length")
    Integer getLength() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        return myService.getLength();
    }
}

最后添加主应用类:

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

3.2. Spring-Boot Maven 插件

添加 Maven spring-boot 插件的最新版本基础配置:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>3.3.0</version>
</plugin>

通过以下命令运行项目:

$ mvn spring-boot:run

应用启动成功,但当尝试访问 http://localhost:8080/length 时,会报错并出现以下日志:

java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.util.ArrayList.size accessible: module java.base does not "opens java.util" to unnamed module

如前所述,必须添加反射相关的 JVM 参数来避免此错误。对于 spring-boot Maven 插件,需使用 jvmArguments 配置标签:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <jvmArguments>--add-opens java.base/java.util=ALL-UNNAMED</jvmArguments>
    </configuration>
</plugin>

重新运行项目后,访问 http://localhost:8080/length 将返回预期值 0

3.3. Surefire 插件

首先为服务添加单元测试:

class MyServiceUnitTest {
    MyService myService = new MyService();
    
    @Test
    void whenGetLength_thenZeroIsReturned() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        assertEquals(0, myService.getLength());
    }
}

Surefire 插件常用于通过 Maven 运行单元测试。先尝试添加插件的最新版本基础配置:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <includes>
            <include>**/*UnitTest.java</include>
        </includes>
    </configuration>
</plugin>

通过 Maven 运行单元测试的最简命令:

$ mvn test

测试失败,报错与之前相同!surefire 插件的修正方式是设置 argLine 配置标签:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <includes>
            <include>**/*UnitTest.java</include>
        </includes>
        <argLine>--add-opens=java.base/java.util=ALL-UNNAMED</argLine>
    </configuration>
</plugin>

现在单元测试可以成功运行!

3.4. Failsafe 插件

为控制器编写集成测试:

@SpringBootTest(classes = MyApplication.class)
@AutoConfigureMockMvc
class MyControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    void whenGetLength_thenZeroIsReturned() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/length"))
          .andExpect(MockMvcResultMatchers.status().isOk())
          .andExpect(MockMvcResultMatchers.content().string("0"));
    }
}

通常使用failsafe 插件运行集成测试。再次配置其最新版本

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.3.0</version>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <includes>
            <include>**/*IntegrationTest.java</include>
        </includes>
    </configuration>
</plugin>

运行集成测试:

$ mvn verify

不出意外,集成测试因同样的错误失败。failsafe 插件的修正方式与 surefire 插件相同。必须使用 argLine 配置标签设置 JVM 参数:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.3.0</version>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <includes>
            <include>**/*IntegrationTest.java</include>
        </includes>
        <argLine>--add-opens=java.base/java.util=ALL-UNNAMED</argLine>
    </configuration>
</plugin>

现在集成测试将成功运行。

4. 总结

本文介绍了在 Maven 中运行 Java 代码时使用 JVM 参数的方法。我们探讨了两种设置全局构建参数的技巧,然后展示了如何为最常用的插件传递 JVM 参数。虽然示例不全面,但其他插件的配置方式通常类似。要了解具体配置方式,建议查阅插件文档。

完整代码可在 GitHub 获取。


原始标题:How to Pass JVM Arguments via Maven | Baeldung