我们最近将消息处理应用程序从 Java 7 升级到了 Java 8。升级后,我们偶尔会遇到异常,即在读取流时已关闭它。日志显示终结器线程正在对保存流的对象调用 finalize()(进而关闭流)。
代码基本大纲如下:
MIMEWriter writer = new MIMEWriter( out );
in = new InflaterInputStream( databaseBlobInputStream );
MIMEBodyPart attachmentPart = new MIMEBodyPart( in );
writer.writePart( attachmentPart );
MIMEWriter 和 MIMEBodyPart 是本地 MIME/HTTP 库的一部分。 MIMEBodyPart 扩展了 HTTPMessage,它有以下内容:
public void close() throws IOException
{
if ( m_stream != null )
{
m_stream.close();
}
}
protected void finalize()
{
try
{
close();
}
catch ( final Exception ignored ) { }
}
异常发生在MIMEWriter.writePart的调用链中,如下:
MIMEWriter.writePart() 写入部分的 header ,然后调用 part.writeBodyPartContent(this)MIMEBodyPart.writeBodyPartContent() 调用我们的实用方法 IOUtil.copy( getContentStream(), out ) 将内容流式传输到输出MIMEBodyPart.getContentStream() 只是返回传入构造函数的输入流(见上面的代码块)IOUtil.copy 有一个循环,从输入流中读取一个 8K block 并将其写入输出流,直到输入流为空。IOUtil.copy运行时调用MIMEBodyPart.finalize(),得到如下异常:
java.io.IOException: Stream closed
at java.util.zip.InflaterInputStream.ensureOpen(InflaterInputStream.java:67)
at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:142)
at java.io.FilterInputStream.read(FilterInputStream.java:107)
at com.blah.util.IOUtil.copy(IOUtil.java:153)
at com.blah.core.net.MIMEBodyPart.writeBodyPartContent(MIMEBodyPart.java:75)
at com.blah.core.net.MIMEWriter.writePart(MIMEWriter.java:65)
我们在 HTTPMessage.close() 方法中添加了一些日志,该方法记录了调用者的堆栈跟踪,并证明调用 HTTPMessage.finalize() 的肯定是终结器线程 而 IOUtil.copy() 正在运行。
MIMEBodyPart 对象在 MIMEBodyPart.writeBodyPartContent 的堆栈帧中绝对可以从当前线程的堆栈中以 this 访问。我不明白为什么 JVM 会调用 finalize().
我尝试提取相关代码并在我自己的机器上紧密循环运行它,但我无法重现该问题。我们可以在我们的一个开发服务器上以高负载可靠地重现问题,但是任何创建更小的可重现测试用例的尝试都失败了。代码在Java 7下编译,但在Java 8下执行。如果我们不重新编译就切换回Java 7,就不会出现问题。
作为一种解决方法,我已经使用 Java Mail MIME 库重写了受影响的代码,并且问题已经消失(推测 Java Mail 不使用 finalize())。但是,我担心应用程序中的其他 finalize() 方法可能被错误地调用,或者 Java 正在尝试对仍在使用的对象进行垃圾收集。
我知道当前的最佳实践建议不要使用 finalize(),我可能会重新访问这个自制库以删除 finalize() 方法。话虽这么说,以前有人遇到过这个问题吗?有人对原因有任何想法吗?
最佳答案
这里有点猜想。即使堆栈上的局部变量中存在对它的引用,并且即使在堆!要求是对象不可访问。即使它在堆栈上,如果没有后续代码触及该引用,它也可能无法访问。
见 this other answer有关如何在引用它的局部变量仍在范围内时对对象进行 GC 的示例。
这是一个在实例方法调用处于 Activity 状态时如何完成对象的示例:
class FinalizeThis {
protected void finalize() {
System.out.println("finalized!");
}
void loop() {
System.out.println("loop() called");
for (int i = 0; i < 1_000_000_000; i++) {
if (i % 1_000_000 == 0)
System.gc();
}
System.out.println("loop() returns");
}
public static void main(String[] args) {
new FinalizeThis().loop();
}
}
当 loop() 方法处于 Activity 状态时,任何代码都不可能对 FinalizeThis 对象的引用做任何事情,因此无法访问。因此它可以被最终确定和 GC'ed。在 JDK 8 GA 上,这会打印以下内容:
loop() called
finalized!
loop() returns
每次。
MimeBodyPart 可能会发生类似的情况。它是否存储在局部变量中? (看起来是这样,因为代码似乎遵守了字段以 m_ 前缀命名的约定。)
更新
在评论中,OP 建议进行以下更改:
public static void main(String[] args) {
FinalizeThis finalizeThis = new FinalizeThis();
finalizeThis.loop();
}
有了这个改变,他没有观察到最终确定,我也没有。但是,如果进行进一步的改变:
public static void main(String[] args) {
FinalizeThis finalizeThis = new FinalizeThis();
for (int i = 0; i < 1_000_000; i++)
Thread.yield();
finalizeThis.loop();
}
最终确定再次发生。我怀疑原因是没有循环, main() 方法被解释,而不是编译。解释器可能对可达性分析不那么激进。有了 yield 循环,main() 方法被编译,JIT 编译器检测到 finalizeThis 变得不可访问,而 loop() 方法正在执行。
触发此行为的另一种方法是使用 JVM 的 -Xcomp 选项,这会强制方法在执行前进行 JIT 编译。我不会以这种方式运行整个应用程序 - JIT 编译所有内容可能会非常慢并且占用大量空间 - 但它对于在小测试程序中清除此类案例很有用,而不是修补循环。
关于java - finalize() 在 Java 8 中调用强可达对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26642153/
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser
我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL