如何遵循Java中值的来源?

时间:2009-10-25 12:34:26

标签: java debugging

我有一个很少得到错误值的变量。由于系统非常复杂,我无法跟踪值经过的所有代码路径 - 涉及多个线程,可以保存,然后从DB加载,依此类推。我将尝试使用代码图生成器来查看是否可以通过查看setter的调用方式来发现问题,可能还有其他一些技术。也许用一个跟踪地点和变化的类来包装值?我不确定这个问题是否足够清楚,但我很感激遇到这种情况的人的意见。

[编辑]问题不易重现,我无法在调试器中捕获它。我正在寻找静态分析或记录技术来帮助追踪问题。

[编辑2]为了让事情更清楚,我所说的值是一个时间戳,表示为64位长变量中Unix纪元(01/01/1970)的毫秒数。在某个未知点,该值的前32位被截断,产生完全不正确(和不可恢复)的时间戳。

[编辑3]好的,感谢您的一些建议以及几个小时的代码,我找到了罪魁祸首。基于毫秒的时间戳通过除以1000转换为基于秒的时间戳,并存储在int变量中。在代码的稍后时刻,基于秒的时间戳(int)乘以1000并存储到新的long变量中。由于1000和第二个时间戳都是int值,因此乘法的结果在转换为long之前被截断。这是一个微妙的,感谢所有帮助过的人。

10 个答案:

答案 0 :(得分:6)

如果您使用setter 且只有setter 来设置您的值,您可以添加这些行以跟踪线程和堆栈跟踪:

public void setTimestamp(long value) {
  if(log.idDebugEnabled) {
    log.debug("Setting the value to " + value + ". Old value is " + this.timestamp);
    log.debug("Thread is " + Thread.currentThread().getName());
    log.debug("Stacktrace is", new Throwable()); // we could also iterate on Thread.currentThread().getStackTrace()
  }
  // check for bad value
  if(value & 0xffffffff00000000L == 0L) {
    log.warn("Danger Will Robinson", new IlegalValueException());
  }
  this.timestamp = value;
}

此外,浏览包含该字段的类,并确保每个引用都通过setter完成(即使在私有/受保护的方法中)

修改

也许FindBugs可以在静态分析方面提供帮助,我稍后会尝试找到确切的规则。

答案 1 :(得分:4)

长的32位变化而不是整个值的事实强烈表明这是一个线程问题(两个线程同时更新变量)。由于java不保证对long值的原子访问,如果两个线程同时更新它,最终可能会有一半的位设置为一半而另一半。这意味着解决问题的最佳方法是从线程角度来看。可能的是,没有任何设置变量的方式,静态分析工具会显示一个不正确的值,而是需要检查围绕此变量的同步和锁定策略是否存在潜在的漏洞。

作为快速修复,您可以将该值包装在AtomicLong

答案 2 :(得分:2)

我同意 - 如果只是通过设置器更改了值(无论是什么orgin) - 最好是 - 那么最好的方法是修改setter来为你做跟踪(在每个设置下打印堆栈跟踪) ,可能只有当值设置为特定值时,如果减少了咔嗒声)

答案 3 :(得分:2)

多线程编程很难,但有一些IDE工具可以提供帮助。如果您有intellij IDEA,则可以使用analyze dataflow功能来确定更改的位置。如果不会向您显示实时流程(它是一个静态分析工具),但它可以为您提供一个良好的开端。

或者,你可以使用一些Aspects,只是在任何地方打印出变量的值,但结果调试信息太过于无意义。

解决方案是避免线程之间共享状态。使用不可变对象,并在功能上编程。

答案 4 :(得分:1)

1)假设foo是变量的名称,你可以在setter方法中添加这样的东西:

try {
  throw new Exception();
}
catch (Exception e) {
  System.out.println("foo == " + foo.toString());
  e.printStackTrace();
}

这将如何运作取决于调用setter的频率。如果在程序运行中被调用数千次,则可能无法在所有堆栈跟踪中找到错误值。 (我以前用它来解决像你这样的问题。它对我有用。)

2)如果你可以在调试器中运行你的应用程序,你可以识别变量的程序错误值,那么你可以在setter中设置一个断点,条件是它产生的值是什么坏。但这要求你可以写一个不良的测试,这可能是你做不到的。

3)由于您说(在后续编辑中)问题是高32位被归零,您可以在打印堆栈跟踪之前专门测试它。这应该可以减少调试输出的数量,使其易于管理。

答案 5 :(得分:1)

两件事:

首先,对我来说,它闻起来好像有些调用者在整数上下文中处理它们的时间戳,失去了高32位。可能是,正如 Yishai 推测的那样,与线程相关,但我会先看看正在执行的操作。但是,当然,您需要确保您的价值“原子地”更新 - 无论是使用AtomicLong,还是使用其他机制。

除了猜测之外,鉴于您丢失的是高32位,并且您知道自纪元以来的毫秒数,您的设置器可以强制执行有效性:如果提供的值小于程序启动时的时间戳,这是错误的,所以拒绝它,当然,打印堆栈跟踪。

答案 6 :(得分:0)

在你的问题中,你说的是一个值不正确的“变量”,并建议你可以尝试“用类包装值”。也许我在阅读你的选择过多,但我想更多地了解设计。

有问题的价值是原始的吗?它是一个在线程之间共享的大型复杂对象的字段吗?如果它是某个对象的字段,该对象是DTO还是它实现了特定于域的行为?

一般情况下,我同意之前的评论,重新审视“变量”是一个字段的对象,但有关此变量的性质和用法的更多信息将有助于指导更精确的建议。

答案 7 :(得分:0)

根据您的描述,我不知道这是否意味着实时调试应用程序是不可行的,但如果是,则根据您的IDE,有一堆调试选项可用。

我知道使用Eclipse,您可以在setter方法中设置条件断点。您可以指定仅在将值设置为特定值时挂起,并且还可以按线程进行筛选,以防您想要专注于特定线程。

答案 8 :(得分:0)

我宁愿在setter中保留一个断点Eclipse允许您这样做。

There are some IDE which allows you to halt ( wait for execution of next instruction ) the program, if the value of variable is changed.

答案 9 :(得分:0)

IMO调试此类问题的最佳方法是使用字段修改断点。 (特别是如果你广泛使用反射)

我不确定如何在eclipse中执行此操作,但在intellij中,您可以右键单击该字段并执行“添加断点”。