我正在设计一个回测应用程序,它将重播以前记录的数据并处理数据。我想在我的应用程序中配置不会影响操作系统的日期和时间值。我发现有一个用于设置 JVM 时区的 JVM 参数,但我找不到类似的日期和时间。 有可能做到吗?
问候
不是。没有办法制作例如System.currentTimeMillis()
(因此,Instant.now()
和其他此类调用)在不更改操作系统时钟的情况下更改行为。
如果你想要"可测试"的时间,你不能调用这些方法。相反,创建一个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()
。
这可能需要使用依赖关系注入框架。或者,为整个应用创建一个全局时钟(一个具有静态时钟字段的类,所有代码都从中读取,并且测试代码设置该字段默认为 systemUTC)。
据我所知,没有办法配置System.currentTimeMillis
和朋友使用的时钟。
更改特定进程的日期/时间的常用方法是使用LD_PRELOAD技巧挂钩相应的系统函数。Linux 中有两个基本函数可以获得绝对时间:gettimeofday
和clock_gettime
。下面是截获这两个函数的示例库。
不幸的是,上述技巧仅适用于 Linux。但这是另一个专门针对Java的便携式解决方案。它依赖于JVM工具接口。
在OpenJDK中,所有用于获取当前日期/时间的Java API最终都会调用System.currentTimeMillis()
或VM.getNanoTimeAdjustment()
。后者是提供高分辨率Clock
的内部JDK方法。因此,为了更改 Java 应用程序的日期/时间,调整这两种方法的实现就足够了。
这个想法如下。
- JVM TI 代理通过订阅 NativeMethodBind 事件来截获本机方法绑定。
- 当
NativeMethodBind
被调用currentTimeMillis
或getNanoTimeAdjustment
时,代理会记住本机函数的地址,并将其替换为自己的地址。 - 每次调用挂钩方法时,代理首先委托给原始函数,然后将指定的偏移量添加到返回值。
此类代理的完整代码在此处。
如何编译:
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