如何在我的 Java 应用程序中设置 JVM 时间(不是 OS 时间)?
How can I set JVM time(not OS time) in my Java application?
我正在设计一个回放测试应用程序,它将重放以前记录的数据并处理数据。我想在我的应用程序中配置日期和时间值,这不会影响操作系统。我发现有一个用于设置 JVM 时区的 JVM 参数,但我找不到类似的日期和时间参数。
可以吗?
此致
不是。没有办法使例如System.currentTimeMillis()
(因此,Instant.now()
和其他类似调用)在不改变 OS 时钟的情况下改变行为。
如果你想要 'testable' 时间,你不能调用那些方法。相反,制作一个 Clock
对象并使用它。
因此,这就是你必须做的:
查找代码中所有硬编码为使用 OS 时钟的地方。 System.currentTimeMillis()
、new Date()
、X.now()
,其中 X 是 java.time
包中的任何类型(Instant
、LocalDateTime
等),关键是任何库那是在幕后做的。将它们替换为适当的基于时钟的调用。 System.currentTimeMillis()
变为 clock.millis()
。 Instant.now()
变为 clock.instant()
,依此类推。
在测试期间,制作您自己的时钟,或使用 Clock.fixed
始终报告完全相同时间的时钟。在生产过程中,时钟必须是 Clock.systemUTC()
.
这可能需要使用依赖注入框架。或者,为您的整个应用程序制作一个全局时钟(一个 class 带有一个所有代码都从中读取的静态时钟字段,并且您的测试代码集,默认为 systemUTC)。
据我所知,无法配置 System.currentTimeMillis
和朋友使用的时钟。
为特定进程更改 date/time 的常用方法是使用拦截这两个函数的 LD_PRELOAD trick. There are two basic functions in Linux to get absolute time: gettimeofday
and clock_gettime
. Here is an example library 挂钩相应的系统函数。
不幸的是,上述技巧仅适用于 Linux。但这里有另一个专门针对 Java 的便携式解决方案。它依赖于 JVM Tool Interface.
在 OpenJDK 中,所有用于获取当前 date/time 的 Java API 最终都会调用 System.currentTimeMillis()
或 VM.getNanoTimeAdjustment()
。后者是提供高分辨率Clock
的内部JDK 方法。因此,为了更改 Java 应用程序的 date/time,调整这两个方法的实现就足够了。
思路如下。
- JVM TI 代理通过订阅 NativeMethodBind 事件拦截本机方法绑定。
- 当
NativeMethodBind
被 currentTimeMillis
或 getNanoTimeAdjustment
调用时,代理会记住本机函数的地址并将其替换为自己的地址。
- 每次调用钩子方法时,代理首先委托给原始函数,然后将指定的偏移量添加到返回值。
此类代理的完整代码为here。
如何编译:
g++ -O2 -fPIC -shared -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -olibfaketime.so faketime.cpp
如何运行:
java -agentpath:/path/to/libfaketime.so=<newtime> MainClass
其中 newtime
是距 Epoch 的绝对时间戳(以毫秒为单位),或者(当以 +
或 -
开头时)距当前时间的相对偏移量(以毫秒为单位)。
唯一的问题是 System.currentTimeMillis
是一个 JVM 内部函数。这意味着,JIT 编译的方法可能会跳过调用 JNI 实现(从而跳过我们的钩子)。为避免这种情况,我们可以简单地使用更多 JVM 选项禁用相应的内部函数:
-XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_currentTimeMillis -XX:CompileCommand=dontinline,java.lang.System::currentTimeMillis
我正在设计一个回放测试应用程序,它将重放以前记录的数据并处理数据。我想在我的应用程序中配置日期和时间值,这不会影响操作系统。我发现有一个用于设置 JVM 时区的 JVM 参数,但我找不到类似的日期和时间参数。 可以吗?
此致
不是。没有办法使例如System.currentTimeMillis()
(因此,Instant.now()
和其他类似调用)在不改变 OS 时钟的情况下改变行为。
如果你想要 'testable' 时间,你不能调用那些方法。相反,制作一个 Clock
对象并使用它。
因此,这就是你必须做的:
查找代码中所有硬编码为使用 OS 时钟的地方。
System.currentTimeMillis()
、new Date()
、X.now()
,其中 X 是java.time
包中的任何类型(Instant
、LocalDateTime
等),关键是任何库那是在幕后做的。将它们替换为适当的基于时钟的调用。System.currentTimeMillis()
变为clock.millis()
。Instant.now()
变为clock.instant()
,依此类推。在测试期间,制作您自己的时钟,或使用
Clock.fixed
始终报告完全相同时间的时钟。在生产过程中,时钟必须是Clock.systemUTC()
.
这可能需要使用依赖注入框架。或者,为您的整个应用程序制作一个全局时钟(一个 class 带有一个所有代码都从中读取的静态时钟字段,并且您的测试代码集,默认为 systemUTC)。
据我所知,无法配置 System.currentTimeMillis
和朋友使用的时钟。
为特定进程更改 date/time 的常用方法是使用拦截这两个函数的 LD_PRELOAD trick. There are two basic functions in Linux to get absolute time: gettimeofday
and clock_gettime
. Here is an example library 挂钩相应的系统函数。
不幸的是,上述技巧仅适用于 Linux。但这里有另一个专门针对 Java 的便携式解决方案。它依赖于 JVM Tool Interface.
在 OpenJDK 中,所有用于获取当前 date/time 的 Java API 最终都会调用 System.currentTimeMillis()
或 VM.getNanoTimeAdjustment()
。后者是提供高分辨率Clock
的内部JDK 方法。因此,为了更改 Java 应用程序的 date/time,调整这两个方法的实现就足够了。
思路如下。
- JVM TI 代理通过订阅 NativeMethodBind 事件拦截本机方法绑定。
- 当
NativeMethodBind
被currentTimeMillis
或getNanoTimeAdjustment
调用时,代理会记住本机函数的地址并将其替换为自己的地址。 - 每次调用钩子方法时,代理首先委托给原始函数,然后将指定的偏移量添加到返回值。
此类代理的完整代码为here。
如何编译:
g++ -O2 -fPIC -shared -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -olibfaketime.so faketime.cpp
如何运行:
java -agentpath:/path/to/libfaketime.so=<newtime> MainClass
其中 newtime
是距 Epoch 的绝对时间戳(以毫秒为单位),或者(当以 +
或 -
开头时)距当前时间的相对偏移量(以毫秒为单位)。
唯一的问题是 System.currentTimeMillis
是一个 JVM 内部函数。这意味着,JIT 编译的方法可能会跳过调用 JNI 实现(从而跳过我们的钩子)。为避免这种情况,我们可以简单地使用更多 JVM 选项禁用相应的内部函数:
-XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_currentTimeMillis -XX:CompileCommand=dontinline,java.lang.System::currentTimeMillis