java.lang.System.getProperty()的性能影响
java.lang.System.getProperty() "是Java开发人员用来读取应用程序启动时配置的系统属性的常用API。例如,当你将"-DappName=buggyApp "作为应用程序的启动JVM参数时,"appName "系统属性的值可以通过调用 "java.lang.System.getProperty() "读取。例子:
public static String getAppName() {
String app = System.getProperty("appName");
return app;
}
当上述方法被调用时,'buggyApp'将被返回。然而,如果'java.lang.System.getProperty()'被用在关键的代码路径中,它有可能降低应用程序的性能。在这篇文章中,我们将讨论调用'java.lang.System.getProperty()'对性能的影响是什么,如何缓解它,以及由这个API引发的一个真实世界的生产问题。
使用'java.lang.System.getProperty()'API的性能影响是什么?
java.lang.System.getProperty()' API底层使用了'java.util.Hashtable.get()' API。请注意,'java.util.Hashtable.get()'是一个同步的API。这意味着在任何时候只有一个线程可以调用'java.util.Hashtable.get()'方法。如果一个新的线程试图在第一个线程还在执行时调用'java.util.Hashtable.get()'API,新线程将被置于BLOCKED状态。当一个线程处于BLOCKED状态时,它将无法向前推进。只有当第一个线程完成执行 "java.util.Hashtable.get() "API时,新线程才能够向前推进。因此,如果'java.lang.System.getProperty()'或'java.util.Hashtable.get()'在关键代码路径中被调用,将影响交易的响应时间。
Atlassian SDK中的真实世界问题
最近在Atlassian SDK中观察到这种类型的退化。线程转储被从该应用程序中捕获,并使用线程转储分析工具--fastThread进行分析。
根据线程转储分析报告,有189个线程处于BLOCKED状态。下面是线程转储报告中的横向依赖关系图,显示了处于BLOCKED状态的线程名称。 当你点击图中的线程名称时,该特定线程的堆栈跟踪将在该工具中报告。
图:189个线程在'java.lang.System.getProperty()'API上被封锁,由fastThread报告。
所有这些线程进入BLOCKED状态是因为 "Camel Thread #6 - backboneThreadPool"(即图中的红色节点)。下面是这个线程的堆栈跟踪的最初几行。
Camel Thread #6 – backboneThreadPool
Stack Trace is:
at java.util.Hashtable.get(Hashtable.java:362)
- locked <0x0000000080f5e118> (a java.util.Properties)
at java.util.Properties.getProperty(Properties.java:969)
at java.util.Properties.getProperty(Properties.java:988)
at java.lang.System.getProperty(System.java:756)
at net.java.ao.atlassian.ConverterUtils.enforceLength(ConverterUtils.java:16)
at net.java.ao.atlassian.ConverterUtils.checkLength(ConverterUtils.java:9)
:
:
图:获得LOCK的线程的堆栈跟踪
从堆栈跟踪中,你可以注意到这个线程正在调用 "java.lang.System.getProperty() "API。由于'java.lang.System.getProperty()'API底层使用'java.util.Hashtable.get()'API(它是一个
是一个同步的API调用)。因此,"Camel Thread #6 - backboneThreadPool "将是唯一允许进入这个方法的线程。下面是几个线程(189个线程中)的最初几行,它们处于BLOCKED状态,因为它们正在等待进入 "java.util.Hashtable.get() "API。
http-nio-8080-exec-293
Stack Trace is:
java.lang.Thread.State: BLOCKED (on object monitor)
at java.util.Hashtable.get(Hashtable.java:362)
- waiting to lock <0x0000000080f5e118> (a java.util.Properties)
at java.util.Properties.getProperty(Properties.java:969)
at java.util.Properties.getProperty(Properties.java:988)
at java.lang.System.getProperty(System.java:756)
at net.java.ao.atlassian.ConverterUtils.enforceLength(ConverterUtils.java:16)
at net.java.ao.atlassian.ConverterUtils.checkLength(ConverterUtils.java:9)
:
:
图:其中一个等待LOCK的BLOCKED线程的堆栈痕迹
http-nio-8080-exec-279
Stack Trace is:
java.lang.Thread.State: BLOCKED (on object monitor)
at java.util.Hashtable.get(Hashtable.java:362)
- waiting to lock <0x0000000080f5e118> (a java.util.Properties)
at java.util.Properties.getProperty(Properties.java:969)
at java.util.Properties.getProperty(Properties.java:988)
at java.lang.System.getProperty(System.java:756)
at org.ofbiz.core.entity.EntityFindOptions.<init>(EntityFindOptions.java:124)
:
:
图:另一个等待LOCK的BLOCKED线程的堆栈跟踪
由于这个 "java.lang.System.getProperty() "API出现在关键代码路径中,多个线程都试图调用它。因此,所有这些试图调用这个API的189个线程都进入了BLOCKED状态。最终,整个应用程序的响应时间被降低了。
解决方法是什么?
由于系统属性在运行时不会改变,我们不必在每一个事务中不断调用 "java.lang.System.getProperty() "API。相反,我们可以调用一次'java.lang.System.getProperty()'API,缓存其值,并在以后的所有调用中返回缓存的值,如下面的代码片断所示。
private static String app = System.getProperty("appName");
public static String getAppName() {
return app;
}
如果你注意到上面的代码,''java.lang.System.getProperty()'现在被分配给一个静态成员变量。这意味着这个API将在应用程序启动时被调用,而且只有一次。从那时起,如果有人调用getAppName()API,他将被返回缓存的值。因此,应用程序线程在运行时不会被置于BLOCKED状态。这个简单的改变可以提高应用程序的整体响应时间。
