├── .gitignore ├── LICENSE.txt ├── README.md ├── pom.xml └── src ├── main └── java │ ├── jdk │ └── internal │ │ └── perf │ │ └── Perf.java │ ├── org │ └── gridkit │ │ └── lab │ │ └── jvm │ │ ├── attach │ │ ├── AttachAPI.java │ │ ├── AttachManager.java │ │ ├── HeapDumper.java │ │ ├── HeapHisto.java │ │ ├── JavaProcessDetails.java │ │ ├── JavaProcessId.java │ │ ├── JavaProcessMatcher.java │ │ ├── LogStream.java │ │ ├── PatternJvmMatcher.java │ │ ├── Slf4JLogger.java │ │ └── SysLogger.java │ │ ├── perfdata │ │ └── JStatData.java │ │ └── threaddump │ │ ├── JvmThreadInfo.java │ │ ├── JvmThreadInfoParser.java │ │ ├── SimpleStackParser.java │ │ └── StackTraceElementParser.java │ └── sun │ └── misc │ └── Perf.java └── test └── java └── org └── gridkit └── lab └── jvm ├── attach ├── AttachCheck.java ├── AttachListCheck.java ├── ClassHistoCheck.java └── HeapDumpCheck.java ├── perfdata └── TestPerfData.java └── threaddump └── ThreadDumpCheck.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings/ 3 | .classpath 4 | .factorypath 5 | .project 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JVM Attach Protocol Wrapper 2 | ========= 3 | 4 | JVM Attach Protocol allow diagnostic tools send various commands to JVM identified by PID. 5 | Exact attach protocol is platform dependend, but standarized can comatible between JVM versions. 6 | 7 | 8 | Usage of JVM Attach Protocol requires `tools.jar` in class path. 9 | 10 | 11 | This project wraps API available via `tools.jar` with some helper code to 12 | 13 | - add `tools.jar` to classpath automatically 14 | - provide timeouts for command invocation 15 | - offer utility to parse JVM command output (e.g. heap histogram or stack trace) 16 | 17 | 18 | In addition, API for HotSpot JVM perf counter is also included. 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 4.0.0 20 | 21 | 22 | org.gridkit.lab 23 | grid-lab-pom 24 | 2 25 | 26 | 27 | jvm-attach-api 28 | 1.5 29 | ${project.groupId}::${project.artifactId} 30 | 31 | 32 | 33 | The Apache Software License, Version 2.0 34 | http://www.apache.org/licenses/LICENSE-2.0.txt 35 | repo 36 | 37 | 38 | 39 | 40 | 41 | alexey.ragozin 42 | Alexey Ragozin 43 | alexey.ragozin@gmail.com 44 | 45 | 46 | 47 | 48 | scm:git:https://github.com/gridkit/jvm-attach.git 49 | scm:git:https://github.com/gridkit/jvm-attach.git 50 | https://github.com/gridkit/jvm-attach 51 | jvm-attach-api-1.5 52 | 53 | 54 | 55 | 1.6 56 | 57 | 58 | 59 | 60 | 61 | org.slf4j 62 | slf4j-api 63 | 1.6.6 64 | true 65 | 66 | 67 | 68 | junit 69 | junit 70 | 4.10 71 | test 72 | 73 | 74 | 75 | ch.qos.logback 76 | logback-classic 77 | 1.0.6 78 | test 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-release-plugin 88 | 2.4 89 | 90 | true 91 | true 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-compiler-plugin 97 | 3.0 98 | 99 | ${javaVersion} 100 | ${javaVersion} 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-surefire-plugin 106 | 2.13 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-javadoc-plugin 111 | 2.9 112 | 113 | 114 | attach-javadoc 115 | 116 | jar 117 | 118 | 119 | 120 | 121 | 122 | org.apache.maven.plugins 123 | maven-source-plugin 124 | 2.2.1 125 | 126 | 127 | attach-source 128 | 129 | jar 130 | 131 | 132 | 133 | 134 | 135 | org.apache.maven.plugins 136 | maven-resources-plugin 137 | 2.6 138 | 139 | UTF-8 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | post_java9 148 | 149 | [1.9,) 150 | 151 | 152 | 153 | 154 | 155 | 156 | org.apache.maven.plugins 157 | maven-surefire-plugin 158 | 159 | --add-exports java.base/jdk.internal.perf=ALL-UNNAMED 160 | 161 | 162 | 163 | 164 | 165 | 166 | tools_jar 167 | 168 | 169 | ${java.home}/../lib/tools.jar 170 | 171 | 172 | 173 | 174 | com.sun 175 | tools 176 | 1.6 177 | system 178 | ${java.home}/../lib/tools.jar 179 | true 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /src/main/java/jdk/internal/perf/Perf.java: -------------------------------------------------------------------------------- 1 | package jdk.internal.perf; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | 6 | /** 7 | * Stub class to support pre and post Java 9 runtimes. 8 | * This class should never be loaded at runtime. 9 | * 10 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 11 | */ 12 | public final class Perf { 13 | 14 | public static Perf getPerf() { 15 | throw new UnsupportedOperationException("Stub class"); 16 | } 17 | 18 | public ByteBuffer attach(int paramInt, String paramString) throws IllegalArgumentException, IOException { 19 | throw new UnsupportedOperationException("Stub class"); 20 | } 21 | 22 | public ByteBuffer attach(String paramString1, int paramInt, String paramString2) 23 | throws IllegalArgumentException, IOException { 24 | throw new UnsupportedOperationException("Stub class"); 25 | } 26 | 27 | public ByteBuffer createLong(String paramString, int paramInt1, int paramInt2, long paramLong) { 28 | throw new UnsupportedOperationException("Stub class"); 29 | } 30 | 31 | public ByteBuffer createString(String paramString1, int paramInt1, int paramInt2, String paramString2, int paramInt3) { 32 | throw new UnsupportedOperationException("Stub class"); 33 | } 34 | 35 | public ByteBuffer createString(String paramString1, int paramInt1, int paramInt2, String paramString2) { 36 | throw new UnsupportedOperationException("Stub class"); 37 | } 38 | 39 | public ByteBuffer createByteArray(String paramString, int paramInt1, int paramInt2, byte[] paramArrayOfByte, int paramInt3) { 40 | throw new UnsupportedOperationException("Stub class"); 41 | } 42 | 43 | public long highResCounter() { 44 | throw new UnsupportedOperationException("Stub class"); 45 | } 46 | 47 | public long highResFrequency() { 48 | throw new UnsupportedOperationException("Stub class"); 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/AttachAPI.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | import java.lang.reflect.Method; 19 | import java.net.URL; 20 | import java.net.URLClassLoader; 21 | 22 | /** 23 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 24 | */ 25 | class AttachAPI { 26 | 27 | private static final LogStream LOG_ERROR = LogStream.error(); 28 | private static boolean started; 29 | 30 | static { 31 | try { 32 | 33 | if (ClassLoader.getSystemClassLoader() instanceof URLClassLoader) { 34 | // Try to add tools.jar into classpath 35 | String javaHome = System.getProperty("java.home"); 36 | String toolsJarURL = "file:" + javaHome + "/../lib/tools.jar"; 37 | 38 | // Make addURL public 39 | Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); 40 | method.setAccessible(true); 41 | 42 | URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader(); 43 | if (sysloader.getResourceAsStream("/com/sun/tools/attach/VirtualMachine.class") == null) { 44 | method.invoke(sysloader, (Object) new URL(toolsJarURL)); 45 | Thread.currentThread().getContextClassLoader().loadClass("com.sun.tools.attach.VirtualMachine"); 46 | Thread.currentThread().getContextClassLoader().loadClass("com.sun.tools.attach.AttachNotSupportedException"); 47 | } 48 | } 49 | else { 50 | // is it Java 9 or above? 51 | // let's hope tools classes are already on classpath 52 | Thread.currentThread().getContextClassLoader().loadClass("com.sun.tools.attach.VirtualMachine"); 53 | Thread.currentThread().getContextClassLoader().loadClass("com.sun.tools.attach.AttachNotSupportedException"); 54 | } 55 | 56 | } catch (Exception e) { 57 | LOG_ERROR.log("Java home points to " + System.getProperty("java.home") + " make sure it is not a JRE path"); 58 | LOG_ERROR.log("Failed to add tools.jar to classpath", e); 59 | } 60 | started = true; 61 | }; 62 | 63 | public static void ensureToolsJar() { 64 | if (!started) { 65 | LOG_ERROR.log("Attach API not initialized"); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/AttachManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.InputStreamReader; 23 | import java.io.OutputStream; 24 | import java.io.Reader; 25 | import java.io.StringWriter; 26 | import java.lang.reflect.InvocationTargetException; 27 | import java.lang.reflect.Method; 28 | import java.nio.CharBuffer; 29 | import java.util.ArrayList; 30 | import java.util.Arrays; 31 | import java.util.Collections; 32 | import java.util.HashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Properties; 36 | import java.util.concurrent.Callable; 37 | import java.util.concurrent.ExecutionException; 38 | import java.util.concurrent.ExecutorService; 39 | import java.util.concurrent.Future; 40 | import java.util.concurrent.FutureTask; 41 | import java.util.concurrent.SynchronousQueue; 42 | import java.util.concurrent.ThreadFactory; 43 | import java.util.concurrent.ThreadPoolExecutor; 44 | import java.util.concurrent.TimeUnit; 45 | import java.util.concurrent.TimeoutException; 46 | 47 | import javax.management.MBeanServerConnection; 48 | import javax.management.remote.JMXConnector; 49 | import javax.management.remote.JMXConnectorFactory; 50 | import javax.management.remote.JMXServiceURL; 51 | 52 | import com.sun.tools.attach.AgentInitializationException; 53 | import com.sun.tools.attach.AgentLoadException; 54 | import com.sun.tools.attach.AttachNotSupportedException; 55 | import com.sun.tools.attach.VirtualMachine; 56 | import com.sun.tools.attach.VirtualMachineDescriptor; 57 | 58 | import sun.tools.attach.HotSpotVirtualMachine; 59 | 60 | /** 61 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 62 | */ 63 | public class AttachManager { 64 | 65 | private static final LogStream LOG_WARN = LogStream.warn(); 66 | private static final LogStream LOG_INFO = LogStream.info(); 67 | private static final LogStream LOG_DEBUG = LogStream.debug(); 68 | 69 | private static long ATTACH_TIMEOUT = TimeUnit.MILLISECONDS.toNanos(500); 70 | private static long VM_LIST_EXPIRY = TimeUnit.SECONDS.toNanos(1); 71 | private static long VM_PROPS_EXPIRY = TimeUnit.SECONDS.toNanos(1); 72 | private static long VM_MBEAN_SERVER_EXPIRY = TimeUnit.SECONDS.toNanos(30); 73 | 74 | static { 75 | AttachAPI.ensureToolsJar(); 76 | } 77 | 78 | public static void ensureToolsJar() { 79 | // do nothing, just ensure call to static initializer 80 | } 81 | 82 | private static AttachManagerInt INSTANCE = new AttachManagerInt(); 83 | 84 | private static ExecutorService threadPool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 500, TimeUnit.MILLISECONDS, new SynchronousQueue(), new ThreadFactory() { 85 | int counter = 0; 86 | 87 | @Override 88 | public synchronized Thread newThread(Runnable r) { 89 | Thread t = new Thread(r); 90 | t.setDaemon(true); 91 | t.setName("JvmAttachWorker-" + (counter++)); 92 | return t; 93 | } 94 | }); 95 | 96 | public static JavaProcessDetails getDetails(long pid) { 97 | return INSTANCE.internalGetDetails(pid); 98 | } 99 | 100 | public static JavaProcessDetails getDetails(JavaProcessId jpid) { 101 | return getDetails(jpid.getPID()); 102 | } 103 | 104 | public static List listJavaProcesses() { 105 | return INSTANCE.internalListJavaProcesses(); 106 | } 107 | 108 | public static List listJavaProcesses(JavaProcessMatcher matcher) { 109 | return INSTANCE.internalListJavaProcesses(matcher); 110 | } 111 | 112 | public static MBeanServerConnection getJmxConnection(long pid) { 113 | return INSTANCE.internalGetJmxConnection(pid); 114 | } 115 | 116 | public static MBeanServerConnection getJmxConnection(JavaProcessId jpid) { 117 | return getJmxConnection(jpid.getPID()); 118 | } 119 | 120 | public static void loadAgent(long pid, String agentPath, String agentArgs, long timeoutMs) throws Exception { 121 | INSTANCE.internalLoadAgent(pid, agentPath, agentArgs, timeoutMs); 122 | } 123 | 124 | public static List getHeapHisto(long pid, Object[] args, long timeoutMs) throws Exception { 125 | return INSTANCE.internalHeapHisto(pid, args, timeoutMs); 126 | } 127 | 128 | /** 129 | * Sends 'heapdump' command. 130 | * 131 | * @return JVM diagnostic output 132 | */ 133 | public static String getHeapDump(long pid, Object[] args, long timeoutMs) throws Exception { 134 | return INSTANCE.internalHeapDump(pid, args, timeoutMs); 135 | } 136 | 137 | public static void getThreadDump(long pid, Object[] args, Appendable output, long timeoutMs) throws Exception { 138 | INSTANCE.internalThreadDump(pid, args, output, timeoutMs); 139 | } 140 | 141 | private static void readString(InputStream stream, Appendable target) throws IOException { 142 | try { 143 | if (target == null) { 144 | return; 145 | } 146 | Reader r = new InputStreamReader(stream); 147 | CharBuffer cb = CharBuffer.allocate(16 << 10); 148 | while(true) { 149 | int m = r.read(cb.array()); 150 | if (m < 0) { 151 | break; 152 | } 153 | cb.limit(m); 154 | target.append(cb); 155 | cb.clear(); 156 | } 157 | } 158 | finally { 159 | try { 160 | stream.close(); 161 | } 162 | catch(IOException e) { 163 | // ignore 164 | } 165 | } 166 | } 167 | 168 | private static void copy(InputStream in, OutputStream out) throws IOException { 169 | try { 170 | byte[] buf = new byte[1 << 12]; 171 | while(true) { 172 | int n = in.read(buf); 173 | if(n >= 0) { 174 | out.write(buf, 0, n); 175 | } 176 | else { 177 | break; 178 | } 179 | } 180 | } finally { 181 | try { 182 | in.close(); 183 | } 184 | catch(Exception e) { 185 | // ignore 186 | } 187 | } 188 | } 189 | 190 | static class AttachManagerInt { 191 | 192 | private List vmList; 193 | private long vmListEndOfLife; 194 | 195 | private Map> vmPropsCache = new HashMap>(); 196 | private Map> vmMBeanCache = new HashMap>(); 197 | 198 | private Map attachQueue = new HashMap(); 199 | 200 | JavaProcessDetails internalGetDetails(long pid) { 201 | String name = getProcesssName(pid); 202 | return new ProcessDetails(pid, name); 203 | } 204 | 205 | private String getProcesssName(long pid) { 206 | List vms = getVmList(); 207 | for(VirtualMachineDescriptor vm: vms) { 208 | if (vm.id().equals(String.valueOf(pid))) { 209 | return vm.displayName(); 210 | } 211 | } 212 | return ""; 213 | } 214 | 215 | private synchronized List getVmList() { 216 | if (vmList == null || vmListEndOfLife < System.nanoTime()) { 217 | vmList = VirtualMachine.list(); 218 | vmListEndOfLife = System.nanoTime() + VM_LIST_EXPIRY; 219 | } 220 | return vmList; 221 | } 222 | 223 | List internalListJavaProcesses() { 224 | List vms = getVmList(); 225 | List result = refine(vms); 226 | return result; 227 | } 228 | 229 | private List refine(List vms) { 230 | JavaProcessId[] jpids = new JavaProcessId[vms.size()]; 231 | for(int i = 0 ; i != jpids.length; ++i) { 232 | VirtualMachineDescriptor vm = vms.get(i); 233 | jpids[i] = new JavaProcessId(Long.parseLong(vm.id()), vm.displayName()); 234 | } 235 | List result = Arrays.asList(jpids); 236 | return result; 237 | } 238 | 239 | List internalListJavaProcesses(final JavaProcessMatcher matcher) { 240 | 241 | List> futures = new ArrayList>(); 242 | List vms = getVmList(); 243 | for(final VirtualMachineDescriptor vm: vms) { 244 | futures.add(threadPool.submit(new Callable() { 245 | 246 | @Override 247 | public JavaProcessId call() throws Exception { 248 | ProcessDetails pd = new ProcessDetails(Long.parseLong(vm.id()), vm.displayName()); 249 | if (matcher.evaluate(pd)) { 250 | return new JavaProcessId(pd.pid, pd.name); 251 | } 252 | else { 253 | return null; 254 | } 255 | } 256 | 257 | })); 258 | } 259 | 260 | List result = new ArrayList(); 261 | for(Future fp: futures) { 262 | try { 263 | JavaProcessId p = fp.get(); 264 | if (p != null) { 265 | result.add(p); 266 | } 267 | } 268 | catch(InterruptedException e) { 269 | Thread.interrupted(); 270 | return Collections.emptyList(); 271 | } 272 | catch(ExecutionException e) { 273 | LOG_DEBUG.log("Process filtering exception", e.getCause()); 274 | } 275 | } 276 | 277 | return result; 278 | } 279 | 280 | synchronized MBeanServerConnection internalGetJmxConnection(long pid) { 281 | 282 | Expirable mbh = vmMBeanCache.get(pid); 283 | if (mbh == null || mbh.expiryDeadline < System.nanoTime()) { 284 | MBeanServerConnection mserver = getMBeanServer(pid); 285 | mbh = new Expirable(System.nanoTime() + VM_MBEAN_SERVER_EXPIRY, mserver); 286 | vmMBeanCache.put(pid, mbh); 287 | } 288 | 289 | return mbh.value; 290 | } 291 | 292 | Properties internalGetSystemProperties(long pid) { 293 | 294 | Expirable proph; 295 | synchronized(this) { 296 | proph = vmPropsCache.get(pid); 297 | } 298 | if (proph == null || proph.expiryDeadline < System.nanoTime()) { 299 | Properties props = getSysProps(pid); 300 | proph = new Expirable(System.nanoTime() + VM_PROPS_EXPIRY, props); 301 | synchronized(this) { 302 | vmPropsCache.put(pid, proph); 303 | } 304 | } 305 | 306 | return proph.value; 307 | } 308 | 309 | Properties internalGetAgentProperties(long pid) { 310 | return getAgentProps(pid); 311 | } 312 | 313 | void internalLoadAgent(long pid, String agentPath, String agentArgs, long timeoutMs) throws Exception { 314 | try { 315 | attachAndPerform(pid, new LoadAgent(agentPath, agentArgs), TimeUnit.MILLISECONDS.toNanos(timeoutMs)); 316 | } catch (ExecutionException e) { 317 | if (isAttachException(e.getCause())) { 318 | throw new Exception(e.getCause().toString()); 319 | } else { 320 | if (e.getCause() instanceof Exception) { 321 | throw (Exception)e.getCause(); 322 | } 323 | else { 324 | throw e; 325 | } 326 | } 327 | } 328 | } 329 | 330 | List internalHeapHisto(long pid, Object[] args, long timeoutMs) throws Exception { 331 | try { 332 | return attachAndPerform(pid, new HeapHisto(args), TimeUnit.MILLISECONDS.toNanos(timeoutMs)); 333 | } catch (ExecutionException e) { 334 | if (isAttachException(e.getCause())) { 335 | throw new Exception(e.getCause().toString()); 336 | } else { 337 | if (e.getCause() instanceof Exception) { 338 | throw (Exception)e.getCause(); 339 | } 340 | else { 341 | throw e; 342 | } 343 | } 344 | } 345 | } 346 | 347 | String internalHeapDump(long pid, Object[] args, long timeoutMs) throws Exception { 348 | try { 349 | return attachAndPerform(pid, new HeapDump(args), TimeUnit.MILLISECONDS.toNanos(timeoutMs)); 350 | } catch (ExecutionException e) { 351 | if (isAttachException(e.getCause())) { 352 | throw new Exception(e.getCause().toString()); 353 | } else { 354 | if (e.getCause() instanceof Exception) { 355 | throw (Exception)e.getCause(); 356 | } 357 | else { 358 | throw e; 359 | } 360 | } 361 | } 362 | } 363 | 364 | String internalPrintFlag(long pid, String flag, long timeoutMs) throws Exception { 365 | try { 366 | return attachAndPerform(pid, new PrintVmFlag(flag), TimeUnit.MILLISECONDS.toNanos(timeoutMs)); 367 | } catch (ExecutionException e) { 368 | if (isAttachException(e.getCause())) { 369 | throw new Exception(e.getCause().toString()); 370 | } else { 371 | if (e.getCause() instanceof Exception) { 372 | throw (Exception)e.getCause(); 373 | } 374 | else { 375 | throw e; 376 | } 377 | } 378 | } 379 | } 380 | 381 | void internalJCmd(long pid, String command, Appendable output, long timeoutMs) throws Exception { 382 | try { 383 | attachAndPerform(pid, new JCmdCommand(command, output), TimeUnit.MILLISECONDS.toNanos(timeoutMs)); 384 | } catch (ExecutionException e) { 385 | if (isAttachException(e.getCause())) { 386 | throw new Exception(e.getCause().toString()); 387 | } else { 388 | if (e.getCause() instanceof Exception) { 389 | throw (Exception)e.getCause(); 390 | } 391 | else { 392 | throw e; 393 | } 394 | } 395 | } 396 | } 397 | 398 | String internalSendAttachCommand(long pid, String command, Object[] args, OutputStream output, long timeoutMs) throws Exception { 399 | try { 400 | StringBuilder sb = new StringBuilder(); 401 | attachAndPerform(pid, new GenericCommand(command, args, output), TimeUnit.MILLISECONDS.toNanos(timeoutMs)); 402 | return sb.toString(); 403 | } catch (ExecutionException e) { 404 | if (isAttachException(e.getCause())) { 405 | throw new Exception(e.getCause().toString()); 406 | } else { 407 | if (e.getCause() instanceof Exception) { 408 | throw (Exception)e.getCause(); 409 | } 410 | else { 411 | throw e; 412 | } 413 | } 414 | } 415 | } 416 | 417 | 418 | void internalThreadDump(long pid, Object[] args, Appendable output, long timeoutMs) throws Exception { 419 | try { 420 | attachAndPerform(pid, new ThreadDump(args, output), TimeUnit.MILLISECONDS.toNanos(timeoutMs)); 421 | } catch (ExecutionException e) { 422 | if (isAttachException(e.getCause())) { 423 | throw new Exception(e.getCause().toString()); 424 | } else { 425 | if (e.getCause() instanceof Exception) { 426 | throw (Exception)e.getCause(); 427 | } 428 | else { 429 | throw e; 430 | } 431 | } 432 | } 433 | } 434 | 435 | private boolean isAttachException(Throwable e) { 436 | return e.getClass().getName().startsWith("com.sun.tools.attach."); 437 | } 438 | 439 | private MBeanServerConnection getMBeanServer(long pid) { 440 | try { 441 | String uri; 442 | try { 443 | uri = attachAndPerform(pid, new GetManagementAgent(), ATTACH_TIMEOUT); 444 | } catch (InterruptedException e) { 445 | LOG_WARN.log("Cannot connect to JVM (" + pid + ") - interrupted"); 446 | return null; 447 | } catch (ExecutionException e) { 448 | Throwable cause = e.getCause(); 449 | if (cause instanceof AgentLoadException || cause instanceof AgentInitializationException) { 450 | LOG_DEBUG.log("Cannot connect to JVM (" + pid + "). Agent error: " + e.toString()); 451 | return null; 452 | } 453 | else { 454 | LOG_DEBUG.log("Cannot connect to JVM (" + pid + ") - " + e.toString()); 455 | return null; 456 | } 457 | } catch (TimeoutException e) { 458 | LOG_DEBUG.log("Cannot connect to JVM (" + pid + ") - timeout"); 459 | return null; 460 | } 461 | JMXServiceURL jmxurl = new JMXServiceURL(uri); 462 | JMXConnector conn = JMXConnectorFactory.connect(jmxurl); 463 | MBeanServerConnection mserver = conn.getMBeanServerConnection(); 464 | return mserver; 465 | 466 | } catch (Exception e) { 467 | LOG_DEBUG.log("Cannot connect to JVM (" + pid + ") - " + e.toString()); 468 | return null; 469 | } 470 | } 471 | 472 | private Properties getSysProps(long pid) { 473 | try { 474 | return attachAndPerform(pid, new GetVmSysProps(), ATTACH_TIMEOUT); 475 | } catch (InterruptedException e) { 476 | LOG_WARN.log("Failed to read system properties, JVM pid: " + pid + ", interrupted"); 477 | return new Properties(); 478 | } catch (ExecutionException e) { 479 | LOG_INFO.log("Failed to read system properties, JVM pid: " + pid + ", error: " + e.getCause().toString()); 480 | return new Properties(); 481 | } catch (TimeoutException e) { 482 | LOG_INFO.log("Failed to read system properties, JVM pid: " + pid + ", read timeout"); 483 | return new Properties(); 484 | } 485 | } 486 | 487 | private Properties getAgentProps(long pid) { 488 | try { 489 | return attachAndPerform(pid, new GetVmAgentProps(), ATTACH_TIMEOUT); 490 | } catch (InterruptedException e) { 491 | LOG_WARN.log("Failed to read agent properties, JVM pid: " + pid + ", interrupted"); 492 | return new Properties(); 493 | } catch (ExecutionException e) { 494 | LOG_INFO.log("Failed to read agent properties, JVM pid: " + pid + ", error: " + e.getCause().toString()); 495 | return new Properties(); 496 | } catch (TimeoutException e) { 497 | LOG_INFO.log("Failed to read agent properties, JVM pid: " + pid + ", read timeout"); 498 | return new Properties(); 499 | } 500 | } 501 | 502 | @SuppressWarnings("unused") 503 | private static String attachManagementAgent(VirtualMachine vm) throws IOException, AgentLoadException, AgentInitializationException 504 | { 505 | Properties localProperties = vm.getAgentProperties(); 506 | if (localProperties.containsKey("com.sun.management.jmxremote.localConnectorAddress")) { 507 | return ((String)localProperties.get("com.sun.management.jmxremote.localConnectorAddress")); 508 | } 509 | 510 | String jhome = vm.getSystemProperties().getProperty("java.home"); 511 | Object localObject = jhome + File.separator + "jre" + File.separator + "lib" + File.separator + "management-agent.jar"; 512 | File localFile = new File((String)localObject); 513 | 514 | if (!(localFile.exists())) { 515 | localObject = jhome + File.separator + "lib" + File.separator + "management-agent.jar"; 516 | 517 | localFile = new File((String)localObject); 518 | if (!(localFile.exists())) { 519 | throw new IOException("Management agent not found"); 520 | } 521 | } 522 | 523 | localObject = localFile.getCanonicalPath(); 524 | vm.loadAgent((String)localObject, "com.sun.management.jmxremote"); 525 | 526 | localProperties = vm.getAgentProperties(); 527 | return ((String)localProperties.get("com.sun.management.jmxremote.localConnectorAddress")); 528 | } 529 | 530 | private V attachAndPerform(long pid, VMAction action, long timeout) throws InterruptedException, ExecutionException, TimeoutException { 531 | VMTask task = new VMTask(action); 532 | boolean spawnThread = false; 533 | AttachRequest ar; 534 | synchronized(attachQueue) { 535 | ar = attachQueue.get(pid); 536 | if (ar != null) { 537 | ar.tasks.add(task); 538 | } 539 | else { 540 | ar = new AttachRequest(pid); 541 | ar.tasks.add(task); 542 | spawnThread = true; 543 | attachQueue.put(pid, ar); 544 | } 545 | } 546 | if (spawnThread) { 547 | Thread attacher = new Thread(ar); 548 | attacher.setName("AttachToJVM-" + pid); 549 | attacher.setDaemon(true); 550 | attacher.start(); 551 | } 552 | 553 | try { 554 | V result = task.box.get(timeout, TimeUnit.MILLISECONDS); 555 | return result; 556 | } 557 | finally { 558 | task.box.cancel(false); 559 | } 560 | } 561 | 562 | @SuppressWarnings("unused") 563 | private static VirtualMachine attachToJvm(final String id) throws AttachNotSupportedException, IOException { 564 | FutureTask vmf = new FutureTask(new Callable() { 565 | @Override 566 | public VirtualMachine call() throws Exception { 567 | return VirtualMachine.attach(id); 568 | } 569 | }); 570 | Thread attacher = new Thread(vmf); 571 | attacher.setName("AttachManager::attachToJvm(" + id + ")"); 572 | attacher.setDaemon(true); 573 | attacher.start(); 574 | try { 575 | return vmf.get(ATTACH_TIMEOUT, TimeUnit.NANOSECONDS); 576 | } catch (Exception e) { 577 | IOException er; 578 | if (e instanceof ExecutionException) { 579 | er = new IOException(e.getCause()); 580 | } 581 | else { 582 | er = new IOException(e); 583 | } 584 | if (attacher.isAlive()) { 585 | attacher.interrupt(); 586 | } 587 | throw er; 588 | } 589 | } 590 | 591 | @SuppressWarnings("unused") 592 | private static Properties getSysPropsAsync(final String id) { 593 | FutureTask vmf = new FutureTask(new Callable() { 594 | @Override 595 | public Properties call() throws Exception { 596 | VirtualMachine vm = null; 597 | try { 598 | vm = VirtualMachine.attach(id); 599 | return vm.getSystemProperties(); 600 | } finally { 601 | if (vm != null) { 602 | vm.detach(); 603 | } 604 | } 605 | } 606 | }); 607 | Thread attacher = new Thread(vmf); 608 | attacher.setName("AttachManager::getSysPropsAsync(" + id + ")"); 609 | attacher.setDaemon(true); 610 | attacher.start(); 611 | try { 612 | return vmf.get(ATTACH_TIMEOUT, TimeUnit.NANOSECONDS); 613 | } catch (Exception e) { 614 | Throwable x = e; 615 | if (e instanceof ExecutionException) { 616 | x = e.getCause(); 617 | } 618 | if (attacher.isAlive()) { 619 | attacher.interrupt(); 620 | } 621 | LOG_INFO.log("Attach to (" + id + ") has failed: " + x.toString()); 622 | LOG_DEBUG.log("Attach to (" + id + ") has failed", x); 623 | 624 | return new Properties(); 625 | } 626 | } 627 | 628 | private class ProcessDetails implements JavaProcessDetails { 629 | 630 | private final long pid; 631 | private final String name; 632 | 633 | public ProcessDetails(long pid, String name) { 634 | this.pid = pid; 635 | this.name = name; 636 | } 637 | 638 | @Override 639 | public long getPid() { 640 | return pid; 641 | } 642 | 643 | @Override 644 | public String getDescription() { 645 | return name; 646 | } 647 | 648 | @Override 649 | public JavaProcessId getJavaProcId() { 650 | return new JavaProcessId(pid, name); 651 | } 652 | 653 | @Override 654 | public Properties getSystemProperties() { 655 | return internalGetSystemProperties(pid); 656 | } 657 | 658 | @Override 659 | public Properties getAgentProperties() { 660 | return internalGetAgentProperties(pid); 661 | } 662 | 663 | /** 664 | * Queries JVM for internal flag. Will return null if command is not supported. 665 | */ 666 | @Override 667 | public String getVmFlag(String flag) { 668 | try { 669 | return internalPrintFlag(pid, flag, 5 * ATTACH_TIMEOUT); 670 | } catch (Exception e) { 671 | return null; 672 | } 673 | } 674 | 675 | @Override 676 | public MBeanServerConnection getMBeans() { 677 | return internalGetJmxConnection(pid); 678 | } 679 | 680 | @Override 681 | public void jcmd(String command, Appendable result) { 682 | try { 683 | internalJCmd(pid, command, result, ATTACH_TIMEOUT); 684 | } 685 | catch(RuntimeException e) { 686 | throw e; 687 | } 688 | catch(Exception e) { 689 | throw new RuntimeException(e); 690 | } 691 | } 692 | 693 | @Override 694 | public void sendAttachCommand(String command, Object[] args, OutputStream output, long timeoutMS) { 695 | try { 696 | internalSendAttachCommand(pid, command, args, output, timeoutMS); 697 | } 698 | catch(RuntimeException e) { 699 | throw e; 700 | } 701 | catch(Exception e) { 702 | throw new RuntimeException(e); 703 | } 704 | } 705 | } 706 | 707 | private static class Expirable { 708 | 709 | long expiryDeadline; 710 | V value; 711 | 712 | public Expirable(long expiryDeadline, V value) { 713 | this.expiryDeadline = expiryDeadline; 714 | this.value = value; 715 | } 716 | } 717 | 718 | private class AttachRequest implements Runnable { 719 | 720 | final long pid; 721 | final List> tasks; 722 | 723 | public AttachRequest(long pid) { 724 | this.pid = pid; 725 | this.tasks = new ArrayList>(); 726 | } 727 | 728 | @Override 729 | public void run() { 730 | VirtualMachine vm; 731 | try { 732 | try { 733 | vm = VirtualMachine.attach(String.valueOf(pid)); 734 | } 735 | catch(IOException e) { 736 | LogStream.debug().log("Attach attempt failed, would retry", e); 737 | // second try 738 | vm = VirtualMachine.attach(String.valueOf(pid)); 739 | } 740 | dispatch(vm); 741 | } catch (Throwable e) { 742 | fail(e); 743 | return; 744 | } 745 | try { 746 | vm.detach(); 747 | } catch (IOException e) { 748 | // ignore 749 | } 750 | } 751 | 752 | private void dispatch(VirtualMachine vm) { 753 | while(true) { 754 | VMTask task; 755 | synchronized(attachQueue) { 756 | if (tasks.isEmpty()) { 757 | attachQueue.remove(pid); 758 | return; 759 | } 760 | else { 761 | task = tasks.remove(0); 762 | } 763 | } 764 | task.perform(vm); 765 | } 766 | } 767 | 768 | private void fail(Throwable e) { 769 | LOG_INFO.log("Attach to (" + pid + ") has failed: " + e.toString()); 770 | LOG_DEBUG.log("Attach to (" + pid + ") has failed", e); 771 | while(true) { 772 | VMTask task; 773 | synchronized(attachQueue) { 774 | if (tasks.isEmpty()) { 775 | attachQueue.remove(pid); 776 | return; 777 | } 778 | else { 779 | task = tasks.remove(0); 780 | } 781 | } 782 | task.fail(e); 783 | } 784 | } 785 | } 786 | 787 | private static class FutureBox extends FutureTask { 788 | 789 | private FutureBox(Callable callable) { 790 | super(callable); 791 | } 792 | 793 | @Override 794 | public void setException(Throwable t) { 795 | super.setException(t); 796 | } 797 | } 798 | 799 | private static class VMTask { 800 | 801 | VirtualMachine vm; 802 | VMAction action; 803 | FutureBox box; 804 | 805 | public VMTask(VMAction action) { 806 | this.action = action; 807 | this.box = new FutureBox(new Callable() { 808 | @Override 809 | public V call() throws Exception { 810 | return VMTask.this.action.perform(new VMWarpper(vm)); 811 | } 812 | }); 813 | } 814 | 815 | public void perform(VirtualMachine vm) { 816 | this.vm = vm; 817 | box.run(); 818 | this.vm = null; 819 | } 820 | 821 | public void fail(Throwable e) { 822 | box.setException(e); 823 | } 824 | } 825 | 826 | private static interface VMAction { 827 | 828 | public V perform(VMWarpper vm) throws Exception; 829 | } 830 | 831 | private static class GetVmSysProps implements VMAction { 832 | 833 | @Override 834 | public Properties perform(VMWarpper vm) throws IOException { 835 | return vm.getSystemProperties(); 836 | } 837 | } 838 | 839 | private static class GetVmAgentProps implements VMAction { 840 | 841 | @Override 842 | public Properties perform(VMWarpper vm) throws IOException { 843 | return vm.getAgentProperties(); 844 | } 845 | } 846 | 847 | private static class PrintVmFlag implements VMAction { 848 | 849 | private String flag; 850 | 851 | public PrintVmFlag(String flag) { 852 | this.flag = flag; 853 | } 854 | 855 | @Override 856 | public String perform(VMWarpper vm) throws IOException { 857 | try { 858 | InputStream is = vm.printFlag(flag); 859 | BufferedReader br = new BufferedReader(new InputStreamReader(is)); 860 | return br.readLine(); 861 | } catch (Exception e) { 862 | return null; 863 | } 864 | } 865 | } 866 | 867 | private static class GetManagementAgent implements VMAction { 868 | 869 | @Override 870 | public String perform(VMWarpper vm) throws IOException, AgentLoadException, AgentInitializationException { 871 | Properties localProperties = vm.getAgentProperties(); 872 | if (localProperties.containsKey("com.sun.management.jmxremote.localConnectorAddress")) { 873 | String jmxuri = (String)localProperties.get("com.sun.management.jmxremote.localConnectorAddress"); 874 | LOG_DEBUG.log("JMX agent already running: " + jmxuri); 875 | return jmxuri; 876 | } 877 | 878 | if (vm.isJCmdSupported()) { 879 | LOG_DEBUG.log("[" + vm.id() + "] try to start JMX via jcmd"); 880 | 881 | try { 882 | StringBuilder sb = new StringBuilder(); 883 | readString(vm.execJCmd("ManagementAgent.start_local"), sb); 884 | if (sb.length() > 0) { 885 | LOG_DEBUG.log("[" + vm.id() + "] ManagementAgent.start_local -> " + sb.toString()); 886 | } 887 | } catch (Exception e) { 888 | LOG_DEBUG.log("[" + vm.id() + "] Failed to exec ManagementAgent.start_local: " + e.toString()); 889 | } 890 | } 891 | else { 892 | LOG_DEBUG.log("[" + vm.id() + "] try to starn JMX via 'management-agent.jar'"); 893 | 894 | String jhome = vm.getSystemProperties().getProperty("java.home"); 895 | Object localObject = jhome + File.separator + "jre" + File.separator + "lib" + File.separator + "management-agent.jar"; 896 | File localFile = new File((String)localObject); 897 | 898 | if (!(localFile.exists())) { 899 | localObject = jhome + File.separator + "lib" + File.separator + "management-agent.jar"; 900 | 901 | localFile = new File((String)localObject); 902 | if (!(localFile.exists())) { 903 | LOG_DEBUG.log("Failed to find 'management-agent.jar' cannot start JMX agent"); 904 | throw new IOException("Failed to find 'management-agent.jar' cannot start JMX agent"); 905 | } 906 | } 907 | 908 | localObject = localFile.getCanonicalPath(); 909 | LOG_DEBUG.log("[" + vm.id() + "] load agent form " + localObject); 910 | vm.loadAgent((String)localObject, "com.sun.management.jmxremote"); 911 | } 912 | 913 | localProperties = vm.getAgentProperties(); 914 | return ((String)localProperties.get("com.sun.management.jmxremote.localConnectorAddress")); 915 | } 916 | } 917 | 918 | private static class LoadAgent implements VMAction { 919 | private final String agentPath; 920 | private final String agentArgs; 921 | 922 | public LoadAgent(String agentPath, String agentArgs) { 923 | this.agentPath = agentPath; 924 | this.agentArgs = agentArgs; 925 | } 926 | 927 | @Override 928 | public Void perform(VMWarpper vm) throws Exception { 929 | vm.loadAgent(agentPath, agentArgs); 930 | return null; 931 | } 932 | } 933 | 934 | private static class HeapHisto implements VMAction> { 935 | 936 | private final Object[] args; 937 | 938 | public HeapHisto(Object[] args) { 939 | this.args = args; 940 | } 941 | 942 | @Override 943 | public List perform(VMWarpper vm) throws Exception { 944 | InputStream is = vm.heapHisto(args); 945 | List result = new ArrayList(); 946 | BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 947 | String line; 948 | while(null != (line = reader.readLine())) { 949 | result.add(line); 950 | } 951 | return result; 952 | } 953 | } 954 | 955 | private static class HeapDump implements VMAction { 956 | 957 | private final Object[] args; 958 | 959 | public HeapDump(Object[] args) { 960 | this.args = args; 961 | } 962 | 963 | @Override 964 | public String perform(VMWarpper vm) throws Exception { 965 | StringWriter sw = new StringWriter(); 966 | readString(vm.dumpHeap(args), sw); 967 | return sw.toString(); 968 | } 969 | } 970 | 971 | private static class ThreadDump implements VMAction { 972 | 973 | private final Object[] args; 974 | private final Appendable writer; 975 | 976 | public ThreadDump(Object[] args, Appendable writer) { 977 | this.args = args; 978 | this.writer = writer; 979 | } 980 | 981 | @Override 982 | public Void perform(VMWarpper vm) throws Exception { 983 | readString(vm.remoteDataDump(args), writer); 984 | return null; 985 | } 986 | } 987 | 988 | private static class JCmdCommand implements VMAction { 989 | 990 | private final String command; 991 | private final Appendable writer; 992 | 993 | public JCmdCommand(String command, Appendable writer) { 994 | this.command = command; 995 | this.writer = writer; 996 | } 997 | 998 | @Override 999 | public Void perform(VMWarpper vm) throws Exception { 1000 | if (!vm.isJCmdSupported()) { 1001 | throw new NoSuchMethodException("jcmd is not supported for this VM"); 1002 | } 1003 | readString(vm.execJCmd(command), writer); 1004 | return null; 1005 | } 1006 | } 1007 | 1008 | private static class GenericCommand implements VMAction { 1009 | 1010 | private final String command; 1011 | private final Object[] args; 1012 | private final OutputStream output; 1013 | 1014 | public GenericCommand(String command, Object[] args, OutputStream output) { 1015 | this.command = command; 1016 | this.args = args; 1017 | this.output = output; 1018 | } 1019 | 1020 | @Override 1021 | public Void perform(VMWarpper vm) throws Exception { 1022 | InputStream is = vm.execCommand(command, args); 1023 | if (output != null) { 1024 | copy(is, output); 1025 | } 1026 | else { 1027 | try { 1028 | is.close(); 1029 | } catch (Exception e) { 1030 | // ignore 1031 | } 1032 | } 1033 | return null; 1034 | } 1035 | } 1036 | 1037 | public static class VMWarpper { 1038 | 1039 | private final VirtualMachine vm; 1040 | 1041 | public VMWarpper(VirtualMachine vm) { 1042 | this.vm = vm; 1043 | } 1044 | 1045 | // public VirtualMachine unwarp() { 1046 | // return vm; 1047 | // } 1048 | // 1049 | public String id() { 1050 | return vm.id(); 1051 | } 1052 | 1053 | public void loadAgent(String agent, String args) throws AgentLoadException, AgentInitializationException, IOException { 1054 | vm.loadAgent(agent, args); 1055 | } 1056 | 1057 | public Properties getSystemProperties() throws IOException { 1058 | return vm.getSystemProperties(); 1059 | } 1060 | 1061 | public Properties getAgentProperties() throws IOException { 1062 | return vm.getAgentProperties(); 1063 | } 1064 | 1065 | public InputStream remoteDataDump(Object... args) throws IOException { 1066 | return ((HotSpotVirtualMachine)vm).remoteDataDump(args); 1067 | } 1068 | 1069 | public InputStream dumpHeap(Object... args) throws IOException { 1070 | return ((HotSpotVirtualMachine)vm).dumpHeap(args); 1071 | } 1072 | 1073 | public InputStream heapHisto(Object... args) throws IOException { 1074 | return ((HotSpotVirtualMachine)vm).heapHisto(args); 1075 | } 1076 | 1077 | public InputStream setFlag(String name, String value) throws IOException { 1078 | return ((HotSpotVirtualMachine)vm).setFlag(name, value); 1079 | } 1080 | 1081 | public InputStream printFlag(String name) throws IOException { 1082 | return ((HotSpotVirtualMachine)vm).printFlag(name); 1083 | } 1084 | 1085 | public boolean isJCmdSupported() { 1086 | return getExecuteJCmdMethod() != null; 1087 | } 1088 | 1089 | public InputStream execJCmd(String command) throws Exception { 1090 | try { 1091 | return (InputStream) getExecuteJCmdMethod().invoke(vm, command); 1092 | } catch (InvocationTargetException e) { 1093 | if (e.getTargetException() instanceof Exception) { 1094 | throw (Exception)e.getTargetException(); 1095 | } 1096 | else { 1097 | throw new ExecutionException(e.getTargetException()); 1098 | } 1099 | } 1100 | } 1101 | 1102 | public InputStream execCommand(String command, Object[] args) throws Exception { 1103 | try { 1104 | return (InputStream) getExecuteCommand().invoke(vm, command, args); 1105 | } catch (InvocationTargetException e) { 1106 | if (e.getTargetException() instanceof Exception) { 1107 | throw (Exception)e.getTargetException(); 1108 | } 1109 | else { 1110 | throw new ExecutionException(e.getTargetException()); 1111 | } 1112 | } 1113 | } 1114 | 1115 | private Method getExecuteJCmdMethod() { 1116 | Class c = vm.getClass(); 1117 | while(c != Object.class) { 1118 | try { 1119 | Method m = c.getDeclaredMethod("executeJCmd", String.class); 1120 | try { 1121 | m.setAccessible(true); 1122 | return m; 1123 | } catch (Exception e) { 1124 | LOG_DEBUG.log("Failed setAccessible on " + c.getSimpleName() + "." + m.getName(), e); 1125 | } 1126 | } 1127 | catch(NoSuchMethodException e) { 1128 | } 1129 | c = c.getSuperclass(); 1130 | } 1131 | return null; 1132 | } 1133 | 1134 | private Method getExecuteCommand() { 1135 | Class c = vm.getClass(); 1136 | while(c != Object.class) { 1137 | try { 1138 | Method m = c.getDeclaredMethod("executeCommand", String.class, Object[].class);//c.getDeclaredMethods(); 1139 | try { 1140 | m.setAccessible(true); 1141 | return m; 1142 | } catch (Exception e) { 1143 | LOG_DEBUG.log("Failed setAccessible on " + c.getSimpleName() + "." + m.getName(), e); 1144 | } 1145 | } 1146 | catch(NoSuchMethodException e) { 1147 | } 1148 | c = c.getSuperclass(); 1149 | } 1150 | c = vm.getClass(); 1151 | while(c != Object.class) { 1152 | try { 1153 | Method m = c.getDeclaredMethod("execute", String.class, Object[].class); 1154 | try { 1155 | m.setAccessible(true); 1156 | return m; 1157 | } catch (Exception e) { 1158 | LOG_DEBUG.log("Failed setAccessible on " + c.getSimpleName() + "." + m.getName(), e); 1159 | } 1160 | } 1161 | catch(NoSuchMethodException e) { 1162 | } 1163 | c = c.getSuperclass(); 1164 | } 1165 | return null; 1166 | } 1167 | } 1168 | } 1169 | } 1170 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/HeapDumper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | import java.io.File; 19 | import java.io.FileNotFoundException; 20 | 21 | /** 22 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 23 | */ 24 | public class HeapDumper { 25 | 26 | public static String dumpLive(int pid, String targetFile, long timeoutMs) { 27 | try { 28 | File file = new File(targetFile).getCanonicalFile(); 29 | file.getParentFile().mkdirs(); 30 | if (!file.getParentFile().isDirectory()) { 31 | throw new FileNotFoundException("Cannot create: " + file.getPath()); 32 | } 33 | file.delete(); 34 | String[] plive = { file.getPath(), "-live" }; 35 | return AttachManager.getHeapDump(pid, plive, timeoutMs); 36 | } 37 | catch(RuntimeException e) { 38 | throw e; 39 | } 40 | catch(Exception e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | 45 | public static String dumpAll(int pid, String targetFile, long timeoutMs) { 46 | try { 47 | File file = new File(targetFile).getCanonicalFile(); 48 | file.getParentFile().mkdirs(); 49 | if (!file.getParentFile().isDirectory()) { 50 | throw new FileNotFoundException("Cannot create: " + file.getPath()); 51 | } 52 | file.delete(); 53 | String[] plive = { file.getPath()}; 54 | return AttachManager.getHeapDump(pid, plive, timeoutMs); 55 | } 56 | catch(RuntimeException e) { 57 | throw e; 58 | } 59 | catch(Exception e) { 60 | throw new RuntimeException(e); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/HeapHisto.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collections; 20 | import java.util.Comparator; 21 | import java.util.LinkedHashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | /** 26 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 27 | */ 28 | public class HeapHisto { 29 | 30 | public static HeapHisto getHistoDead(int pid, long timeoutMs) { 31 | HeapHisto all = getHistoAll(pid, timeoutMs); 32 | HeapHisto live = getHistoLive(pid, timeoutMs); 33 | return subtract(all, live); 34 | } 35 | 36 | 37 | public static HeapHisto getHistoLive(int pid, long timeoutMs) { 38 | try { 39 | String[] plive = { "-live" }; 40 | List hh = AttachManager.getHeapHisto(pid, plive, timeoutMs); 41 | return HeapHisto.parse(hh); 42 | } 43 | catch(RuntimeException e) { 44 | throw e; 45 | } 46 | catch(Exception e) { 47 | throw new RuntimeException(e); 48 | } 49 | } 50 | 51 | public static HeapHisto getHistoAll(int pid, long timeoutMs) { 52 | try { 53 | String[] plive = { "-all" }; 54 | List hh = AttachManager.getHeapHisto(pid, plive, timeoutMs); 55 | return HeapHisto.parse(hh); 56 | } 57 | catch(RuntimeException e) { 58 | throw e; 59 | } 60 | catch(Exception e) { 61 | throw new RuntimeException(e); 62 | } 63 | } 64 | 65 | private Map histo = new LinkedHashMap(); 66 | 67 | public static HeapHisto parse(Iterable text) { 68 | HeapHisto histo = new HeapHisto(); 69 | for(String line: text) { 70 | String[] split = line.trim().split("\\s+"); 71 | if (split.length == 4 && split[0].endsWith(":")) { 72 | Bucket b = new Bucket(); 73 | b.num = Integer.parseInt(split[0].substring(0, split[0].length() - 1)); 74 | b.instances = Long.parseLong(split[1]); 75 | b.bytes = Long.parseLong(split[2]); 76 | b.className = split[3]; 77 | histo.histo.put(b.className, b); 78 | } 79 | } 80 | return histo; 81 | } 82 | 83 | public static HeapHisto subtract(HeapHisto a, HeapHisto b) { 84 | List buckets = new ArrayList(); 85 | for(Bucket b1: a.getBuckets()) { 86 | Bucket b2 = b.get(b1.className); 87 | 88 | Bucket nb = new Bucket(); 89 | nb.className = b1.className; 90 | nb.instances = b1.instances; 91 | nb.bytes = b1.bytes; 92 | 93 | if (b2 != null) { 94 | nb.instances -= b2.instances; 95 | nb.bytes -= b2.bytes; 96 | } 97 | if (nb.bytes != 0 || nb.instances != 0) { 98 | buckets.add(nb); 99 | } 100 | } 101 | for(Bucket b2: b.getBuckets()) { 102 | if (a.get(b2.className) == null) { 103 | Bucket nb = new Bucket(); 104 | nb.className = b2.className; 105 | nb.instances = -b2.instances; 106 | nb.bytes = -b2.bytes; 107 | buckets.add(nb); 108 | } 109 | } 110 | 111 | Collections.sort(buckets, new SizeComparator()); 112 | for(int i = 0; i != buckets.size(); ++i) { 113 | buckets.get(i).num = i + 1; 114 | } 115 | 116 | HeapHisto histo = new HeapHisto(); 117 | for(Bucket bb: buckets) { 118 | histo.histo.put(bb.className, bb); 119 | } 120 | 121 | return histo; 122 | } 123 | 124 | public List getBuckets() { 125 | List result = new ArrayList(histo.values()); 126 | Collections.sort(result, new SizeComparator()); 127 | return result; 128 | } 129 | 130 | public Bucket get(String classname) { 131 | return histo.get(classname); 132 | } 133 | 134 | public long totalInstances() { 135 | long sum = 0; 136 | for(Bucket b: histo.values()) { 137 | sum += b.instances; 138 | } 139 | return sum; 140 | } 141 | 142 | public long totalBytes() { 143 | long sum = 0; 144 | for(Bucket b: histo.values()) { 145 | sum += b.bytes; 146 | } 147 | return sum; 148 | } 149 | 150 | public String print() { 151 | return print(histo.size()); 152 | } 153 | 154 | public String print(int top) { 155 | StringBuilder sb = new StringBuilder(); 156 | int n = 0; 157 | for(Bucket b: getBuckets()) { 158 | if (++n > top) { 159 | break; 160 | } 161 | sb.append(b.toString()).append('\n'); 162 | } 163 | sb.append(String.format("Total%14d%15d\n", totalInstances(), totalBytes())); 164 | return sb.toString(); 165 | } 166 | 167 | public static class Bucket { 168 | 169 | int num; 170 | String className; 171 | long instances; 172 | long bytes; 173 | 174 | public String toString() { 175 | return String.format("%4d:%14d%15d %s", num, instances, bytes, className); 176 | } 177 | } 178 | 179 | public static class SizeComparator implements Comparator { 180 | 181 | @Override 182 | public int compare(Bucket o1, Bucket o2) { 183 | return Long.signum(o2.bytes - o1.bytes); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/JavaProcessDetails.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | import java.io.OutputStream; 19 | import java.util.Properties; 20 | 21 | import javax.management.MBeanServerConnection; 22 | 23 | /** 24 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 25 | */ 26 | public interface JavaProcessDetails { 27 | 28 | public long getPid(); 29 | 30 | public String getDescription(); 31 | 32 | public JavaProcessId getJavaProcId(); 33 | 34 | public Properties getSystemProperties(); 35 | 36 | public Properties getAgentProperties(); 37 | 38 | public String getVmFlag(String flag); 39 | 40 | public void jcmd(String command, Appendable output); 41 | 42 | public void sendAttachCommand(String command, Object[] args, OutputStream output, long timeoutMS); 43 | 44 | public MBeanServerConnection getMBeans(); 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/JavaProcessId.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | /** 19 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 20 | */ 21 | public class JavaProcessId { 22 | 23 | private final long pid; 24 | private final String description; 25 | 26 | protected JavaProcessId(long pid, String description) { 27 | this.pid = pid; 28 | this.description = description; 29 | } 30 | 31 | public long getPID() { 32 | return pid; 33 | } 34 | 35 | public String getDescription() { 36 | return description == null ? "" : description; 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | final int prime = 31; 42 | int result = 1; 43 | result = prime * result + (int) (pid ^ (pid >>> 32)); 44 | return result; 45 | } 46 | 47 | @Override 48 | public boolean equals(Object obj) { 49 | if (this == obj) 50 | return true; 51 | if (obj == null) 52 | return false; 53 | if (getClass() != obj.getClass()) 54 | return false; 55 | JavaProcessId other = (JavaProcessId) obj; 56 | if (pid != other.pid) 57 | return false; 58 | return true; 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return pid + (description == null ? "" : (" " + description)); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/JavaProcessMatcher.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | import java.io.Serializable; 19 | import java.util.Arrays; 20 | import java.util.Collection; 21 | 22 | /** 23 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 24 | */ 25 | public interface JavaProcessMatcher { 26 | 27 | public boolean evaluate(JavaProcessDetails details); 28 | 29 | public static class Union implements JavaProcessMatcher, Serializable { 30 | 31 | private static final long serialVersionUID = 20121112L; 32 | 33 | private final JavaProcessMatcher[] matchers; 34 | 35 | public Union(JavaProcessMatcher... matchers) { 36 | this.matchers = matchers; 37 | if (matchers.length == 0) { 38 | throw new IllegalArgumentException("Matcher list is empty"); 39 | } 40 | } 41 | 42 | public Union(Collection matchers) { 43 | this(matchers.toArray(new JavaProcessMatcher[0])); 44 | } 45 | 46 | @Override 47 | public boolean evaluate(JavaProcessDetails details) { 48 | for(JavaProcessMatcher matcher: matchers) { 49 | if (matcher.evaluate(details)) { 50 | return true; 51 | } 52 | } 53 | return false; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "UNION" + Arrays.asList(matchers); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/LogStream.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | /** 19 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 20 | */ 21 | abstract class LogStream { 22 | 23 | private static String getCallerName() { 24 | String name = Thread.currentThread().getStackTrace()[3].getClassName(); 25 | return name; 26 | } 27 | 28 | public static LogStream info() { 29 | String name = getCallerName(); 30 | try { 31 | return Slf4JLogger.info(name); 32 | } 33 | catch(NoClassDefFoundError error) { 34 | return SysLogger.info(name); 35 | } 36 | } 37 | 38 | public static LogStream debug() { 39 | String name = getCallerName(); 40 | try { 41 | return Slf4JLogger.debug(name); 42 | } 43 | catch(NoClassDefFoundError error) { 44 | return SysLogger.debug(name); 45 | } 46 | } 47 | 48 | public static LogStream warn() { 49 | String name = getCallerName(); 50 | try { 51 | return Slf4JLogger.warn(name); 52 | } 53 | catch(NoClassDefFoundError error) { 54 | return SysLogger.warn(name); 55 | } 56 | } 57 | 58 | public static LogStream error() { 59 | String name = getCallerName(); 60 | try { 61 | return Slf4JLogger.error(name); 62 | } 63 | catch(NoClassDefFoundError error) { 64 | return SysLogger.error(name); 65 | } 66 | } 67 | 68 | 69 | public abstract void log(String message); 70 | 71 | public abstract void log(String message, Throwable error); 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/PatternJvmMatcher.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | import java.io.Serializable; 19 | import java.util.LinkedHashMap; 20 | import java.util.Map; 21 | import java.util.Properties; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | /** 26 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 27 | */ 28 | public class PatternJvmMatcher implements JavaProcessMatcher, Serializable { 29 | 30 | private static final long serialVersionUID = 20121106L; 31 | 32 | private final Map patterns = new LinkedHashMap(); 33 | 34 | public void matchVmName(String pattern) { 35 | matchProp(":name", pattern); 36 | } 37 | 38 | public void matchProp(String prop, String pattern) { 39 | Pattern p = Pattern.compile(pattern); 40 | patterns.put(prop, p); 41 | } 42 | 43 | public void matchPropExact(String prop, String pattern) { 44 | matchProp(prop, Pattern.quote(pattern)); 45 | } 46 | 47 | @Override 48 | public boolean evaluate(JavaProcessDetails proc) { 49 | if (patterns.containsKey(":name")) { 50 | if (!match(":name", proc.getDescription())) { 51 | return false; 52 | } 53 | } 54 | 55 | Properties props = proc.getSystemProperties(); 56 | if (props == null) { 57 | return false; 58 | } 59 | 60 | for(String prop: patterns.keySet()) { 61 | if (!prop.startsWith(":")) { 62 | if (!match(prop, props.getProperty(prop))) { 63 | return false; 64 | } 65 | } 66 | } 67 | 68 | return true; 69 | } 70 | 71 | private boolean match(String prop, String value) { 72 | if (value == null) { 73 | return false; 74 | } 75 | Matcher matcher = patterns.get(prop).matcher(value); 76 | return matcher.matches(); 77 | } 78 | 79 | 80 | @Override 81 | public String toString() { 82 | return String.format("%s%s", getClass().getSimpleName(), patterns.toString()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/Slf4JLogger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | /** 22 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 23 | */ 24 | class Slf4JLogger { 25 | 26 | public static LogStream error(final String name) { 27 | return new LogStream() { 28 | 29 | Logger logger = LoggerFactory.getLogger(name); 30 | 31 | @Override 32 | public void log(String message) { 33 | logger.error(message); 34 | } 35 | 36 | @Override 37 | public void log(String message, Throwable error) { 38 | logger.error(message, error); 39 | } 40 | }; 41 | } 42 | 43 | public static LogStream warn(final String name) { 44 | return new LogStream() { 45 | 46 | Logger logger = LoggerFactory.getLogger(name); 47 | 48 | @Override 49 | public void log(String message) { 50 | logger.warn(message); 51 | } 52 | 53 | @Override 54 | public void log(String message, Throwable error) { 55 | logger.warn(message, error); 56 | } 57 | }; 58 | } 59 | 60 | public static LogStream info(final String name) { 61 | return new LogStream() { 62 | 63 | Logger logger = LoggerFactory.getLogger(name); 64 | 65 | @Override 66 | public void log(String message) { 67 | logger.info(message); 68 | } 69 | 70 | @Override 71 | public void log(String message, Throwable error) { 72 | logger.info(message, error); 73 | } 74 | }; 75 | } 76 | 77 | public static LogStream debug(final String name) { 78 | return new LogStream() { 79 | 80 | Logger logger = LoggerFactory.getLogger(name); 81 | 82 | @Override 83 | public void log(String message) { 84 | logger.debug(message); 85 | } 86 | 87 | @Override 88 | public void log(String message, Throwable error) { 89 | logger.debug(message, error); 90 | } 91 | }; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/attach/SysLogger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.attach; 17 | 18 | import java.io.PrintStream; 19 | 20 | /** 21 | * Fallback console logging. 22 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 23 | */ 24 | public class SysLogger { 25 | 26 | public static final SysLogStream DEBUG = new SysLogStream(null); 27 | public static final SysLogStream INFO = new SysLogStream(null); 28 | public static final SysLogStream WARN = new SysLogStream(System.err); 29 | public static final SysLogStream ERROR = new SysLogStream(System.err); 30 | 31 | public static class SysLogStream extends LogStream { 32 | 33 | private PrintStream target; 34 | 35 | public SysLogStream(PrintStream target) { 36 | this.target = target; 37 | } 38 | 39 | public void setTarget(PrintStream ps) { 40 | this.target = ps; 41 | } 42 | 43 | @Override 44 | public void log(String message) { 45 | if (target != null) { 46 | target.println(message); 47 | } 48 | } 49 | 50 | @Override 51 | public void log(String message, Throwable error) { 52 | if (target != null) { 53 | if (message.length() > 0) { 54 | target.println(message); 55 | } 56 | error.printStackTrace(target); 57 | } 58 | } 59 | } 60 | 61 | public static LogStream debug(String name) { 62 | return DEBUG; 63 | } 64 | 65 | public static LogStream info(String name) { 66 | return INFO; 67 | } 68 | 69 | public static LogStream warn(String name) { 70 | return WARN; 71 | } 72 | 73 | public static LogStream error(String name) { 74 | return ERROR; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/perfdata/JStatData.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.perfdata; 17 | 18 | import java.io.IOException; 19 | import java.nio.ByteBuffer; 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.LinkedHashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | import sun.management.counter.perf.PerfInstrumentation; 28 | 29 | /** 30 | * Wraps {@link PerfInstrumentation} class. 31 | * Its purpose is to shield warnings and {@link NoClassDefFoundError}s. 32 | * 33 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 34 | */ 35 | @SuppressWarnings("restriction") 36 | public abstract class JStatData { 37 | 38 | public static JStatData connect(long pid) { 39 | try { 40 | return new PerfIntr((int)pid); 41 | } 42 | catch(ThreadDeath e) { 43 | throw e; 44 | } 45 | catch(OutOfMemoryError e) { 46 | throw e; 47 | } 48 | catch(Error e) { 49 | throw new RuntimeException("Cannot perf data for process " + pid + " - " + e.toString()); 50 | } 51 | catch(Exception e) { 52 | throw new RuntimeException("Cannot perf data for process " + pid + " - " + e.toString()); 53 | } 54 | } 55 | 56 | public abstract int getMajorVersion(); 57 | 58 | public abstract int getMinorVersion(); 59 | 60 | public abstract long getModificationTimeStamp(); 61 | 62 | public abstract Map> getAllCounters(); 63 | 64 | public abstract List> findByPattern(String pattern); 65 | 66 | public enum Units { 67 | 68 | INVALID, 69 | NONE, 70 | BYTES, 71 | TICKS, 72 | EVENTS, 73 | STRING, 74 | HERTZ, 75 | } 76 | 77 | public enum Variability { 78 | 79 | INVALID, 80 | CONSTANT, 81 | MONOTONIC, 82 | VARIABLE, 83 | } 84 | 85 | public interface Counter { 86 | 87 | public String getName(); 88 | 89 | public Units getUnits(); 90 | 91 | public Variability getVariability(); 92 | 93 | public T getValue(); 94 | 95 | } 96 | 97 | public interface StringCounter extends Counter { 98 | 99 | public String getString(); 100 | 101 | } 102 | 103 | public interface LongCounter extends Counter { 104 | 105 | public long getLong(); 106 | 107 | } 108 | 109 | public interface TickCounter extends LongCounter { 110 | 111 | public long getTicks(); 112 | 113 | double getTick(); 114 | 115 | public long getNanos(); 116 | 117 | } 118 | 119 | private static PerfInstrumentation attach(int pid) throws IllegalArgumentException, IOException { 120 | try { 121 | ByteBuffer bb = sun.misc.Perf.getPerf().attach(pid, "r"); 122 | PerfInstrumentation instr = new PerfInstrumentation(bb); 123 | return instr; 124 | } 125 | catch(NoClassDefFoundError e) { 126 | // try JDK 9 package 127 | ByteBuffer bb = jdk.internal.perf.Perf.getPerf().attach(pid, "r"); 128 | PerfInstrumentation instr = new PerfInstrumentation(bb); 129 | return instr; 130 | } 131 | } 132 | 133 | private static class PerfIntr extends JStatData { 134 | 135 | private static sun.management.counter.Units U_TICKS = sun.management.counter.Units.TICKS; 136 | 137 | private static Map UNIT_MAP = new HashMap(); 138 | private static Map VARIABILITY_MAP = new HashMap(); 139 | 140 | static { 141 | UNIT_MAP.put(sun.management.counter.Units.INVALID, Units.INVALID); 142 | UNIT_MAP.put(sun.management.counter.Units.NONE, Units.NONE); 143 | UNIT_MAP.put(sun.management.counter.Units.BYTES, Units.BYTES); 144 | UNIT_MAP.put(sun.management.counter.Units.TICKS, Units.TICKS); 145 | UNIT_MAP.put(sun.management.counter.Units.EVENTS, Units.EVENTS); 146 | UNIT_MAP.put(sun.management.counter.Units.STRING, Units.STRING); 147 | UNIT_MAP.put(sun.management.counter.Units.HERTZ, Units.HERTZ); 148 | 149 | VARIABILITY_MAP.put(sun.management.counter.Variability.INVALID, Variability.INVALID); 150 | VARIABILITY_MAP.put(sun.management.counter.Variability.CONSTANT, Variability.CONSTANT); 151 | VARIABILITY_MAP.put(sun.management.counter.Variability.MONOTONIC, Variability.MONOTONIC); 152 | VARIABILITY_MAP.put(sun.management.counter.Variability.VARIABLE, Variability.VARIABLE); 153 | } 154 | 155 | final PerfInstrumentation instr; 156 | final double tick; 157 | 158 | public PerfIntr(int pid) throws IllegalArgumentException, IOException { 159 | instr = attach(pid); 160 | long hz = ((sun.management.counter.LongCounter)instr.findByPattern("sun.os.hrt.frequency").get(0)).longValue(); 161 | tick = ((double)TimeUnit.SECONDS.toNanos(1)) / hz; 162 | } 163 | 164 | @Override 165 | public int getMajorVersion() { 166 | return instr.getMajorVersion(); 167 | } 168 | 169 | @Override 170 | public int getMinorVersion() { 171 | return instr.getMinorVersion(); 172 | } 173 | 174 | @Override 175 | public long getModificationTimeStamp() { 176 | return instr.getModificationTimeStamp(); 177 | } 178 | 179 | @Override 180 | public Map> getAllCounters() { 181 | Map> result = new LinkedHashMap>(); 182 | 183 | for(Object c : instr.getAllCounters()) { 184 | Counter cc = convert(c); 185 | result.put(cc.getName(), cc); 186 | } 187 | 188 | return result; 189 | } 190 | 191 | @Override 192 | public List> findByPattern(String pattern) { 193 | return convert(instr.findByPattern(pattern)); 194 | } 195 | 196 | @SuppressWarnings("rawtypes") 197 | private List> convert(List list) { 198 | List> cl = new ArrayList>(list.size()); 199 | for(Object c: list) { 200 | cl.add(convert(c)); 201 | } 202 | return cl; 203 | } 204 | 205 | @SuppressWarnings("rawtypes") 206 | private Counter convert(Object c) { 207 | if (c instanceof sun.management.counter.LongCounter) { 208 | sun.management.counter.LongCounter lc = (sun.management.counter.LongCounter) c; 209 | if (U_TICKS.equals(lc.getUnits())) { 210 | return new TickWrapper(tick, lc); 211 | } 212 | else { 213 | return new LongWrapper(lc); 214 | } 215 | } 216 | else if (c instanceof sun.management.counter.StringCounter) { 217 | sun.management.counter.StringCounter lc = (sun.management.counter.StringCounter) c; 218 | return new StringWrapper(lc); 219 | } 220 | else if (c instanceof sun.management.counter.LongArrayCounter) { 221 | sun.management.counter.LongArrayCounter lc = (sun.management.counter.LongArrayCounter) c; 222 | return new CounterWrapper(lc); 223 | } 224 | else if (c instanceof sun.management.counter.ByteArrayCounter) { 225 | sun.management.counter.ByteArrayCounter lc = (sun.management.counter.ByteArrayCounter) c; 226 | return new ByteArrayWrapper(lc); 227 | } 228 | else { 229 | return new CounterWrapper((sun.management.counter.Counter)c); 230 | } 231 | } 232 | 233 | private static class CounterWrapper implements Counter { 234 | 235 | protected final sun.management.counter.Counter counter; 236 | 237 | public CounterWrapper(sun.management.counter.Counter counter) { 238 | this.counter = counter; 239 | } 240 | 241 | @Override 242 | public String getName() { 243 | return counter.getName(); 244 | } 245 | 246 | @Override 247 | public Units getUnits() { 248 | Units u = UNIT_MAP.get(counter.getUnits()); 249 | return u == null ? Units.INVALID : u; 250 | } 251 | 252 | @Override 253 | public Variability getVariability() { 254 | Variability v = VARIABILITY_MAP.get(counter.getVariability()); 255 | return v == null ? Variability.INVALID : v; 256 | } 257 | 258 | @Override 259 | @SuppressWarnings("unchecked") 260 | public T getValue() { 261 | return (T) counter.getValue(); 262 | } 263 | 264 | @Override 265 | public String toString() { 266 | return counter.toString().replace((char)0, ' '); 267 | } 268 | } 269 | 270 | private static class ByteArrayWrapper extends CounterWrapper implements Counter { 271 | 272 | public ByteArrayWrapper(sun.management.counter.ByteArrayCounter counter) { 273 | super(counter); 274 | } 275 | } 276 | 277 | private static class StringWrapper extends CounterWrapper implements StringCounter { 278 | 279 | public StringWrapper(sun.management.counter.StringCounter counter) { 280 | super(counter); 281 | } 282 | 283 | @Override 284 | public String getString() { 285 | return trim(((sun.management.counter.StringCounter)counter).stringValue()); 286 | } 287 | 288 | private String trim(String value) { 289 | int n = value.indexOf(0); 290 | if (n >= 0) { 291 | return value.substring(0, n); 292 | } 293 | else { 294 | return value; 295 | } 296 | } 297 | } 298 | 299 | private static class LongWrapper extends CounterWrapper implements LongCounter { 300 | 301 | public LongWrapper(sun.management.counter.LongCounter counter) { 302 | super(counter); 303 | } 304 | 305 | @Override 306 | public long getLong() { 307 | return ((sun.management.counter.LongCounter)counter).longValue(); 308 | } 309 | } 310 | 311 | private static class TickWrapper extends LongWrapper implements TickCounter { 312 | 313 | private final double tick; 314 | 315 | public TickWrapper(double tick, sun.management.counter.LongCounter counter) { 316 | super(counter); 317 | this.tick = tick; 318 | } 319 | 320 | @Override 321 | public double getTick() { 322 | return tick; 323 | } 324 | 325 | @Override 326 | public long getTicks() { 327 | return getLong(); 328 | } 329 | 330 | @Override 331 | public long getNanos() { 332 | return (long)(tick * getLong()); 333 | } 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/threaddump/JvmThreadInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.threaddump; 17 | 18 | import java.lang.Thread.State; 19 | 20 | public class JvmThreadInfo { 21 | 22 | private final String name; 23 | private final boolean isJavaThread; 24 | private final boolean isDaemon; 25 | private final long nativeId; 26 | private final State javaThreadState; 27 | private final String extThreadState; 28 | private StackTraceElement[] javaStackTrace; 29 | 30 | public JvmThreadInfo(String name, boolean isDaemon, boolean isJavaThread, long nativeId, State javaThreadState, String extThreadState) { 31 | this.name = name; 32 | this.isJavaThread = isJavaThread; 33 | this.isDaemon = isDaemon; 34 | this.nativeId = nativeId; 35 | this.javaThreadState = javaThreadState; 36 | this.extThreadState = extThreadState; 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public boolean isJavaThread() { 44 | return isJavaThread; 45 | } 46 | 47 | public boolean isDaemon() { 48 | return isDaemon; 49 | } 50 | 51 | public long getNativeId() { 52 | return nativeId; 53 | } 54 | 55 | public State getJavaThreadState() { 56 | return javaThreadState; 57 | } 58 | 59 | public String getExtThreadState() { 60 | return extThreadState; 61 | } 62 | 63 | public StackTraceElement[] getJavaStackTrace() { 64 | return javaStackTrace; 65 | } 66 | 67 | public void setJavaStackTrace(StackTraceElement[] trace) { 68 | this.javaStackTrace = trace; 69 | } 70 | 71 | public String toString() { 72 | StringBuilder sb = new StringBuilder(); 73 | sb.append("\""); 74 | sb.append(name); 75 | sb.append("\""); 76 | if (isDaemon) { 77 | sb.append(" daemon"); 78 | } 79 | if (nativeId > 0) { 80 | sb.append(" nid=0x").append(Long.toHexString(nativeId)); 81 | } 82 | if (nativeId > 0) { 83 | sb.append(" nid=0x").append(Long.toHexString(nativeId)); 84 | } 85 | if (extThreadState != null) { 86 | sb.append(" ").append(extThreadState); 87 | } 88 | if (isJavaThread) { 89 | if (javaStackTrace != null && javaStackTrace.length > 0) { 90 | sb.append(" java stack (").append(javaStackTrace.length).append(")"); 91 | } 92 | else { 93 | sb.append(" no java stack"); 94 | } 95 | } 96 | else { 97 | sb.append(" non-java thread"); 98 | } 99 | 100 | return sb.toString(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/threaddump/JvmThreadInfoParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.threaddump; 17 | 18 | import java.io.IOException; 19 | import java.lang.Thread.State; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | public class JvmThreadInfoParser implements Appendable { 26 | 27 | private List threads = new ArrayList(); 28 | private StringBuilder line = new StringBuilder(); 29 | private boolean threadPending = false; 30 | private String threadName; 31 | private long nativeThreadId; 32 | private boolean isDaemon; 33 | private String extThreadState; 34 | 35 | private boolean parseTrace = false; 36 | private StackTraceElementParser stackParser; 37 | private boolean expectTrace = false; 38 | private List lastTrace = new ArrayList(); 39 | 40 | private Matcher threadState = Pattern.compile("\\s+ java\\.lang\\.Thread\\.State:\\s+([A-Z_]+)").matcher(""); 41 | private Matcher threadLine = Pattern.compile("\\s+(daemon)?.+nid=0x([0-9a-fA-F]+)\\s+([^\\[]*)(\\[0x([a-fA-F0-9]*)\\])?").matcher(""); 42 | 43 | public JvmThreadInfoParser() { 44 | this(false); 45 | } 46 | 47 | public JvmThreadInfoParser(boolean parseTrace) { 48 | this.parseTrace = parseTrace; 49 | if (parseTrace) { 50 | stackParser = StackTraceElementParser.DEFAULT; 51 | } 52 | } 53 | 54 | public void setStackElementParser(StackTraceElementParser parser) { 55 | stackParser = parser; 56 | } 57 | 58 | @Override 59 | public Appendable append(CharSequence csq) throws IOException { 60 | append(csq, 0, csq.length()); 61 | return this; 62 | } 63 | 64 | @Override 65 | public Appendable append(CharSequence csq, int start, int end) throws IOException { 66 | for(int i = start; i != end; ++i) { 67 | append(csq.charAt(i)); 68 | } 69 | return this; 70 | } 71 | 72 | @Override 73 | public Appendable append(char c) { 74 | if (c == '\n') { 75 | lineComplete(); 76 | } 77 | else { 78 | line.append(c); 79 | } 80 | return this; 81 | } 82 | 83 | private void lineComplete() { 84 | if (line.length() > 0) { 85 | if (line.charAt(line.length() - 1) == '\r') { 86 | line.setLength(line.length() - 1); 87 | } 88 | if (threadPending) { 89 | threadState.reset(line); 90 | if (threadState.lookingAt()) { 91 | String tstate = threadState.group(1); 92 | State state = null; 93 | try { 94 | state = State.valueOf(tstate); 95 | } catch (Exception e) { 96 | // ignore 97 | } 98 | appendJavaThread(state); 99 | } 100 | else { 101 | appendNonJavaThread(); 102 | } 103 | } 104 | else { 105 | if (line.length() > 0) { 106 | if (line.charAt(0) == '"') { 107 | int n = line.lastIndexOf("\""); 108 | threadLine.reset(line); 109 | if (threadLine.find(n + 1)) { 110 | threadName = line.substring(1, n); 111 | isDaemon = threadLine.group(1) != null; 112 | nativeThreadId = 0; 113 | try { 114 | nativeThreadId = Long.parseLong(threadLine.group(2), 16); 115 | } catch (NumberFormatException e) { 116 | // ignore 117 | } 118 | extThreadState = threadLine.group(3); 119 | if (extThreadState != null) { 120 | extThreadState = extThreadState.trim(); 121 | } 122 | completeStackTrace(); 123 | threadPending = true; 124 | expectTrace = true; 125 | } 126 | } 127 | } 128 | } 129 | if (parseTrace && expectTrace) { 130 | int n = 0; 131 | for(; n < line.length(); ++n) { 132 | if (!Character.isWhitespace(line.charAt(n))) { 133 | break; 134 | } 135 | } 136 | if (n < line.length() + 3) { 137 | if ((line.charAt(n) == 'a') && (line.charAt(n + 1) == 't') && (line.charAt(n + 2) == ' ')) { 138 | n += 3; 139 | StackTraceElement ste = stackParser.paser(line.substring(n)); 140 | if (ste != null) { 141 | lastTrace.add(ste); 142 | } 143 | } 144 | } 145 | } 146 | } 147 | else { 148 | // Empty line 149 | if (threadPending) { 150 | appendNonJavaThread(); 151 | } 152 | 153 | completeStackTrace(); 154 | } 155 | line.setLength(0); 156 | } 157 | 158 | private void completeStackTrace() { 159 | if (parseTrace) { 160 | if (!lastTrace.isEmpty() && !threads.isEmpty()) { 161 | StackTraceElement[] trace = lastTrace.toArray(new StackTraceElement[lastTrace.size()]); 162 | threads.get(threads.size() - 1).setJavaStackTrace(trace); 163 | } 164 | lastTrace.clear(); 165 | } 166 | expectTrace = false; 167 | } 168 | 169 | private void appendJavaThread(State state) { 170 | threads.add(new JvmThreadInfo(threadName, isDaemon, true, nativeThreadId, state, extThreadState)); 171 | threadPending = false; 172 | } 173 | 174 | private void appendNonJavaThread() { 175 | threads.add(new JvmThreadInfo(threadName, isDaemon, false, nativeThreadId, null, extThreadState)); 176 | threadPending = false; 177 | } 178 | 179 | public JvmThreadInfo[] getThreads() { 180 | append('\n'); 181 | if (threadPending) { 182 | appendNonJavaThread(); 183 | } 184 | return threads.toArray(new JvmThreadInfo[threads.size()]); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/threaddump/SimpleStackParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.threaddump; 17 | 18 | public class SimpleStackParser implements StackTraceElementParser { 19 | 20 | protected static final String NATIVE_METHOD = "Native Method"; 21 | protected static final String UNKNOWN_SOURCE = "Unknown Source"; 22 | 23 | protected static final int NO_LINE_NUMBER = 0; 24 | protected static final int NO_SOURCE = -1; 25 | protected static final int NATIVE = -2; 26 | 27 | private final boolean suppress; 28 | 29 | public SimpleStackParser(boolean suppressErrors) { 30 | this.suppress = suppressErrors; 31 | } 32 | 33 | @Override 34 | public StackTraceElement paser(CharSequence line) { 35 | StringBuilder sb = new StringBuilder(line.length()); 36 | int dot1 = -1; 37 | int dot2 = -1; 38 | int n = 0; 39 | while(true) { 40 | char ch = line.charAt(n); 41 | if (ch == '(') { 42 | break; 43 | } 44 | if (ch == '.') { 45 | dot2 = dot1; 46 | dot1 = n; 47 | } 48 | sb.append(ch); 49 | ++n; 50 | if (n >= line.length()) { 51 | if (suppress) { 52 | return null; 53 | } 54 | else { 55 | throw new IllegalArgumentException("Cannot parse [" + line + "]"); 56 | } 57 | } 58 | } 59 | if (dot1 == -1) { 60 | if (suppress) { 61 | return null; 62 | } 63 | else { 64 | throw new IllegalArgumentException("Cannot parse [" + line + "]"); 65 | } 66 | } 67 | String pref = null; 68 | String cn = null; 69 | String mn = null; 70 | if (dot2 != -1) { 71 | pref = sb.substring(0, dot2); 72 | cn = sb.substring(dot2 + 1, dot1); 73 | mn = sb.substring(dot1 + 1); 74 | } 75 | else { 76 | cn = sb.substring(0, dot1); 77 | mn = sb.substring(dot1 + 1); 78 | } 79 | sb.setLength(0); 80 | int col = -1; 81 | ++n; 82 | int off = n; 83 | while(true) { 84 | char ch = line.charAt(n); 85 | if (ch == ')') { 86 | break; 87 | } 88 | if (ch == ':') { 89 | col = n - off; 90 | } 91 | sb.append(ch); 92 | ++n; 93 | if (n >= line.length()) { 94 | if (suppress) { 95 | return null; 96 | } 97 | else { 98 | throw new IllegalArgumentException("Cannot parse [" + line + "]"); 99 | } 100 | } 101 | } 102 | String file = null; 103 | int lnum = -1; 104 | if (col != -1) { 105 | file = sb.substring(0, col); 106 | try { 107 | lnum = Integer.parseInt(sb.substring(col + 1)); 108 | } 109 | catch(NumberFormatException e) { 110 | if (suppress) { 111 | return null; 112 | } 113 | else { 114 | throw new IllegalArgumentException("Number format exception '" + e.getMessage() + "' parsing [" + line + "]"); 115 | } 116 | } 117 | } 118 | else { 119 | file = sb.toString(); 120 | if (file.equals(NATIVE_METHOD)) { 121 | file = null; 122 | lnum = -2; 123 | } 124 | else if (file.equals(UNKNOWN_SOURCE)) { 125 | file = null; 126 | } 127 | 128 | } 129 | return new StackTraceElement(pref + "." + cn, mn, file, lnum); 130 | } 131 | } -------------------------------------------------------------------------------- /src/main/java/org/gridkit/lab/jvm/threaddump/StackTraceElementParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Alexey Ragozin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gridkit.lab.jvm.threaddump; 17 | 18 | public interface StackTraceElementParser { 19 | 20 | public static StackTraceElementParser DEFAULT = new SimpleStackParser(true); 21 | 22 | public StackTraceElement paser(CharSequence line); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/sun/misc/Perf.java: -------------------------------------------------------------------------------- 1 | package sun.misc; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | 6 | /** 7 | * Stub class to support pre and post Java 9 runtimes. 8 | * This class should never be loaded at runtime. 9 | * 10 | * @author Alexey Ragozin (alexey.ragozin@gmail.com) 11 | */ 12 | public final class Perf { 13 | 14 | public static Perf getPerf() { 15 | throw new UnsupportedOperationException("Stub class"); 16 | } 17 | 18 | public ByteBuffer attach(int paramInt, String paramString) throws IllegalArgumentException, IOException { 19 | throw new UnsupportedOperationException("Stub class"); 20 | } 21 | 22 | public ByteBuffer attach(String paramString1, int paramInt, String paramString2) 23 | throws IllegalArgumentException, IOException { 24 | throw new UnsupportedOperationException("Stub class"); 25 | } 26 | 27 | public ByteBuffer createLong(String paramString, int paramInt1, int paramInt2, long paramLong) { 28 | throw new UnsupportedOperationException("Stub class"); 29 | } 30 | 31 | public ByteBuffer createString(String paramString1, int paramInt1, int paramInt2, String paramString2, int paramInt3) { 32 | throw new UnsupportedOperationException("Stub class"); 33 | } 34 | 35 | public ByteBuffer createString(String paramString1, int paramInt1, int paramInt2, String paramString2) { 36 | throw new UnsupportedOperationException("Stub class"); 37 | } 38 | 39 | public ByteBuffer createByteArray(String paramString, int paramInt1, int paramInt2, byte[] paramArrayOfByte, int paramInt3) { 40 | throw new UnsupportedOperationException("Stub class"); 41 | } 42 | 43 | public long highResCounter() { 44 | throw new UnsupportedOperationException("Stub class"); 45 | } 46 | 47 | public long highResFrequency() { 48 | throw new UnsupportedOperationException("Stub class"); 49 | } 50 | } -------------------------------------------------------------------------------- /src/test/java/org/gridkit/lab/jvm/attach/AttachCheck.java: -------------------------------------------------------------------------------- 1 | package org.gridkit.lab.jvm.attach; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.lang.management.ManagementFactory; 5 | import java.lang.management.RuntimeMXBean; 6 | 7 | import javax.management.JMX; 8 | import javax.management.MalformedObjectNameException; 9 | import javax.management.ObjectName; 10 | 11 | import org.junit.Test; 12 | 13 | 14 | public class AttachCheck { 15 | 16 | @Test 17 | public void match_self_vm() { 18 | 19 | System.getProperties().put("test", ""); 20 | 21 | PatternJvmMatcher matcher = new PatternJvmMatcher(); 22 | matcher.matchProp("test", ".*"); 23 | 24 | for(JavaProcessId jpid: AttachManager.listJavaProcesses(matcher)) { 25 | System.out.println(jpid); 26 | } 27 | } 28 | 29 | @Test 30 | public void self_jmx() throws MalformedObjectNameException, NullPointerException { 31 | 32 | System.getProperties().put("test", ""); 33 | 34 | PatternJvmMatcher matcher = new PatternJvmMatcher(); 35 | matcher.matchProp("test", ".*"); 36 | 37 | for(JavaProcessId jpid: AttachManager.listJavaProcesses(matcher)) { 38 | System.out.println(jpid); 39 | RuntimeMXBean runtime = JMX.newMXBeanProxy(AttachManager.getJmxConnection(jpid), ObjectName.getInstance(ManagementFactory.RUNTIME_MXBEAN_NAME), RuntimeMXBean.class); 40 | System.out.println(runtime.getName()); 41 | } 42 | } 43 | 44 | @Test 45 | public void self_command_exec() throws MalformedObjectNameException, NullPointerException { 46 | 47 | System.getProperties().put("test", ""); 48 | 49 | PatternJvmMatcher matcher = new PatternJvmMatcher(); 50 | matcher.matchProp("test", ".*"); 51 | 52 | for(JavaProcessId jpid: AttachManager.listJavaProcesses(matcher)) { 53 | System.out.println(jpid); 54 | JavaProcessDetails jpd = AttachManager.getDetails(jpid); 55 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 56 | jpd.sendAttachCommand("jcmd", new Object[] {"help"}, bos, 5000); 57 | System.out.println(new String(bos.toByteArray())); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/org/gridkit/lab/jvm/attach/AttachListCheck.java: -------------------------------------------------------------------------------- 1 | package org.gridkit.lab.jvm.attach; 2 | 3 | import org.junit.Test; 4 | 5 | 6 | public class AttachListCheck { 7 | 8 | @Test 9 | public void list_vm() { 10 | 11 | for(JavaProcessId jpid: AttachManager.listJavaProcesses()) { 12 | System.out.println(jpid); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/org/gridkit/lab/jvm/attach/ClassHistoCheck.java: -------------------------------------------------------------------------------- 1 | package org.gridkit.lab.jvm.attach; 2 | 3 | import java.lang.management.ManagementFactory; 4 | import java.util.List; 5 | 6 | import org.junit.Test; 7 | 8 | public class ClassHistoCheck { 9 | 10 | private int pid() { 11 | String name = ManagementFactory.getRuntimeMXBean().getName(); 12 | name = name.substring(0, name.indexOf("@")); 13 | return Integer.parseInt(name); 14 | } 15 | 16 | @Test 17 | public void test_heap_histo() throws Exception { 18 | String[] args = {}; 19 | List histo = AttachManager.getHeapHisto(pid(), args, 10000); 20 | for(String line: histo) { 21 | System.out.println(line); 22 | } 23 | } 24 | 25 | @Test 26 | public void test_heap_histo_live() throws Exception { 27 | String[] args = {"-live"}; 28 | List histo = AttachManager.getHeapHisto(pid(), args, 10000); 29 | for(String line: histo) { 30 | System.out.println(line); 31 | } 32 | } 33 | 34 | @Test 35 | public void test_heap_histo_object() throws Exception { 36 | String[] args = {"-live"}; 37 | List histo = AttachManager.getHeapHisto(pid(), args, 10000); 38 | for(String line: histo) { 39 | System.out.println(line); 40 | } 41 | HeapHisto hh = HeapHisto.parse(histo); 42 | System.out.println(); 43 | System.out.println(hh.print()); 44 | } 45 | 46 | @Test 47 | public void test_heap_histo_new() throws Exception { 48 | String[] all = {"-all"}; 49 | String[] live = {"-live"}; 50 | List hall = AttachManager.getHeapHisto(pid(), all, 10000); 51 | List hlive = AttachManager.getHeapHisto(pid(), live, 10000); 52 | 53 | HeapHisto hhall = HeapHisto.parse(hall); 54 | HeapHisto hhlive = HeapHisto.parse(hlive); 55 | 56 | System.out.println("Young garbage:"); 57 | System.out.println(HeapHisto.subtract(hhall, hhlive).print(20)); 58 | 59 | System.out.println("Old objects:"); 60 | System.out.println(hhall.print(20)); 61 | } 62 | 63 | @Test 64 | public void test_heap_histo_dead() throws Exception { 65 | HeapHisto dead = HeapHisto.getHistoDead(pid(), 30000); 66 | System.out.println(dead.print()); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/org/gridkit/lab/jvm/attach/HeapDumpCheck.java: -------------------------------------------------------------------------------- 1 | package org.gridkit.lab.jvm.attach; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.lang.management.ManagementFactory; 6 | 7 | import org.junit.Test; 8 | 9 | public class HeapDumpCheck { 10 | 11 | private int pid() { 12 | String name = ManagementFactory.getRuntimeMXBean().getName(); 13 | name = name.substring(0, name.indexOf("@")); 14 | return Integer.parseInt(name); 15 | } 16 | 17 | @Test 18 | public void checkAllDump() throws IOException { 19 | System.out.println(HeapDumper.dumpAll(pid(), "target/all-dump.hprof", 60000)); 20 | System.out.println("Dump size: " + (new File("target/all-dump.hprof").length() >> 10) + "k"); 21 | } 22 | 23 | @Test 24 | public void checkLiveDump() throws IOException { 25 | System.out.println(HeapDumper.dumpLive(pid(), "target/live-dump.hprof", 60000)); 26 | System.out.println("Dump size: " + (new File("target/live-dump.hprof").length() >> 10) + "k"); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/org/gridkit/lab/jvm/perfdata/TestPerfData.java: -------------------------------------------------------------------------------- 1 | package org.gridkit.lab.jvm.perfdata; 2 | 3 | import java.lang.management.ManagementFactory; 4 | 5 | import org.gridkit.lab.jvm.perfdata.JStatData.StringCounter; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | public class TestPerfData { 10 | 11 | private int pid() { 12 | String name = ManagementFactory.getRuntimeMXBean().getName(); 13 | name = name.substring(0, name.indexOf("@")); 14 | return Integer.parseInt(name); 15 | } 16 | 17 | @Test 18 | public void verify_self_attach() { 19 | JStatData data = JStatData.connect(pid()); 20 | for(JStatData.Counter c: data.getAllCounters().values()) { 21 | Assert.assertNotNull(c.getName()); 22 | Assert.assertNotNull(c.getUnits()); 23 | Assert.assertNotNull(c.getVariability()); 24 | Assert.assertNotNull(c.getValue()); 25 | System.out.println(c); 26 | if (c instanceof StringCounter) { 27 | String val = (String) c.getValue(); 28 | Assert.assertTrue(val.indexOf(0) < 0); 29 | } 30 | } 31 | } 32 | 33 | @Test 34 | public void verify_self_attach_reentrancy() { 35 | JStatData data = JStatData.connect(pid()); 36 | for(JStatData.Counter c: data.getAllCounters().values()) { 37 | Assert.assertNotNull(c.getName()); 38 | Assert.assertNotNull(c.getUnits()); 39 | Assert.assertNotNull(c.getVariability()); 40 | Assert.assertNotNull(c.getValue()); 41 | if (c instanceof StringCounter) { 42 | String val = (String) c.getValue(); 43 | Assert.assertTrue(val.indexOf(0) < 0); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/gridkit/lab/jvm/threaddump/ThreadDumpCheck.java: -------------------------------------------------------------------------------- 1 | package org.gridkit.lab.jvm.threaddump; 2 | 3 | import java.lang.management.ManagementFactory; 4 | 5 | import org.gridkit.lab.jvm.attach.AttachManager; 6 | import org.junit.Test; 7 | 8 | public class ThreadDumpCheck { 9 | 10 | private int pid() { 11 | String name = ManagementFactory.getRuntimeMXBean().getName(); 12 | name = name.substring(0, name.indexOf("@")); 13 | return Integer.parseInt(name); 14 | } 15 | 16 | @Test 17 | public void test_thread_dump() throws Exception { 18 | String[] args = {}; 19 | StringBuilder sb = new StringBuilder(); 20 | AttachManager.getThreadDump(pid(), args, sb, 30000); 21 | System.out.println(sb); 22 | } 23 | 24 | @Test 25 | public void test_parsed_thread_dump() throws Exception { 26 | spawnBusyThread("Blocker-1"); 27 | spawnBusyThread("Blocker-2"); 28 | spawnBusyThread("Blocker-3"); 29 | spawnBusyThread("Blocker-4"); 30 | 31 | String[] args = {}; 32 | StringBuilder sb = new StringBuilder(); 33 | AttachManager.getThreadDump(pid(), args, sb, 30000); 34 | System.out.println(sb); 35 | 36 | System.out.println("\nParsed dump\n"); 37 | 38 | JvmThreadInfoParser tp = new JvmThreadInfoParser(true); 39 | AttachManager.getThreadDump(pid(), args, tp, 30000); 40 | for(JvmThreadInfo ti: tp.getThreads()) { 41 | System.out.println(ti); 42 | if (ti.getJavaStackTrace() != null) { 43 | for(StackTraceElement e: ti.getJavaStackTrace()) { 44 | System.out.println(" " + e); 45 | } 46 | } 47 | System.out.println(); 48 | } 49 | } 50 | 51 | @Test 52 | public void test_thread_enumerator() throws Exception { 53 | String[] args = {}; 54 | JvmThreadInfoParser tp = new JvmThreadInfoParser(); 55 | AttachManager.getThreadDump(pid(), args, tp, 30000); 56 | for(JvmThreadInfo ti: tp.getThreads()) { 57 | System.out.print("[" + ti.getNativeId() + "] "); 58 | System.out.println(ti); 59 | } 60 | } 61 | 62 | public synchronized void busyCall() { 63 | try { 64 | Thread.sleep(1); 65 | } catch (InterruptedException e) { 66 | } 67 | } 68 | 69 | public void spawnBusyThread(String name) { 70 | Thread t = new Thread(name) { 71 | @Override 72 | public void run() { 73 | while(true) { 74 | busyCall(); 75 | } 76 | } 77 | }; 78 | t.setDaemon(true); 79 | t.start(); 80 | } 81 | } 82 | --------------------------------------------------------------------------------