【译】java.lang.System.getProperty() 的性能影响和解决方法转载
‘java.lang.System.getProperty()’ 是 Java 开发人员用来读取在程序启动期间配置系统属性的通用 API。
当你将“-DappName=buggyApp”作为应用程序的启动JVM 参数传递时,可以通过调用“java.lang.System.getProperty()”来读取“appName”系统属性的值。
举个栗子:
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.util.Hashtable.get()’ API。请注意,‘java.util.Hashtable.get()’ 是一个同步 API。这意味着在任何给定时间只有一个线程可以调用 ‘java.util.Hashtable.get()’ 方法。
如果新线程在第一个线程仍在执行时尝试调用“java.util.Hashtable.get()”API,则新线程将处于阻塞状态。当线程处于 BLOCKED 状态时,它将无法继续前进。
只有当第一个线程完成执行 ‘java.util.Hashtable.get()’ API 时,新线程才能继续前进。因此,如果在关键代码路径中调用了“java.lang.System.getProperty()”或“java.util.Hashtable.get()”,则会影响事务的响应时间。
Atlassian SDK 中的实际问题
最近在 Atlassian SDK 中观察到了这种类型的退化。从此应用程序中捕获了线程转储,并使用线程转储分析工具fastThread进行了分析。
根据线程heapdump分析报告,有189个线程处于BLOCKED状态。下面是线程转储报告中的传递依赖图,显示了处于 BLOCKED 状态的线程的名称。当您单击图表中的线程名称时,该特定线程的堆栈跟踪将在工具中报告。
图:‘java.lang.System.getProperty()’ API 上的 189 个线程被阻塞,由fastThread报告
由于“Camel Thread #6 –boneThreadPool”(即图中的红色节点),所有这些线程都进入了 BLOCKED 状态。这是该线程堆栈跟踪的最初几行:
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 –boneThreadPool”将是唯一允许进入此方法的线程。下面是最初的几行线程(共 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 状态。这个简单的更改可以提高应用程序的整体响应时间。