英文:
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
对象并使用它。
因此,您需要执行以下操作:
-
找出代码中所有硬编码使用操作系统时钟的地方。例如
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
等方法使用的时钟。
英文:
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:
-
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 thejava.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()
becomesclock.millis()
.Instant.now()
becomesclock.instant()
, and so on. -
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 beClock.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中有两个基本函数用于获取绝对时间:gettimeofday
和clock_gettime
。这里有一个示例库,用于拦截这两个函数。
不幸的是,上述技巧仅适用于Linux。但是这里有另一个特定于Java的可移植解决方案,它依赖于JVM工具接口。
在OpenJDK中,所有用于获取当前日期/时间的Java API最终都会调用System.currentTimeMillis()
或VM.getNanoTimeAdjustment()
其中之一。后者是提供高分辨率Clock
的内部JDK方法。因此,为了更改Java应用程序的日期/时间,只需调整这两种方法的实现即可。
其思想如下:
- JVM TI代理通过订阅NativeMethodBind事件来拦截本地方法绑定。
- 当调用
currentTimeMillis
或getNanoTimeAdjustment
的NativeMethodBind
时,代理会记住本机函数的地址,并将其替换为自己的地址。 - 每次调用挂钩的方法时,代理首先委托给原始函数,然后将指定的偏移添加到返回值中。
这样代理的完整代码在此处。
编译方法:
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 forcurrentTimeMillis
orgetNanoTimeAdjustment
, 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=<newtime> 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论