最终字段语义&使用 setAccessible(true) 进行反序列化

发布于 2024-12-08 11:23:16 字数 1353 浏览 4 评论 0原文

根据 Java 内存模型,在对象的构造函数中初始化且不受进一步修改的 final 字段保证其值能够被每个读取它的线程正确地看到,即使该对象本身已通过以下方式发布:数据竞赛。

JLS 讨论 17.5.3 Final 的后续修改字段,并含糊地指出

实现可以提供一种在最终字段安全上下文中执行代码块的方法。

它似乎并没有真正定义此类修改的语义,也没有确切地定义这个最终字段安全上下文事物必须存在的位置或如何定义它(即,JLS似乎没有给出任何保证最终字段的后续修改)。

我必须说,我没有完全理解偏序 dereferences()mc(),也没有完全理解采取的 freeze 操作的行为放置在对最终字段进行任何修改(归因于其的初始值或后续修改)之后。

在这种情况下,我想知道的是:(反)序列化框架(例如 Gson)如何保证包含在构造函数中正确初始化的最终字段的反序列化对象不会造成线程可见性问题?

例如,考虑这个类:

class X {
  private final String s;
  public X(final String s) { this.s = s; }
  @Override public String toString() { return s; }
}

以及以下代码:

final Gson gson = new Gson();
X x = gson.fromJson(gson.toJson(new X("abc")), X.class);
System.out.println(x);
// prints abc

使用调试器进入方法 fromJson ,我看到 sun.misc.Unsafe 用于分配一个实例X 没有调用其构造函数,并且字段是 setAccessible(true),最后它们被设置。

这仅适用于 Sun 的(或兼容的)JVM!看起来 Gson 也有针对多个 Android 版本的代码。

