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

huangapple go评论65阅读模式
英文:

How can I set JVM time(not OS time) in my Java application?

问题

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

谢谢

英文:

I'm designing a backtesting application that will replay previously recorded data and process the data. I want to configure the date and time value in my application which will not affect the operating system. I find that there is a JVM parameter for setting the timezone of the JVM but I could not find a similar thing for date and time.
Is it possible to do that?

Regards

答案1

得分: 6

这并不是可以实现的。无法在不改变操作系统时钟的情况下更改例如 System.currentTimeMillis()(因此,Instant.now() 以及其他类似调用)的行为。

如果您想要具有“可测试”的时间,就不能调用那些方法。取而代之的是,创建一个 Clock 对象并使用它。

因此,您需要执行以下操作:

  1. 找出代码中所有硬编码使用操作系统时钟的地方。例如 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 等方法使用的时钟。

英文:

It is not. There is no way to make e.g. System.currentTimeMillis() (and therefore, Instant.now() and other such calls as well) change behaviour without changing the OS clock.

If you want 'testable' time, you can't call those methods. Instead, make a Clock object and use that.

Thus, this is what you have to do:

  1. Find all places in your code that are hardcoded to use OS clocks. System.currentTimeMillis(), new Date(), X.now() where X is any type from the java.time package (Instant, LocalDateTime, etcetera), and crucially any library that is doing that under the hood. Replace them with the appropriate clock based call instead. System.currentTimeMillis() becomes clock.millis(). Instant.now() becomes clock.instant(), and so on.

  2. During testing, make your own clock, or use Clock.fixed for a clock that always reports the exact same time. During production, clock has to be Clock.systemUTC().

This may require the use of dependency injection frameworks. Or, make one global clock for your entire app (a class with a static Clock field that all code reads from, and that your test code sets, which defaults to systemUTC).

As far as I know there is no way to configure a clock used by System.currentTimeMillis and friends.

答案2

得分: 4

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

不幸的是,上述技巧仅适用于Linux。但是这里有另一个特定于Java的可移植解决方案,它依赖于JVM工具接口

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

其思想如下:

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

这样代理的完整代码在此处

编译方法:

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可以是距离纪元的绝对时间戳(以毫秒为单位),或者(以+-开头)是相对于当前时间的毫秒偏移。

唯一的问题是,System.currentTimeMillis是JVM内部函数。这意味着,经JIT编译的方法可能会跳过调用JNI实现(从而跳过我们的挂钩)。为了避免这种情况,我们可以通过一些JVM选项来简单地禁用相应的内部函数:

-XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_currentTimeMillis -XX:CompileCommand=dontinline,java.lang.System::currentTimeMillis
英文:

The usual way to change date/time for a particular process is to hook corresponding system functions with an LD_PRELOAD trick. There are two basic functions in Linux to get absolute time: gettimeofday and clock_gettime. Here is an example library that intercepts these two functions.

Unfortunately, the above trick works on Linux only. But here is another portable solution specifically for Java. It relies on JVM Tool Interface.

In OpenJDK all Java APIs for getting current date/time end up in calling either System.currentTimeMillis() or VM.getNanoTimeAdjustment(). The latter is the internal JDK method to provide high resolution Clock. So, in order to change date/time for a Java application, it's enough to tweak the implementation of those two methods.

The idea is the following.

  • JVM TI agent intercepts native method bindings by subscribing to NativeMethodBind event.
  • When NativeMethodBind is called for currentTimeMillis or getNanoTimeAdjustment, the agent remembers the address of the native function and replaces it with its own one.
  • Every time the hooked methods are called, the agent first delegates to the original function and then adds the specified offset to the returned value.

The complete code for such an agent is here.

How to compile:

g++ -O2 -fPIC -shared -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -olibfaketime.so faketime.cpp

How to run:

java -agentpath:/path/to/libfaketime.so=&lt;newtime&gt; MainClass

where newtime is either an absolute timestamp in milliseconds from Epoch, or (when begins with + or -) a relative offset in milliseconds from current time.

The only problem is that System.currentTimeMillis is a JVM intrinsic. This means, a JIT compiled method may skip calling JNI implementation (and thus skip our hook). To avoid this, we can simply disable the corresponding intrinsic with a few more JVM options:

-XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_currentTimeMillis -XX:CompileCommand=dontinline,java.lang.System::currentTimeMillis

huangapple
  • 本文由 发表于 2020年10月13日 19:42:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/64334569.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定