【译】一个Java UUID生成导致的性能问题和解决方案转载
Java 开发者们平时经常用 “java.util.UUID#randomUUID()” API 来生成 UUID(通用唯一标识符)编号(即“b8bbcbed-ca07-490c-8711-5118ee0af2f9”)。
在某些情况下,用这个API会影响到程序的性能。
‘java.util.UUID#randomUUID()’ API 是如何工作的?
java.util.UUID#randomUUID() API 在内部使用操作系统中的“熵”来生成唯一编号。“熵”是什么意思?Linux 内核使用某些技术,如用户的鼠标移动、硬件风扇噪音的变化、设备驱动程序的噪音变化……来生成随机数。
当操作系统中缺少“熵”时,随机数的生成将减慢。当速度变慢时,调用此“java.util.UUID#randomUUID()”API 调用的应用程序线程将被置于 BLOCKED 状态,它们将无法继续前进。
如果您的应用程序在关键代码路径中使用“java.util.UUID#randomUUID()”API,并且操作系统中缺少熵,那么多个线程可以进入此 BLOCKED 状态,从而使您的整个应用程序停止运行.
案例:java.util.UUID#randomUUID() API 中的 50 个线程被阻塞
这是线上问题的dump文件,它将提供更好的问题背景。(文件中,已将名称更改为“buggycompany”以隐藏软件的背景身份)。
在分析结果中,您可以注意到总共有 102 个线程。在这 102 个线程中,由于 ‘java.util.UUID#randomUUID()’ API 调用,有 50 个线程处于 BLOCKED 状态。下面是这 50 个线程之一的堆栈跟踪:
"[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'" waiting for lock java.security.SecureRandom@20a56b2b BLOCKED
java.security.SecureRandom.nextBytes(SecureRandom.java:433)
java.util.UUID.randomUUID(UUID.java:159)
com.buggycompany.jtm.bp.<init>(bp.java:185)
com.buggycompany.jtm.a4.f(a4.java:94)
com.buggycompany.agent.trace.RootTracer.topComponentMethodBbuggycompanyin(RootTracer.java:439)
weblogicx.servlet.gzip.filter.GZIPFilter.doFilter(GZIPFilter.java)
weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.wrapRun(WebAppServletContext.java:3730)
weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3696)
weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2273)
weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2179)
weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1490)
weblogic.work.ExecuteThread.execute(ExecuteThread.java:256)
weblogic.work.ExecuteThread.run(ExecuteThread.java:221)
您会注意到,由于缺少“熵”并且无法继续前进,因此在调用“java.util.UUID#randomUUID()”时线程进入了阻塞状态。由于那 50 个线程被卡住了。因此,它使应用程序无响应。
潜在的解决方案
如果您的应用程序中出现此问题,以下是解决这些问题的潜在解决方案:
1.JDK升级
这个问题源于 Java 中的一个已知错误。但是,自 JDK8u112 或 JDK9b105 以来,它已被修复。因此,如果您可以升级 JDK,请这样做。它应该可以解决问题。
2. 在 Linux 中安装 Haveged
如果您的应用程序在 Linux 中运行,那么您可以考虑安装 ‘haveged’ 库。“ haveged 项目”旨在提供一个基于 HAVEGE 算法改编的易于使用、不可预测的随机数生成器。这是“已处理”项目 GIT 存储库页面。以下是如何安装它:
在基于 Debian 的平台(Debian、Ubuntu)上:
sudo apt-get install rng-tools
sudo update-rc.d haveged defaults
在 Redhat 平台(RHEL、Fedora、CentOS)上:
sudo yum install rng-tools
sudo chkconfig haveged on
3. 使用 /dev/urandom 代替 /dev/random
类 Unix 操作系统提供了特殊文件“/dev/random”,用作伪随机数生成器。Java 使用这个文件来生成随机数。您可以将其配置为使用“/dev/urandom”而不是“/dev/random”。
‘/dev/urandom’ 是另一个能够生成随机数的特殊文件。但是,由于随机性较小,它具有降低安全性的缺点。您可以通过在启动期间将以下 JVM 参数传递给您的应用程序来实现它:
-Djava.security.egd=file:/dev/./urandom