那么,是否存在与这些反序列化的最终字段相关的任何线程安全保证,就像我对使用 new X("abc “)?如果是这样,这个保证从何而来?

谢谢!

According to the Java Memory Model, a final field initialized in the object's constructor not subject to further modifications is guaranteed to have its value correctly seen by every thread reading it, even if the object itself has been published with data races.

The JLS talks about 17.5.3 Subsequent Modification of Final Fields, and vaguely states that

An implementation may provide a way to execute a block of code in a final field safe context.

It does not seem to really define the semantics of such modifications, nor where exactly this final field safe context thing must exist or how to define it (ie, the JLS doesn't seem to give any guarantee about subsequent modification of final fields).

I must say that I did not fully understood the partial orders dereferences() and mc(), nor the behavior of the freeze action that takes place after any modification to a final field (either the initial value attributed to it or subsequent modifications).

On this context, what I want to know is: how can a (de)serialization framework, such as Gson, guarantee that deserialized objects containing final fields properly initialized in a constructor will pose no thread visibility problem?

For example, consider this class:

class X {
  private final String s;
  public X(final String s) { this.s = s; }
  @Override public String toString() { return s; }
}

And the following code:

final Gson gson = new Gson();
X x = gson.fromJson(gson.toJson(new X("abc")), X.class);
System.out.println(x);
// prints abc

Stepping into the method fromJson with a debugger, I see that sun.misc.Unsafe is used to allocate an instance of X without calling its constructor, and the fields are setAccessible(true), and finally they get set.

And this is only in Sun's (or compatible) JVMs! It looks like Gson has code specific to multiple Android versions as well.

So, are there any thread-safety guarantees associated with these deserialized final fields, just like I would have with an instance of X constructed with new X("abc")? If so, where does this guarantee come from?

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

客…行舟 2024-12-15 11:23:16

线程安全

正如我所读到的,线程安全保证来自于给定属性被声明为final的事实。它不是线程安全的点是:

  • 在反序列化期间,当分配对象内存空间时,但在 final 属性分配值之前
  • 通过 Reflection API 修改 final 字段期间(即,当值正在被修改的过程中,并且在此过程完成之前)

这里需要注意的是,引用你链接到允许某事理论上的可能性存在除 Reflection API 之外的其他(但具有相同的 final 字段修改能力)。

冻结

最终字段的冻结发生在构造函数的末尾
最终字段被设置,并在每次修改后立即设置
通过反射或其他特殊机制的最终字段。

至于冻结调用,基本上是说 freeze 用于将属性标记为“无法更改”,它是这样做的:

  • 在构造函数执行结束时,其中未定义的 final 字段实际上被分配了一个值
  • final 字段值更改后,通过诸如 Reflection API 之类的方式

线程安全问题仅适用到正在修改/反序列化的对象。

所以:

(...code that comes before...)
END final field safe context - entering non-threadsafe area

  final fields are not frozen
    code that deserializes an object OR modifies a final field via reflection
  final fields are re-frozen

RESUME final field safe context - re-entering threadsafe area
(...code that comes after...)

因此...

构造函数运行后(即您已经获得了分配了值的实例化对象),最终字段无法更改,因为它已被冻结,除非使用 Reflection API 直接更改该值 - 此后它会再次冻结,因为毕竟该字段被声明为最终的。

如果您正在寻找特定于 Android 的答案(以确保其 JVM 行为与 Sun/Oracle 的 JVM 一致),则需要找到该 JVM 的等效文档。

Thread safety

As I read it, the thread safety guarantee comes from the fact that a given attribute is declared as final. The point at which it is NOT thread safe, is either:

  • During deserialization, when the object memory space is allocated, but before the final attribute is assigned a value
  • During a modification of a final field via the Reflection API (i.e. while the value is in the process of being modified, and before it's done with this process)

The caveat here is that the reference you linked to allows the theoretical possibility of something other than the Reflection API (but with the same final field modification abilities) to exist.

Freezing

Freezes of a final field occur both at the end of the constructor in
which the final field is set, and immediately after each modification
of a final field via reflection or other special mechanism.

As far as the freezing calls, basically it's saying that freeze is used to mark an attributes as "this cannot be changed", and it does so:

  • At the end of the execution of a constructor, in which an undefined final field is actually assigned a value
  • After a final field value change, via something like the Reflection API

The thread-safety concern only applies to the object being modified/deserialized.

So:

(...code that comes before...)
END final field safe context - entering non-threadsafe area

  final fields are not frozen
    code that deserializes an object OR modifies a final field via reflection
  final fields are re-frozen

RESUME final field safe context - re-entering threadsafe area
(...code that comes after...)

Therefore...

After a constructor runs (i.e. you've got an instantiated object with values assigned), the final field cannot be altered, because it is frozen, except in the case where the Reflection API is used to directly alter that value - after which it is frozen again because, after all, the field is declared final.

If you're looking for an Android-specific answer to this (to ensure that its JVM behavior agrees with Sun's/Oracle's JVM), you'll need to find equivalent documentation for that JVM.

緦唸λ蓇 2024-12-15 11:23:16

解冻伴随着 (java.reflect.)Field.setAccessible(true) 调用。大多数定期使用反射来设置最终字段的框架通常在成功修改后永远不会调用 field.setAccessible(false) ,因此使该字段保持“未冻结”状态。

因此,任何其他负责的反射框架都会看到该字段是可访问的,并且可能会这样做。这是理论上的机会,但它可能会发生。序列化机制内部的此类操作使用Unsafe类(sun实现包)的方法是有原因的。

因此,当使用任何类型的反射 API 时,您确实可能会弄乱 JVM 并导致任何类型的不可预测的行为。唯一保存默认反序列化机制的是它是在实例创建时完成的,此时不可能发生任何并发访问。

这就是为什么安全管理器可以限制甚至禁止此类访问。

The unfreezing comes along with the (java.reflect.)Field.setAccessible(true) call. Most frameworks using reflection regularly to set final fields often never call field.setAccessible(false) after a successful modification so leaving this field 'unfrozen'.

So any other reflection framework in charge will see that the field is accessible and might do so. This is a theoretical chance but it might occure. There is a reason why such kind of operation inside the serialization mechanism is using a method of the class Unsafe (sun implementation package).

So when using any kind of a reflection API you can really mess up a JVM and cause any kind of unpredictable behaviour. The only thing that saves the default deserialization mechanism is that it is done on instance creation time, where no chance exist that there will be any concurrent access happening.

Thats why such kind of access can be restricted or even forbidden by a SecurityManager.

一生独一 2024-12-15 11:23:16

来自JLS

最终字段的冻结发生在设置最终字段的构造函数的末尾,以及每次通过反射或其他特殊机制修改最终字段之后。

由于记忆效应仅根据 freeze 操作来定义,这意味着如果在构造函数之后修改 final 字段,语义也适用 - 只要对象引用不存在在此之前泄露。这被认为是合法的用例。

一旦对象引用被发布,进一步修改 Final 字段就不是一个好主意。

From JLS

Freezes of a final field occur both at the end of the constructor in which the final field is set, and immediately after each modification of a final field via reflection or other special mechanism.

since the memory effect is defined only in term of freeze action, this implies that semantics also applies if a final field is modified after constructor - as long as the object reference isn't leaked prior to that. This is considered a legitimate use case.

As soon as the object reference is published, further modification on a final field is not a good idea.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文