如何在我的 Java 应用程序中设置 JVM 时间(而不是操作系统时间)?



我正在设计一个回测应用程序,它将重播以前记录的数据并处理数据。我想在我的应用程序中配置不会影响操作系统的日期和时间值。我发现有一个用于设置 JVM 时区的 JVM 参数,但我找不到类似的日期和时间。 有可能做到吗?

问候

不是。没有办法制作例如System.currentTimeMillis()(因此,Instant.now()和其他此类调用)在不更改操作系统时钟的情况下更改行为。

如果你想要"可测试"的时间,你不能调用这些方法。相反,创建一个Clock对象并使用它。

因此,这是您必须做的:

  1. 查找代码中硬编码为使用 OS 时钟的所有位置。System.currentTimeMillis()new Date()X.now()其中X是java.time包中的任何类型(InstantLocalDateTime等),以及至关重要的是任何在幕后执行此操作的库。请改为使用适当的基于时钟的调用替换它们。System.currentTimeMillis()变得clock.millis().Instant.now()变成clock.instant(),依此类推。

  2. 在测试期间,制作自己的时钟,或使用Clock.fixed作为始终报告完全相同时间的时钟。在生产过程中,时钟必须Clock.systemUTC()

这可能需要使用依赖关系注入框架。或者,为整个应用创建一个全局时钟(一个具有静态时钟字段的类,所有代码都从中读取,并且测试代码设置该字段默认为 systemUTC)。

据我所知,没有办法配置System.currentTimeMillis和朋友使用的时钟。

更改特定进程的日期/时间的常用方法是使用LD_PRELOAD技巧挂钩相应的系统函数。Linux 中有两个基本函数可以获得绝对时间:gettimeofdayclock_gettime。下面是截获这两个函数的示例库。

不幸的是,上述技巧仅适用于 Linux。但这是另一个专门针对Java的便携式解决方案。它依赖于JVM工具接口。

在OpenJDK中,所有用于获取当前日期/时间的Java API最终都会调用System.currentTimeMillis()VM.getNanoTimeAdjustment()。后者是提供高分辨率Clock的内部JDK方法。因此,为了更改 Java 应用程序的日期/时间,调整这两种方法的实现就足够了。

这个想法如下。

  • JVM TI 代理通过订阅 NativeMethodBind 事件来截获本机方法绑定。
  • NativeMethodBind被调用currentTimeMillisgetNanoTimeAdjustment时,代理会记住本机函数的地址,并将其替换为自己的地址。
  • 每次调用挂钩方法时,代理首先委托给原始函数,然后将指定的偏移量添加到返回值。

此类代理的完整代码在此处。

如何编译:

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

相关内容

最新更新