使用静态分析防止Java类初始化死锁SpalantirFollowFeb 2·4分钟阅读
这篇博客文章讲述了在流行的Java库中发现竞争条件的简短故事,cdn能防御cc吗,以及我们如何决定通过静态代码分析防止类似的错误。在此过程中,我们将讨论导致此错误的JVM内部。
发现并修复JavaPoter竞争条件
在Palantir,我们使用Square的奇妙JavaPoter库执行许多代码生成任务,首先是生成RPC框架的Java绑定。我们的同事Justin早在2018年就报告说,JavaPoet在多线程环境中遇到了竞争条件:非确定性地说,两个线程在JavaPoet库代码中会陷入死锁。
正如Justin所猜测的,死锁的原因是两个类初始化代码路径之间的竞争条件。假设我们有一个类库,其初始化代码(例如通过静态字段)取决于它自己的子类Sub:
类库{私有静态最终Sub=new Sub();静态类Sub扩展了Base{}}
为了初始化Base,JVM必须首先初始化Sub。这在单线程环境中按预期工作,linux下防御ddos,但如果一个线程直接初始化Sub(即调用new Sub()),而另一个线程同时尝试通过Base的静态依赖项初始化Sub(即。,通过调用newbase())。事实证明,JavaPoet使用的正是这种模式:TypeName类有一个ClassName类型的静态字段,ClassName是TypeName的一个子类。
背景
Java语言规范在jls-12.4.2中定义了类初始化。特别是,对于C,语句"在初始化锁LC上同步"。这包括等待,直到当前线程可以获取LC。"直接为所描述的死锁打开大门。JDK-8037567中还指出,根据规范,这种死锁是预期的行为,必须在应用程序和库代码中而不是在JVM中防止。如果您有兴趣自己尝试,JDK-8037567包含一个最小的代码示例,可以可靠地再现竞争条件和死锁。
虽然JVM理论上可以提供更复杂的锁定协议,但这将导致类初始化的性能变化,惩罚启动时间以及可能破坏现有代码。许多组件或编程模式直接依赖于类初始化细节,例如按需初始化持有者设计模式。
修复和防止竞争条件
当然,可以通过重新组织静态字段或方法来修复此类死锁。然而,正如javapoter的例子所示,当公共字段或方法被洗牌时,修复程序可能会引入API中断。为了防止竞态条件和随后的API中断,在修复它们时,我们决定制定一个容易出错的规则,在编译时禁止许多已知的不良静态类依赖关系。
简而言之,该规则的工作原理如下:
给定类定义C,检查每个静态块和静态字段。如果代码包含对C子类的引用,从而导致基于jls-12.4.1的初始化,则可能出现死锁情况。初始化发生在以下情况:创建新实例,ddos攻击的检测与防御研究,读取静态(非常量)字段,写入静态字段,调用静态方法。如果子类由C和private包围,如何增加ddos防御,则子类无法在C之外实例化,因此不可能发生死锁。如果子类可见,但是有一个私有构造函数,没有可访问的静态字段或方法,死锁也是不可能的,但是这种假设是脆弱的,因为添加非私有构造函数、方法或字段可能会导致死锁发生!
请注意,此算法并不能捕获所有错误模式;有一些间接层的场景更难检测,但根据我们的经验,ddos防御方法弹性ip,并没有导致死锁。不幸的是,与臭名昭著的棘手静态分析问题一样,人们似乎不太可能(尽管我们没有写下证据)识别出所有此类模式。
示例
易出错规则禁止以下模式,例如:
类基{私有静态最终子子=新子();静态类Sub扩展了基类{}基类{static{new Sub();}静态类Sub扩展了基类{}}基类{static Sub Sub Sub=new Sub();静态类Sub扩展了基类{Sub(){}//可以通过实例化}}
从外部初始化。相反,规则允许这种形式的模式:
类基类{private/*非静态*/最终子子子子子=新子子();静态类Sub扩展了基类{}}基类{static Sub Sub Sub=new Sub();静态类Sub扩展了基类{private Sub(){}//不能在外部实例化}}}
我们已经将易出错规则应用于我们的许多内部代码存储库,遗憾的是,但并不意外的是,我们发现了许多违规行为。当然,我们已经修复了这些潜在的竞争条件,以防止它们在野外造成损害。
如果您想使用此易出错规则或我们的任何其他开发工具,请随时查看我们在GitHub上的基线存储库。
06-20 来源:长虹华伟
10-01 来源:长虹华伟
01-23 来源:长虹华伟
04-07 来源:长虹华伟
01-22 来源:长虹华伟
11-22 来源:长虹华伟
04-17 来源:长虹华伟
12-16 来源:长虹华伟
03-12 来源:长虹华伟
05-29 来源:长虹华伟