├── .gitignore ├── LICENCE.txt ├── README.md ├── agent ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── type │ └── pollution │ └── agent │ ├── Agent.java │ ├── AppendOnlyList.java │ ├── ByteBuddyUtils.java │ └── TraceInstanceOf.java ├── benchmarks ├── .gitignore ├── WHATIF.md ├── images │ └── async │ │ ├── async-profiler-lines.html │ │ ├── async-profiler-lines.png │ │ ├── async-profiler.html │ │ └── async-profiler.png ├── pom.xml ├── reports │ ├── async-profiler.jfr │ ├── hotspot.log │ └── perfasm.txt ├── src │ └── main │ │ └── java │ │ └── io │ │ └── type │ │ └── pollution │ │ └── benchmarks │ │ ├── Context.java │ │ ├── ContextUtil.java │ │ ├── DuplicatedContext.java │ │ ├── InternalContext.java │ │ ├── Main.java │ │ ├── NonDuplicatedContext.java │ │ └── RequireNonNullCheckcastScalability.java └── tools │ └── perfasm.jar ├── example ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── type │ └── pollution │ └── example │ └── Main.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /agent/target/ 3 | /example/target/ 4 | /.idea/ 5 | /agent/type-pollution-agent.iml 6 | /example/type-pollution-example.iml 7 | /type-pollution-parent.iml 8 | -------------------------------------------------------------------------------- /LICENCE.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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java agent to diagnose [JDK-8180450](https://bugs.openjdk.org/browse/JDK-8180450) 2 | 3 | ## What is JDK-8180450? 4 | 5 | See [the issue](https://bugs.openjdk.org/browse/JDK-8180450) for full details... 6 | But briefly, the problem is that the OpenJDK JVM caches the supertype of a class/interface on the JRE's `Klass` structure when evaluating things like `instanceof` or checking type casts and while that usually results in a performance win (because a slower traversal of the whole hierarchy can be avoided), in certain circumstances involving multiple inheritance of interfaces the cache is worse than useless. 7 | CPU will be expended updating the cache, but the cached value won't help avoiding the slow path. 8 | It's a problem because the surprising and significant difference in performance between the ideal and worst case behaviours means programmers can easily write poorly performing code without realising the costs (_usually_ `instanceof` is very cheap, for example). 9 | 10 | ## What is this? 11 | 12 | It's a Java agent which can be used to identify code which may be suffering from this problem. 13 | It attempts to count the number of times an a particular concrete class is used in `instanceof` (and similar) expressions with a different test type. 14 | For example at one point in the code you might have `foo instanceof I1` and somewhere else `bar instanceof I2` (where `foo` and `bar` have the same concrete type). 15 | The agent will estimate the number of times the cached supertype (`I1` and `I2`) is changing, i.e. how often you're hitting the slower path. 16 | 17 | ## Build 18 | 19 | ``` 20 | $ # From the root dir 21 | $ mvn package 22 | ``` 23 | 24 | ## Run 25 | 26 | ``` 27 | $ # From the root dir 28 | $ java -javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar -jar example/target/type-pollution-example-0.1-SNAPSHOT.jar 29 | ``` 30 | The output, with a default agent configuration, is: 31 | ```bash 32 | -------------------------- 33 | Type Pollution Statistics: 34 | -------------------------- 35 | 1: io.type.pollution.example.B 36 | Count: 6477753 37 | Types: 38 | io.type.pollution.example.I2 39 | io.type.pollution.example.I3 40 | io.type.pollution.example.I1 41 | Traces: 42 | io.type.pollution.example.Main.goo(Main.java:74) 43 | class: io.type.pollution.example.I3 44 | count: 1826427 45 | class: io.type.pollution.example.I2 46 | count: 1430748 47 | io.type.pollution.example.Main.foo(Main.java:69) 48 | class: io.type.pollution.example.I1 49 | count: 1803371 50 | io.type.pollution.example.Main$$Lambda$ByteBuddy$4.accept(Unknown Source) 51 | class: io.type.pollution.example.I2 52 | count: 1417207 53 | -------------------------- 54 | 2: io.type.pollution.example.C 55 | Count: 4804011 56 | Types: 57 | io.type.pollution.example.I2 58 | io.type.pollution.example.I1 59 | Traces: 60 | io.type.pollution.example.Main.castToI1(Main.java:85) 61 | class: io.type.pollution.example.I1 62 | count: 2400934 63 | io.type.pollution.example.Main.castToI2(Main.java:89) 64 | class: io.type.pollution.example.I2 65 | count: 1848168 66 | io.type.pollution.example.Main$$Lambda$ByteBuddy$6.accept(Unknown Source) 67 | class: io.type.pollution.example.I2 68 | count: 554909 69 | -------------------------- 70 | ``` 71 | - **Count**: is the number of observed successful `checkcast`/`instanceof`/`Class::isAssignableFrom`/`Class::cast`/`Class::isInstance` 72 | against a different (interface) type from the last seen 73 | - **Types**: is the list of different (interface) types observed 74 | - **Traces**: is a list of top method stack traces along with the interface *class* seen and related *count* (ie observed 75 | secondary super cache invalidations). 76 | 77 | **note** `Traces` format is compatible with 78 | [Idea IntelliJ Stack Trace Viewer](https://www.jetbrains.com/help/idea/analyzing-external-stacktraces.html) 79 | 80 | The report is an ordered list which types are ordered by increasing `Count` ie 81 | `io.type.pollution.example.B` is the top type based on it and more likely the one with the highest chance 82 | to cause scalability issues. 83 | 84 | ### But what about OpenJDK JIT optimizations that could save any check to be performed? 85 | 86 | Due to the complexities of JIT mechanism (Inlining heuristic, method and bytecode Type Profiling, etc etc) 87 | the agent cannot detect if a potential treat translates into a real (scalability) performance issue: 88 | the smart and wise user should create system/[JMH](https://github.com/openjdk/jmh) benchmarks to analyze the 89 | results of changes and narrow the scope of the agent to ignore the innocent traces. 90 | 91 | To narrow the scope of the tracing, users can add a comma separated list of **package prefixes** (not full qualified class names!!!) 92 | to the agent configuration and the agent will place tracing around the usual suspects byte-code instructions just on these: 93 | 94 | eg 95 | ``` 96 | $ java -javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar=io.other -jar example/target/type-pollution-example-0.1-SNAPSHOT.jar 97 | 98 | # IT WOULD PRINT...NOTHING! No packages starting with io.other exists :P 99 | ``` 100 | 101 | Said that, cleaning up every `instanceof/checkcast` misuses is the best way to get rid 102 | of any potential scalability issue, without relying on how JIT optimizes specific tests. 103 | 104 | ### Full stack traces are available? 105 | 106 | Yes! 107 | To enable full-stack trace sampling **for each** type check execution, use: 108 | > **WARNING**: this can have a large performance impact!! 109 | ``` 110 | -Dio.type.pollution.full.traces=true 111 | ``` 112 | 113 | And, the output will become: 114 | ```bash 115 | -------------------------- 116 | Type Pollution Statistics: 117 | -------------------------- 118 | 1: io.type.pollution.example.B 119 | Count: 6477753 120 | Types: 121 | io.type.pollution.example.I2 122 | io.type.pollution.example.I3 123 | io.type.pollution.example.I1 124 | Traces: 125 | io.type.pollution.example.Main.goo(Main.java:74) 126 | class: io.type.pollution.example.I3 127 | count: 1826427 128 | class: io.type.pollution.example.I2 129 | count: 1430748 130 | io.type.pollution.example.Main.foo(Main.java:69) 131 | class: io.type.pollution.example.I1 132 | count: 1803371 133 | io.type.pollution.example.Main$$Lambda$ByteBuddy$4.accept(Unknown Source) 134 | class: io.type.pollution.example.I2 135 | count: 1417207 136 | Full Traces: 137 | -------------------------- 138 | io.type.pollution.example.Main.goo(Main.java:74) 139 | io.type.pollution.example.Main.lambda$main$0(Main.java:57) 140 | io.type.pollution.example.Main$$Lambda$ByteBuddy$1/0x00000008001bf840.run(Unknown Source) 141 | java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) 142 | java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) 143 | java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) 144 | java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) 145 | java.base/java.lang.Thread.run(Thread.java:829) 146 | -------------------------- 147 | io.type.pollution.example.Main$$Lambda$ByteBuddy$4/0x00000008001be440.accept(Unknown Source) 148 | io.type.pollution.example.Main.consumeAsI2(Main.java:81) 149 | io.type.pollution.example.Main.lambda$main$0(Main.java:58) 150 | io.type.pollution.example.Main$$Lambda$ByteBuddy$1/0x00000008001bf840.run(Unknown Source) 151 | java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) 152 | java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) 153 | java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) 154 | java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) 155 | java.base/java.lang.Thread.run(Thread.java:829) 156 | -------------------------- 157 | io.type.pollution.example.Main.foo(Main.java:69) 158 | io.type.pollution.example.Main.lambda$main$0(Main.java:56) 159 | io.type.pollution.example.Main$$Lambda$ByteBuddy$1/0x00000008001bf840.run(Unknown Source) 160 | java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) 161 | java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) 162 | java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) 163 | java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) 164 | java.base/java.lang.Thread.run(Thread.java:829) 165 | -------------------------- 166 | # Rest omitted for brevity.... 167 | ``` 168 | To reduce the performance impact due to collecting full stack traces for each execution, consider adding too 169 | ``` 170 | -Dio.type.pollution.full.traces.ms=10 171 | ``` 172 | that's going to perform stack sampling with a global tick time of 10 milliseconds: tune it to match your perf requirement 173 | and your type of load. 174 | 175 | -------------------------------------------------------------------------------- /agent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.forked.franz 8 | type-pollution-parent 9 | 0.1-SNAPSHOT 10 | 11 | 12 | type-pollution-agent 13 | 0.1-SNAPSHOT 14 | jar 15 | 16 | 17 | 18 | net.bytebuddy 19 | byte-buddy 20 | 1.12.13 21 | 22 | 23 | 24 | 25 | 26 | 27 | maven-jar-plugin 28 | 3.2.2 29 | 30 | 31 | 32 | io.type.pollution.agent.Agent 33 | true 34 | 35 | 36 | 37 | 38 | 39 | 40 | maven-shade-plugin 41 | 3.3.0 42 | 43 | 44 | package 45 | 46 | shade 47 | 48 | 49 | 50 | 51 | net.bytebuddy 52 | agent.net.bytebuddy 53 | 54 | 55 | false 56 | 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-compiler-plugin 63 | 64 | 9 65 | 9 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /agent/src/main/java/io/type/pollution/agent/Agent.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.agent; 2 | 3 | import net.bytebuddy.agent.builder.AgentBuilder; 4 | import net.bytebuddy.asm.AsmVisitorWrapper; 5 | import net.bytebuddy.asm.TypeConstantAdjustment; 6 | import net.bytebuddy.description.field.FieldDescription; 7 | import net.bytebuddy.description.field.FieldList; 8 | import net.bytebuddy.description.method.MethodList; 9 | import net.bytebuddy.description.type.TypeDescription; 10 | import net.bytebuddy.implementation.Implementation; 11 | import net.bytebuddy.jar.asm.ClassWriter; 12 | import net.bytebuddy.matcher.ElementMatcher; 13 | import net.bytebuddy.pool.TypePool; 14 | 15 | import java.io.IOException; 16 | import java.lang.instrument.Instrumentation; 17 | import java.nio.ByteBuffer; 18 | import java.nio.channels.FileChannel; 19 | import java.nio.file.Paths; 20 | import java.nio.file.StandardOpenOption; 21 | import java.time.LocalDateTime; 22 | import java.time.format.DateTimeFormatter; 23 | import java.util.Collection; 24 | import java.util.concurrent.Executors; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | import static net.bytebuddy.matcher.ElementMatchers.*; 28 | 29 | public class Agent { 30 | 31 | private static final boolean ENABLE_FULL_STACK_TRACES = Boolean.getBoolean("io.type.pollution.full.traces"); 32 | 33 | private static final String FILE_DUMP = System.getProperty("io.type.pollution.file"); 34 | 35 | private static final int FULL_STACK_TRACES_SAMPLING_PERIOD_MS = Integer.getInteger("io.type.pollution.full.traces.ms", 0); 36 | static final int FULL_STACK_TRACES_LIMIT = Integer.getInteger("io.type.pollution.full.traces.limit", 20); 37 | private static final int TYPE_UPDATE_COUNT_MIN = Integer.getInteger("io.type.pollution.count.min", 10); 38 | private static final int TYPE_MISS_COUNT_MIN = Integer.getInteger("io.type.pollution.miss.count.min", 1); 39 | private static final int TRACING_DELAY_SECS = Integer.getInteger("io.type.pollution.delay", 0); 40 | private static final Long REPORT_INTERVAL_SECS = Long.getLong("io.type.pollution.report.interval"); 41 | private static final boolean ENABLE_LAMBDA_INSTRUMENTATION = Boolean.getBoolean("io.type.pollution.lambda"); 42 | 43 | public static void premain(String agentArgs, Instrumentation inst) { 44 | if (ENABLE_FULL_STACK_TRACES) { 45 | TraceInstanceOf.startMetronome(FULL_STACK_TRACES_SAMPLING_PERIOD_MS); 46 | } 47 | TraceInstanceOf.startTracing(TRACING_DELAY_SECS); 48 | 49 | if (REPORT_INTERVAL_SECS != null) { 50 | Executors.newSingleThreadScheduledExecutor(r -> { 51 | Thread t = new Thread(r); 52 | t.setDaemon(true); 53 | t.setName("type-pollution-periodic-report"); 54 | return t; 55 | }).scheduleWithFixedDelay(Agent::printLiveReport, TRACING_DELAY_SECS + REPORT_INTERVAL_SECS, REPORT_INTERVAL_SECS, TimeUnit.SECONDS); 56 | } 57 | 58 | Runtime.getRuntime().addShutdownHook(new Thread(Agent::printFinalReport)); 59 | 60 | final String[] agentArgsValues = agentArgs == null ? null : agentArgs.split(","); 61 | ElementMatcher.Junction acceptedTypes = any(); 62 | if (agentArgsValues != null && agentArgsValues.length > 0) { 63 | for (String startWith : agentArgsValues) 64 | acceptedTypes = acceptedTypes.and(nameStartsWith(startWith)); 65 | } 66 | new AgentBuilder.Default() 67 | .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()) 68 | .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) 69 | .with(ENABLE_LAMBDA_INSTRUMENTATION ? 70 | AgentBuilder.LambdaInstrumentationStrategy.ENABLED : 71 | AgentBuilder.LambdaInstrumentationStrategy.DISABLED) 72 | .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) 73 | .type(acceptedTypes 74 | .and(not(nameStartsWith("net.bytebuddy."))) 75 | .and(not(nameStartsWith("com.sun"))) 76 | .and(not(nameStartsWith("io.type.pollution.agent")))) 77 | .transform((builder, 78 | typeDescription, 79 | classLoader, 80 | module, 81 | protectionDomain) -> builder 82 | .visit(TypeConstantAdjustment.INSTANCE) 83 | .visit(new AsmVisitorWrapper.AbstractBase() { 84 | 85 | @Override 86 | public int mergeWriter(int flags) { 87 | return flags | ClassWriter.COMPUTE_FRAMES; 88 | } 89 | 90 | @Override 91 | public int mergeReader(int flags) { 92 | return flags; 93 | } 94 | 95 | @Override 96 | public net.bytebuddy.jar.asm.ClassVisitor wrap(TypeDescription instrumentedType, 97 | net.bytebuddy.jar.asm.ClassVisitor classVisitor, 98 | Implementation.Context implementationContext, 99 | TypePool typePool, 100 | FieldList fields, 101 | MethodList methods, 102 | int writerFlags, int readerFlags) { 103 | return new ByteBuddyUtils.ByteBuddyTypePollutionClassVisitor(net.bytebuddy.jar.asm.Opcodes.ASM9, classVisitor); 104 | } 105 | })).installOn(inst); 106 | } 107 | 108 | private static void printFinalReport() { 109 | printReport(true); 110 | } 111 | 112 | private static void printLiveReport() { 113 | printReport(false); 114 | } 115 | 116 | private static final DateTimeFormatter REPORT_TIMESTAMP = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); 117 | private static FileChannel DUMP; 118 | private static ByteBuffer DUMP_TMP_BUFFER; 119 | private static boolean DUMP_ERROR; 120 | 121 | private static boolean LAST_REPORT = false; 122 | 123 | private static CharSequence reportOf(Collection counters) { 124 | if (counters.isEmpty()) { 125 | return ""; 126 | } 127 | StringBuilder report = new StringBuilder(); 128 | int rowId = 0; 129 | for (TraceInstanceOf.TraceCounter.Snapshot counter : counters) { 130 | report.append("--------------------------\n"); 131 | rowId++; 132 | report.append(rowId).append(":\t").append(counter.clazz.getName()).append('\n'); 133 | report.append("Count:\t").append(counter.updateCount).append('\n'); 134 | report.append("Types:\n"); 135 | for (Class seen : counter.seen) { 136 | report.append("\t").append(seen.getName()).append('\n'); 137 | } 138 | report.append("Traces:\n"); 139 | for (TraceInstanceOf.TraceCounter.Snapshot.TraceSnapshot stack : counter.traces) { 140 | report.append("\t").append(stack.trace).append('\n'); 141 | for (TraceInstanceOf.TraceCounter.Snapshot.TraceSnapshot.ClassCount count : stack.interfaceSeenCounters) { 142 | report.append("\t\tclass: ").append(count.interfaceClazz.getName()).append('\n'); 143 | report.append("\t\tcount: ").append(count.count).append('\n'); 144 | } 145 | } 146 | if (ENABLE_FULL_STACK_TRACES) { 147 | report.append("Full Traces:\n"); 148 | for (StackTraceElement[] fullFrames : counter.fullStackFrames) { 149 | report.append("\t--------------------------\n"); 150 | for (StackTraceElement frame : fullFrames) { 151 | report.append("\t").append(frame).append('\n'); 152 | } 153 | } 154 | } 155 | } 156 | return report; 157 | } 158 | 159 | private static void printReport(boolean last) { 160 | if (LAST_REPORT) { 161 | return; 162 | } 163 | if (last) { 164 | LAST_REPORT = true; 165 | } 166 | StringBuilder summary = new StringBuilder("--------------------------\nType Check Statistics:\n--------------------------\n"); 167 | summary.append("Date:\t").append(REPORT_TIMESTAMP.format(LocalDateTime.now())).append('\n'); 168 | summary.append("Last:\t").append(last).append('\n'); 169 | CharSequence typePollutionReport = reportOf(TraceInstanceOf.orderedTypePollutionCountersSnapshot(TYPE_UPDATE_COUNT_MIN)); 170 | if (typePollutionReport.length() > 0) { 171 | summary.append("--------------------------\nType Pollution:\n"); 172 | summary.append(typePollutionReport); 173 | } 174 | CharSequence missReport = reportOf(TraceInstanceOf.orderedMissCountersSnapshot(TYPE_MISS_COUNT_MIN)); 175 | if (missReport.length() > 0) { 176 | summary.append("--------------------------\nMiss:\n"); 177 | summary.append(missReport); 178 | } 179 | boolean emptyReports = typePollutionReport.length() == 0 && missReport.length() == 0; 180 | if (!emptyReports) { 181 | summary.append("--------------------------\n"); 182 | if (DUMP_ERROR || FILE_DUMP == null) { 183 | System.out.println(summary); 184 | return; 185 | } 186 | if (DUMP == null) { 187 | try { 188 | DUMP = FileChannel.open(Paths.get(FILE_DUMP), StandardOpenOption.CREATE, StandardOpenOption.APPEND); 189 | DUMP_TMP_BUFFER = ByteBuffer.allocateDirect(summary.length()); 190 | } catch (IOException e) { 191 | System.err.println("ERROR while creating the Type Pollution Statistics dump to " + FILE_DUMP + " due to: " + e); 192 | DUMP_ERROR = true; 193 | DUMP_TMP_BUFFER = null; 194 | DUMP = null; 195 | System.out.println(summary); 196 | return; 197 | } 198 | } 199 | if (DUMP_TMP_BUFFER.capacity() < summary.length()) { 200 | DUMP_TMP_BUFFER = ByteBuffer.allocateDirect(summary.length()); 201 | } 202 | DUMP_TMP_BUFFER.clear().limit(summary.length()); 203 | // latin encoding rocks for the report, let's keep it simple 204 | for (int i = 0; i < summary.length(); i++) { 205 | DUMP_TMP_BUFFER.put(i, (byte) summary.charAt(i)); 206 | } 207 | try { 208 | DUMP.write(DUMP_TMP_BUFFER); 209 | } catch (IOException e) { 210 | System.err.println("ERROR while dumping the Type Pollution Statistics to " + FILE_DUMP + " due to: " + e); 211 | DUMP_ERROR = true; 212 | DUMP_TMP_BUFFER = null; 213 | if (!closeDump()) { 214 | System.out.println(summary); 215 | } 216 | return; 217 | } 218 | if (last) { 219 | if (!closeDump()) { 220 | System.out.println(summary); 221 | } 222 | } 223 | } 224 | } 225 | 226 | private static boolean closeDump() { 227 | try { 228 | DUMP.close(); 229 | return true; 230 | } catch (IOException ex) { 231 | DUMP_ERROR = true; 232 | DUMP_TMP_BUFFER = null; 233 | System.err.println("ERROR while closing the Type Pollution Statistics dump on " + FILE_DUMP + " due to: " + ex); 234 | return false; 235 | } finally { 236 | DUMP = null; 237 | } 238 | } 239 | 240 | } 241 | 242 | -------------------------------------------------------------------------------- /agent/src/main/java/io/type/pollution/agent/AppendOnlyList.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.agent; 2 | 3 | import java.util.concurrent.ConcurrentLinkedQueue; 4 | import java.util.concurrent.atomic.AtomicLong; 5 | import java.util.concurrent.atomic.AtomicReference; 6 | import java.util.concurrent.atomic.AtomicReferenceArray; 7 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 8 | import java.util.function.Consumer; 9 | 10 | /** 11 | * The algorithm of this list is a mix of 12 | * https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/MpscUnboundedXaddArrayQueue.java 13 | * and the enhanced Michael-Scott queue on {@link ConcurrentLinkedQueue}. 14 | */ 15 | class AppendOnlyList { 16 | private static final class Chunk extends AtomicReferenceArray { 17 | private static final AtomicReferenceFieldUpdater NEXT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(Chunk.class, Chunk.class, "next"); 18 | private Chunk prev; 19 | private long id; 20 | private volatile Chunk next; 21 | 22 | public Chunk(long id) { 23 | this(id, null); 24 | } 25 | 26 | public Chunk(long id, Chunk prev) { 27 | super(CHUNK_SIZE); 28 | this.id = id; 29 | this.prev = prev; 30 | } 31 | 32 | public boolean trySetNext(Chunk next) { 33 | return NEXT_UPDATER.compareAndSet(this, null, next); 34 | } 35 | 36 | } 37 | 38 | // This MUST be a power of 2 39 | private static final int CHUNK_SIZE = 128; 40 | private static final int CHUNK_MASK = CHUNK_SIZE - 1; 41 | private static final int CHUNK_SHIFT = Integer.numberOfTrailingZeros(CHUNK_SIZE); 42 | 43 | private final AtomicLong appenderSequence = new AtomicLong(); 44 | private final Chunk firstChunk; 45 | private final AtomicReference lastChunk = new AtomicReference<>(); 46 | 47 | public AppendOnlyList() { 48 | firstChunk = new Chunk(0); 49 | lastChunk.lazySet(firstChunk); 50 | } 51 | 52 | private Chunk producerChunkForIndex(final Chunk initialChunk, final long requiredChunkId) { 53 | final AtomicReference lastChunk = this.lastChunk; 54 | Chunk currentChunk = initialChunk; 55 | long jumpBackward; 56 | Chunk tmpChunk = null; 57 | while (true) { 58 | if (currentChunk == null) { 59 | currentChunk = lastChunk.get(); 60 | } 61 | final long currentChunkId = currentChunk.id; 62 | // if the required chunk id is less than the current chunk id then we need to walk the linked list of 63 | // chunks back to the required id 64 | jumpBackward = currentChunkId - requiredChunkId; 65 | if (jumpBackward >= 0) { 66 | break; 67 | } 68 | final long nextChunkId = currentChunkId + 1; 69 | Chunk nextChunk; 70 | // fast-path to save allocating a new chunk 71 | if ((nextChunk = currentChunk.next) != null) { 72 | // try help 73 | lastChunk.compareAndSet(currentChunk, nextChunk); 74 | if (requiredChunkId == nextChunkId) { 75 | return nextChunk; 76 | } 77 | currentChunk = null; 78 | } else { 79 | // slow (maybe) allocating path 80 | if (tmpChunk == null) { 81 | tmpChunk = new Chunk(nextChunkId, currentChunk); 82 | } else { 83 | tmpChunk.id = nextChunkId; 84 | tmpChunk.prev = currentChunk; 85 | } 86 | if (currentChunk.trySetNext(tmpChunk)) { 87 | lastChunk.compareAndSet(currentChunk, tmpChunk); 88 | if (requiredChunkId == nextChunkId) { 89 | return tmpChunk; 90 | } 91 | currentChunk = null; 92 | tmpChunk = null; 93 | } else { 94 | // reset it 95 | tmpChunk.prev = null; 96 | // failed to append to next, try help moving lastChunk forward 97 | nextChunk = currentChunk.next; 98 | lastChunk.compareAndSet(currentChunk, nextChunk); 99 | if (requiredChunkId == nextChunkId) { 100 | return nextChunk; 101 | } 102 | currentChunk = null; 103 | } 104 | } 105 | } 106 | for (long i = 0; i < jumpBackward; i++) { 107 | currentChunk = currentChunk.prev; 108 | assert currentChunk != null; 109 | } 110 | return currentChunk; 111 | } 112 | 113 | public void add(E e) { 114 | if (null == e) { 115 | throw new NullPointerException(); 116 | } 117 | final long pIndex = appenderSequence.getAndIncrement(); 118 | 119 | final int piChunkOffset = (int) (pIndex & CHUNK_MASK); 120 | final long piChunkId = pIndex >> CHUNK_SHIFT; 121 | 122 | Chunk pChunk = lastChunk.get(); 123 | if (pChunk.id != piChunkId) { 124 | pChunk = producerChunkForIndex(pChunk, piChunkId); 125 | } 126 | pChunk.lazySet(piChunkOffset, e); 127 | } 128 | 129 | public void forEach(Consumer accept) { 130 | long remaining = appenderSequence.get(); 131 | if (remaining == 0) { 132 | return; 133 | } 134 | Chunk currentChunk = firstChunk; 135 | while (true) { 136 | final int batch = (int) Math.min(CHUNK_SIZE, remaining); 137 | for (int i = 0; i < batch; i++) { 138 | Object e; 139 | while ((e = currentChunk.get(i)) == null) { 140 | Thread.onSpinWait(); 141 | } 142 | accept.accept((E) e); 143 | } 144 | remaining -= batch; 145 | if (remaining == 0) { 146 | return; 147 | } 148 | Chunk nextChunk = currentChunk.next; 149 | if (nextChunk == null) { 150 | final Chunk newChunk = new Chunk(currentChunk.id + 1, currentChunk); 151 | if (nextChunk.trySetNext(newChunk)) { 152 | nextChunk = newChunk; 153 | } else { 154 | // help GC 155 | newChunk.prev = null; 156 | nextChunk = currentChunk.next; 157 | assert nextChunk != null : "trySetNext can fail just if others succeed"; 158 | } 159 | } 160 | currentChunk = nextChunk; 161 | } 162 | } 163 | 164 | public long size() { 165 | return appenderSequence.get(); 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /agent/src/main/java/io/type/pollution/agent/ByteBuddyUtils.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.agent; 2 | 3 | 4 | import net.bytebuddy.jar.asm.Label; 5 | import net.bytebuddy.jar.asm.Opcodes; 6 | import net.bytebuddy.jar.asm.Type; 7 | 8 | public class ByteBuddyUtils { 9 | 10 | static class ByteBuddyTypePollutionInstructionAdapter extends net.bytebuddy.jar.asm.MethodVisitor { 11 | 12 | private final String classDescriptor; 13 | private final String methodName; 14 | 15 | private final String classFile; 16 | 17 | private String tracePrefix; 18 | 19 | private int line; 20 | 21 | protected ByteBuddyTypePollutionInstructionAdapter(int api, net.bytebuddy.jar.asm.MethodVisitor methodVisitor, String classDescriptor, String methodName, String classFile) { 22 | super(api, methodVisitor); 23 | this.classDescriptor = classDescriptor; 24 | this.methodName = methodName; 25 | this.classFile = classFile; 26 | } 27 | 28 | private String trace() { 29 | if (tracePrefix == null) { 30 | tracePrefix = classDescriptor.replace('/', '.') + "." + methodName + "(" + (classFile != null ? classFile : "Unknown Source)"); 31 | } 32 | if (classFile != null) { 33 | return tracePrefix + ":" + line + ")"; 34 | } else { 35 | return tracePrefix; 36 | } 37 | } 38 | 39 | @Override 40 | public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor, final boolean isInterface) { 41 | if (opcode == Opcodes.INVOKEVIRTUAL && "java/lang/Class".equals(owner)) { 42 | switch (name) { 43 | case "cast": 44 | mv.visitInsn(Opcodes.DUP2); 45 | mv.visitLdcInsn(trace()); 46 | mv.visitMethodInsn(net.bytebuddy.jar.asm.Opcodes.INVOKESTATIC, 47 | Type.getInternalName(TraceInstanceOf.class), 48 | "traceCast", 49 | "(Ljava/lang/Class;Ljava/lang/Object;Ljava/lang/String;)V", false); 50 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); 51 | break; 52 | case "isInstance": 53 | mv.visitLdcInsn(trace()); 54 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, 55 | Type.getInternalName(TraceInstanceOf.class), 56 | "traceIsInstance", 57 | "(Ljava/lang/Class;Ljava/lang/Object;Ljava/lang/String;)Z", false); 58 | break; 59 | case "isAssignableFrom": 60 | mv.visitInsn(Opcodes.DUP2); 61 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); 62 | mv.visitLdcInsn(trace()); 63 | mv.visitMethodInsn(net.bytebuddy.jar.asm.Opcodes.INVOKESTATIC, 64 | Type.getInternalName(TraceInstanceOf.class), 65 | "traceIsAssignableFrom", 66 | "(Ljava/lang/Class;Ljava/lang/Class;ZLjava/lang/String;)Z", false); 67 | break; 68 | default: 69 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); 70 | } 71 | } else { 72 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); 73 | } 74 | } 75 | 76 | @Override 77 | public void visitTypeInsn(final int opcode, final String type) { 78 | switch (opcode) { 79 | case Opcodes.CHECKCAST: 80 | checkcast(Type.getObjectType(type)); 81 | break; 82 | case Opcodes.INSTANCEOF: 83 | instanceOf(Type.getObjectType(type)); 84 | break; 85 | default: 86 | super.visitTypeInsn(opcode, type); 87 | } 88 | } 89 | 90 | @Override 91 | public void visitLineNumber(final int line, final Label start) { 92 | this.line = line; 93 | super.visitLineNumber(line, start); 94 | } 95 | 96 | public void checkcast(final Type type) { 97 | mv.visitInsn(net.bytebuddy.jar.asm.Opcodes.DUP); 98 | mv.visitLdcInsn(type); 99 | mv.visitLdcInsn(trace()); 100 | mv.visitMethodInsn(net.bytebuddy.jar.asm.Opcodes.INVOKESTATIC, 101 | Type.getInternalName(TraceInstanceOf.class), 102 | "traceCheckcast", 103 | "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)V", false); 104 | super.visitTypeInsn(net.bytebuddy.jar.asm.Opcodes.CHECKCAST, type.getInternalName()); 105 | } 106 | 107 | public void instanceOf(final Type type) { 108 | mv.visitLdcInsn(type); 109 | mv.visitLdcInsn(trace()); 110 | mv.visitMethodInsn(Opcodes.INVOKESTATIC, 111 | Type.getInternalName(TraceInstanceOf.class), 112 | "traceInstanceOf", 113 | "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)Z", false); 114 | } 115 | } 116 | 117 | static class ByteBuddyTypePollutionClassVisitor extends net.bytebuddy.jar.asm.ClassVisitor { 118 | 119 | private String name; 120 | private String source; 121 | 122 | ByteBuddyTypePollutionClassVisitor(int api, net.bytebuddy.jar.asm.ClassVisitor cv) { 123 | super(api, cv); 124 | } 125 | 126 | @Override 127 | public void visitSource(final String source, final String debug) { 128 | this.source = source; 129 | super.visitSource(source, debug); 130 | } 131 | 132 | @Override 133 | public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { 134 | this.name = name; 135 | super.visit(version, access, name, signature, superName, interfaces); 136 | } 137 | 138 | @Override 139 | public net.bytebuddy.jar.asm.MethodVisitor visitMethod(int flags, String name, 140 | String desc, String signature, String[] exceptions) { 141 | return new ByteBuddyTypePollutionInstructionAdapter(api, super.visitMethod(flags, name, desc, 142 | signature, exceptions), this.name, name, source); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /agent/src/main/java/io/type/pollution/agent/TraceInstanceOf.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.agent; 2 | 3 | import java.util.*; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.concurrent.CopyOnWriteArraySet; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | import java.util.concurrent.atomic.AtomicLongFieldUpdater; 10 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 11 | 12 | public class TraceInstanceOf { 13 | 14 | private static volatile long GLOBAL_SAMPLING_TICK = System.nanoTime(); 15 | private static final AtomicInteger METRONOME_PERIOD_MS = new AtomicInteger(-1); 16 | 17 | // not ideal perf-wise: 18 | // a good candidate to perform one-shot changing checks is 19 | // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/invoke/SwitchPoint.html 20 | private static final AtomicBoolean TRACING_STARTED = new AtomicBoolean(); 21 | private static final Thread METRONOME = new Thread(() -> { 22 | // this isn't supposed to change 23 | final int samplingPeriod = METRONOME_PERIOD_MS.get(); 24 | final Thread current = Thread.currentThread(); 25 | while (!current.isInterrupted()) { 26 | try { 27 | Thread.sleep(samplingPeriod); 28 | } catch (InterruptedException e) { 29 | // let's stop 30 | return; 31 | } 32 | GLOBAL_SAMPLING_TICK = System.nanoTime(); 33 | } 34 | 35 | }); 36 | 37 | public static void startTracing(final int seconds) { 38 | if (seconds <= 0) { 39 | startTracing(); 40 | } else { 41 | final long start = System.nanoTime(); 42 | final Thread startTracingThread = new Thread(() -> { 43 | final long timeToStartThread = System.nanoTime() - start; 44 | final long remaningBeforeStartTracing = 45 | TimeUnit.SECONDS.toNanos(seconds) - timeToStartThread; 46 | if (remaningBeforeStartTracing > 0) { 47 | try { 48 | TimeUnit.NANOSECONDS.sleep(remaningBeforeStartTracing); 49 | startTracing(); 50 | } catch (InterruptedException ignore) { 51 | // we're stopping 52 | } 53 | } 54 | }); 55 | startTracingThread.setName("start-tracing"); 56 | startTracingThread.setDaemon(true); 57 | startTracingThread.start(); 58 | } 59 | } 60 | 61 | private static void startTracing() { 62 | TRACING_STARTED.compareAndSet(false, true); 63 | } 64 | 65 | public static void startMetronome(int samplingPeriod) { 66 | if (METRONOME_PERIOD_MS.compareAndSet(-1, samplingPeriod) && samplingPeriod > 0) { 67 | METRONOME.setDaemon(true); 68 | METRONOME.setName("type-pollution-metronome"); 69 | METRONOME.start(); 70 | } 71 | } 72 | 73 | public static final class MissTraceCounter extends TraceCounter { 74 | 75 | 76 | private MissTraceCounter(Class clazz) { 77 | super(clazz); 78 | } 79 | 80 | public void onTypeCheckMiss(Class interfaceClazz, String trace) { 81 | updateTraceCount(interfaceClazz, trace); 82 | } 83 | } 84 | 85 | public static final class TypePollutionTraceCounter extends TraceCounter { 86 | private static final AtomicReferenceFieldUpdater LAST_SEEN_INTERFACE_UPDATER = 87 | AtomicReferenceFieldUpdater.newUpdater(TypePollutionTraceCounter.class, Class.class, "lastSeenInterface"); 88 | 89 | private volatile Class lastSeenInterface = null; 90 | 91 | private TypePollutionTraceCounter(Class clazz) { 92 | super(clazz); 93 | } 94 | 95 | public void onTypeCheckHit(Class interfaceClazz, String trace) { 96 | final Class lastSeen = lastSeenInterface; 97 | if (interfaceClazz.equals(lastSeen)) { 98 | return; 99 | } 100 | // ok to lose some sample 101 | LAST_SEEN_INTERFACE_UPDATER.lazySet(this, interfaceClazz); 102 | if (lastSeen != null) { 103 | updateTraceCount(interfaceClazz, trace); 104 | } 105 | } 106 | } 107 | 108 | public static class TraceCounter { 109 | 110 | private static final class StackTraceArrayList extends ArrayList { 111 | 112 | public StackTraceArrayList(int capacity) { 113 | super(capacity); 114 | } 115 | } 116 | 117 | 118 | private static final AtomicLongFieldUpdater SAMPLING_TICK_UPDATER = 119 | AtomicLongFieldUpdater.newUpdater(TraceCounter.class, "lastSamplingTick"); 120 | 121 | private final Class clazz; 122 | private volatile long lastSamplingTick = System.nanoTime(); 123 | private final ConcurrentHashMap traces = new ConcurrentHashMap<>(); 124 | private static final ThreadLocal TRACE = ThreadLocal.withInitial(Trace::new); 125 | 126 | public static class Trace { 127 | private Class interfaceClazz; 128 | private String trace; 129 | 130 | public Trace() { 131 | 132 | } 133 | 134 | public Trace with(Class interfaceClazz, String trace) { 135 | this.interfaceClazz = interfaceClazz; 136 | this.trace = trace; 137 | return this; 138 | } 139 | 140 | public Trace clear() { 141 | this.interfaceClazz = null; 142 | this.trace = null; 143 | return this; 144 | } 145 | 146 | @Override 147 | public boolean equals(final Object o) { 148 | if (this == o) return true; 149 | if (o == null || getClass() != o.getClass()) return false; 150 | 151 | final Trace trace1 = (Trace) o; 152 | 153 | if (!interfaceClazz.equals(trace1.interfaceClazz)) return false; 154 | return trace.equals(trace1.trace); 155 | } 156 | 157 | @Override 158 | public int hashCode() { 159 | int result = interfaceClazz.hashCode(); 160 | result = 31 * result + trace.hashCode(); 161 | return result; 162 | } 163 | } 164 | 165 | public static class TraceData { 166 | 167 | private static final ThreadLocal FULL_STACK_TRACE = new ThreadLocal<>(); 168 | 169 | private static final AtomicLongFieldUpdater COUNT_UPDATER = 170 | AtomicLongFieldUpdater.newUpdater(TraceData.class, "count"); 171 | 172 | private volatile long count; 173 | private final CopyOnWriteArraySet sampledStackTraces = new CopyOnWriteArraySet<>(); 174 | 175 | public void weakIncrementUpdateCount() { 176 | COUNT_UPDATER.lazySet(this, count + 1); 177 | } 178 | 179 | private static StackTraceArrayList acquireStackTraceListOf(int capacity) { 180 | StackTraceArrayList list = FULL_STACK_TRACE.get(); 181 | if (list == null) { 182 | list = new StackTraceArrayList(capacity); 183 | FULL_STACK_TRACE.set(list); 184 | } 185 | list.ensureCapacity(capacity); 186 | return list; 187 | } 188 | 189 | private static void releaseStackTraceArrayList() { 190 | FULL_STACK_TRACE.set(null); 191 | } 192 | 193 | public boolean addFullStackTrace() { 194 | // this is not nice :( we KNOW which level we are so can hard-code this 195 | // but is brittle, and it depends on where the trace collection happens 196 | final int START_STACK = 5; 197 | StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace(); 198 | final int stackTraceMaxDepth; 199 | if (Agent.FULL_STACK_TRACES_LIMIT <= 0) { 200 | stackTraceMaxDepth = stackTraces.length; 201 | } else { 202 | stackTraceMaxDepth = Math.min(Agent.FULL_STACK_TRACES_LIMIT + START_STACK, stackTraces.length); 203 | } 204 | final StackTraceArrayList fullStackTraces = acquireStackTraceListOf(stackTraceMaxDepth); 205 | boolean addedFullStackSample = false; 206 | try { 207 | for (int i = START_STACK; i < stackTraceMaxDepth; i++) { 208 | fullStackTraces.add(stackTraces[i]); 209 | } 210 | addedFullStackSample = sampledStackTraces.add(fullStackTraces); 211 | } catch (Throwable ignore) { 212 | 213 | } 214 | if (addedFullStackSample) { 215 | // cannot reuse it 216 | releaseStackTraceArrayList(); 217 | } else { 218 | // keep on reusing it 219 | fullStackTraces.clear(); 220 | } 221 | return addedFullStackSample; 222 | } 223 | 224 | } 225 | 226 | private TraceCounter(Class clazz) { 227 | this.clazz = clazz; 228 | } 229 | 230 | protected final void updateTraceCount(Class interfaceClazz, String trace) { 231 | final Trace pooledTrace = TRACE.get().with(interfaceClazz, trace); 232 | final boolean added; 233 | TraceData data = traces.get(pooledTrace); 234 | if (data == null) { 235 | TraceData newData = new TraceData(); 236 | data = traces.putIfAbsent(pooledTrace, newData); 237 | if (data == null) { 238 | // cannot reuse the pooled trace anymore! 239 | added = true; 240 | data = newData; 241 | } else { 242 | added = false; 243 | } 244 | } else { 245 | added = false; 246 | } 247 | if (added) { 248 | // replace it! 249 | TRACE.set(new Trace()); 250 | } else { 251 | pooledTrace.clear(); 252 | } 253 | data.weakIncrementUpdateCount(); 254 | final int samplingPeriod = METRONOME_PERIOD_MS.get(); 255 | if (samplingPeriod >= 0) { 256 | if (samplingPeriod == 0) { 257 | data.addFullStackTrace(); 258 | } else { 259 | final long tick = lastSamplingTick; 260 | final long globalTick = GLOBAL_SAMPLING_TICK; 261 | if (tick - globalTick < 0) { 262 | // move forward our tick 263 | if (SAMPLING_TICK_UPDATER.compareAndSet(this, tick, globalTick)) { 264 | data.addFullStackTrace(); 265 | } 266 | } 267 | } 268 | } 269 | } 270 | 271 | public long count() { 272 | long count = 0; 273 | for (Map.Entry trace : traces.entrySet()) { 274 | count += trace.getValue().count; 275 | } 276 | return count; 277 | } 278 | 279 | public static class Snapshot implements Comparable { 280 | 281 | public static class TraceSnapshot { 282 | 283 | public static class ClassCount { 284 | public final Class interfaceClazz; 285 | public final long count; 286 | 287 | private ClassCount(final Class interfaceClazz, final long count) { 288 | this.interfaceClazz = interfaceClazz; 289 | this.count = count; 290 | } 291 | } 292 | 293 | public final String trace; 294 | public final ClassCount[] interfaceSeenCounters; 295 | 296 | private TraceSnapshot(final String trace, final ClassCount[] interfaceSeenCounters) { 297 | this.trace = trace; 298 | this.interfaceSeenCounters = interfaceSeenCounters; 299 | } 300 | } 301 | 302 | public final Class clazz; 303 | public final Class[] seen; 304 | public final TraceSnapshot[] traces; 305 | public final StackTraceElement[][] fullStackFrames; 306 | public final long updateCount; 307 | 308 | private Snapshot(Class clazz, Class[] seen, TraceSnapshot[] traces, StackTraceElement[][] fullStackFrame) { 309 | this.clazz = clazz; 310 | this.seen = seen; 311 | this.fullStackFrames = fullStackFrame; 312 | this.traces = traces; 313 | this.updateCount = updateCount(traces); 314 | } 315 | 316 | private static long updateCount(TraceSnapshot[] traces) { 317 | long count = 0; 318 | for (TraceSnapshot trace : traces) { 319 | for (TraceSnapshot.ClassCount counter : trace.interfaceSeenCounters) { 320 | count += counter.count; 321 | } 322 | } 323 | return count; 324 | } 325 | 326 | @Override 327 | public int compareTo(Snapshot o) { 328 | return Long.compare(updateCount, o.updateCount); 329 | } 330 | } 331 | 332 | private static Snapshot.TraceSnapshot[] buildOrderedTraceSnapshots(Map> topStackTraces) { 333 | final Snapshot.TraceSnapshot[] traceSnapshots = new Snapshot.TraceSnapshot[topStackTraces.size()]; 334 | int i = 0; 335 | for (Map.Entry> topStackTrace : topStackTraces.entrySet()) { 336 | Snapshot.TraceSnapshot.ClassCount[] classCounts = topStackTrace.getValue() 337 | .toArray(new Snapshot.TraceSnapshot.ClassCount[0]); 338 | // order update count(s) based on 339 | Arrays.sort(classCounts, 340 | Comparator.comparingLong(classCount -> classCount.count).reversed()); 341 | traceSnapshots[i] = new Snapshot.TraceSnapshot(topStackTrace.getKey(), classCounts); 342 | i++; 343 | } 344 | // order trace snapshot(s) based on total (ie sum) update count 345 | Arrays.sort(traceSnapshots, Comparator.comparingLong(traceSnapshot -> { 346 | long totalUpdateCount = 0; 347 | for (Snapshot.TraceSnapshot.ClassCount classCount : traceSnapshot.interfaceSeenCounters) { 348 | totalUpdateCount += classCount.count; 349 | } 350 | return totalUpdateCount; 351 | }).reversed()); 352 | return traceSnapshots; 353 | } 354 | 355 | private static class Counter { 356 | long value; 357 | } 358 | 359 | private static Class[] buildOrderedInterfaceClasses(final Map interfaceCounters) { 360 | final Class[] interfaceClasses = new Class[interfaceCounters.size()]; 361 | int j = 0; 362 | for (Map.Entry interfaceCounter : interfaceCounters.entrySet()) { 363 | interfaceClasses[j] = interfaceCounter.getKey(); 364 | j++; 365 | } 366 | Arrays.sort(interfaceClasses, 367 | Comparator.comparingLong(aClass -> interfaceCounters.get(aClass).value).reversed()); 368 | return interfaceClasses; 369 | } 370 | 371 | private static StackTraceElement[][] buildUnorderedFullStackTraces(Set fullStackFrames) { 372 | final StackTraceElement[][] fullStackTraces = new StackTraceElement[fullStackFrames.size()][]; 373 | int z = 0; 374 | for (StackTraceArrayList fullStackFrame : fullStackFrames) { 375 | fullStackTraces[z] = fullStackFrame.toArray(new StackTraceElement[0]); 376 | z++; 377 | } 378 | return fullStackTraces; 379 | } 380 | 381 | public Snapshot snapshot() { 382 | final int tracesCount = traces.size(); 383 | if (tracesCount == 0) { 384 | return null; 385 | } 386 | final Map> topStackTraces = new HashMap<>(tracesCount); 387 | final Set fullStackFrames = new HashSet<>(tracesCount); 388 | 389 | final Map interfaceCounters = new HashMap<>(); 390 | traces.forEach((trace, traceData) -> { 391 | for (StackTraceArrayList fullStackTrace : traceData.sampledStackTraces) { 392 | fullStackFrames.add(fullStackTrace); 393 | } 394 | topStackTraces.computeIfAbsent(trace.trace, t -> new ArrayList<>(1)) 395 | .add(new Snapshot.TraceSnapshot.ClassCount(trace.interfaceClazz, traceData.count)); 396 | interfaceCounters.computeIfAbsent(trace.interfaceClazz, t -> new Counter()).value += traceData.count; 397 | }); 398 | final Snapshot.TraceSnapshot[] traceSnapshots = buildOrderedTraceSnapshots(topStackTraces); 399 | final Class[] interfaceClasses = buildOrderedInterfaceClasses(interfaceCounters); 400 | final StackTraceElement[][] fullStackTraces = buildUnorderedFullStackTraces(fullStackFrames); 401 | return new TraceCounter.Snapshot(clazz, interfaceClasses, traceSnapshots, 402 | fullStackTraces); 403 | } 404 | 405 | } 406 | 407 | private static final AppendOnlyList TYPE_POLLUTION_COUNTERS = new AppendOnlyList<>(); 408 | private static final AppendOnlyList MISS_COUNTERS = new AppendOnlyList<>(); 409 | 410 | private static final ClassValue TYPE_POLLUTION_COUNTER_CACHE = new ClassValue<>() { 411 | @Override 412 | protected TypePollutionTraceCounter computeValue(Class aClass) { 413 | final TypePollutionTraceCounter counter = new TypePollutionTraceCounter(aClass); 414 | TYPE_POLLUTION_COUNTERS.add(counter); 415 | return counter; 416 | } 417 | }; 418 | 419 | private static final ClassValue MISS_COUNTER_CACHE = new ClassValue<>() { 420 | @Override 421 | protected MissTraceCounter computeValue(Class aClass) { 422 | final MissTraceCounter counter = new MissTraceCounter(aClass); 423 | MISS_COUNTERS.add(counter); 424 | return counter; 425 | } 426 | }; 427 | 428 | public static boolean traceIsInstance(Class interfaceClazz, Object o, String trace) { 429 | if (!interfaceClazz.isInstance(o)) { 430 | if (o != null && isTracingStarted() && interfaceClazz.isInterface()) { 431 | MISS_COUNTER_CACHE.get(o.getClass()).onTypeCheckMiss(interfaceClazz, trace); 432 | } 433 | return false; 434 | } 435 | if (!isTracingStarted()) { 436 | return true; 437 | } 438 | // unnecessary tracing 439 | if (!interfaceClazz.isInterface()) { 440 | return true; 441 | } 442 | TYPE_POLLUTION_COUNTER_CACHE.get(o.getClass()).onTypeCheckHit(interfaceClazz, trace); 443 | return true; 444 | } 445 | 446 | public static boolean traceIsAssignableFrom(Class interfaceClazz, Class oClazz, boolean result, String trace) { 447 | if (!result) { 448 | if (isTracingStarted() && interfaceClazz.isInterface()) { 449 | MISS_COUNTER_CACHE.get(oClazz).onTypeCheckMiss(interfaceClazz, trace); 450 | } 451 | return false; 452 | } 453 | if (!isTracingStarted()) { 454 | return true; 455 | } 456 | if (!interfaceClazz.isInterface()) { 457 | return true; 458 | } 459 | TYPE_POLLUTION_COUNTER_CACHE.get(oClazz).onTypeCheckHit(interfaceClazz, trace); 460 | return true; 461 | } 462 | 463 | public static void traceCast(Class interfaceClazz, Object o, String trace) { 464 | if (!isTracingStarted()) { 465 | return; 466 | } 467 | if (!interfaceClazz.isInterface()) { 468 | return; 469 | } 470 | if (!interfaceClazz.isInstance(o)) { 471 | return; 472 | } 473 | TYPE_POLLUTION_COUNTER_CACHE.get(o.getClass()).onTypeCheckHit(interfaceClazz, trace); 474 | } 475 | 476 | public static boolean traceInstanceOf(Object o, Class interfaceClazz, String trace) { 477 | if (!interfaceClazz.isInstance(o)) { 478 | if (o!= null && isTracingStarted() && interfaceClazz.isInterface()) { 479 | MISS_COUNTER_CACHE.get(o.getClass()).onTypeCheckMiss(interfaceClazz, trace); 480 | } 481 | return false; 482 | } 483 | if (!isTracingStarted()) { 484 | return true; 485 | } 486 | if (!interfaceClazz.isInterface()) { 487 | return true; 488 | } 489 | TYPE_POLLUTION_COUNTER_CACHE.get(o.getClass()).onTypeCheckHit(interfaceClazz, trace); 490 | return true; 491 | } 492 | 493 | private static boolean isTracingStarted() { 494 | return TRACING_STARTED.get(); 495 | } 496 | 497 | public static void traceCheckcast(Object o, Class interfaceClazz, String trace) { 498 | if (!isTracingStarted()) { 499 | return; 500 | } 501 | if (!interfaceClazz.isInterface()) { 502 | return; 503 | } 504 | if (!interfaceClazz.isInstance(o)) { 505 | return; 506 | } 507 | TYPE_POLLUTION_COUNTER_CACHE.get(o.getClass()).onTypeCheckHit(interfaceClazz, trace); 508 | } 509 | 510 | private static Collection orderedCountersSnapshots(AppendOnlyList counters, final int minUpdateCount) { 511 | final int size = (int) counters.size(); 512 | ArrayList snapshots = new ArrayList<>(size); 513 | counters.forEach(traceCounter -> { 514 | final int minCount = Math.max(1, minUpdateCount); 515 | if (traceCounter.count() > minCount) { 516 | final TraceCounter.Snapshot snapshot = traceCounter.snapshot(); 517 | if (snapshot != null) { 518 | snapshots.add(snapshot); 519 | } 520 | } 521 | }); 522 | snapshots.sort(Comparator.reverseOrder()); 523 | // collect 524 | return snapshots; 525 | } 526 | 527 | public static Collection orderedTypePollutionCountersSnapshot(final int minUpdateCount) { 528 | return orderedCountersSnapshots(TYPE_POLLUTION_COUNTERS, minUpdateCount); 529 | } 530 | 531 | public static Collection orderedMissCountersSnapshot(final int minUpdateCount) { 532 | return orderedCountersSnapshots(MISS_COUNTERS, minUpdateCount); 533 | } 534 | 535 | } 536 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /benchmarks/WHATIF.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting JDK-8180450 2 | 3 | In the perfect, self-contained and controlled environment of a micro-benchmark 4 | written with JMH, troubleshooting JDK-8180450 is challenging but still possible, but what 5 | if it happens in the real world? 6 | 7 | ## Who says we have *any* perf issue? 8 | 9 | *Required*: 10 | - https://github.com/jvm-profiling-tools/async-profiler 11 | - perf-tools 12 | - perf-c2c 13 | 14 | Let's run an example (dumb) application that suffer from this issue: 15 | 16 | ```bash 17 | $ java -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -jar benchmarks/target/type-pollution-benchmarks-0.1-SNAPSHOT.jar 2 18 | ``` 19 | and attaching async-profiler to it with: 20 | ```bash 21 | $ ./profiler.sh -d 10 -t -f out.html -e cpu 22 | ``` 23 | we get 24 | 25 | [![async-profiler per-thread generated flame graph](images/async/async-profiler.png)](https://htmlpreview.github.io/?https://github.com/RedHatPerf/type-pollution-agent/blob/master/benchmarks/images/async/async-profiler.html) 26 | 27 | That's not terribly useful, given that the blamed method doesn't match our expectation, 28 | but still give us a good hint that we're CPU limited: 29 | this is clear by counting the number of samples ie 100 Hz * 10 seconds * 2 threads = 2000 samples. 30 | 31 | We can dig on this more by using `perf`: 32 | ```bash 33 | $ perf stat --timeout 10000 --pid 34 | 35 | Performance counter stats for process id '1247858': 36 | 37 | 20,013.68 msec task-clock # 1.999 CPUs utilized 38 | 616 context-switches # 30.779 /sec 39 | 39 cpu-migrations # 1.949 /sec 40 | 20 page-faults # 0.999 /sec 41 | 82,267,123,004 cycles # 4.111 GHz 42 | 41,091,980,552 instructions # 0.50 insn per cycle 43 | 9,477,353,472 branches # 473.544 M/sec 44 | 483,549 branch-misses # 0.01% of all branches 45 | 46 | 10.010738228 seconds time elapsed 47 | 48 | ``` 49 | IPC is 0.5? IPC < 1 it usually means stalls presence. 50 | A quick check (spoiler; we already know it would report something useful :P) 51 | with [perf c2c](https://man7.org/linux/man-pages/man1/perf-c2c.1.html) it's a no-brain: 52 | ```bash 53 | $ timeout 20s perf c2c record -- --delay 10000 java -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -jar benchmarks/target/type-pollution-benchmarks-0.1-SNAPSHOT.jar 2 54 | Events disabled 55 | # ...10 seconds after... 56 | Events enabled 57 | # ...10 seconds after... 58 | [ perf record: Woken up 49 times to write data ] 59 | [ perf record: Captured and wrote 12.552 MB perf.data (148076 samples) ] 60 | ``` 61 | 62 | and to generate the report: 63 | ```bash 64 | $ perf c2c report --stats 65 | ================================================= 66 | Trace Event Information 67 | ================================================= 68 | Total records : 148076 69 | Locked Load/Store Operations : 18 70 | Load Operations : 70418 71 | Loads - uncacheable : 0 72 | Loads - IO : 0 73 | Loads - Miss : 0 74 | Loads - no mapping : 4 75 | Load Fill Buffer Hit : 70200 76 | Load L1D hit : 71 77 | Load L2D hit : 0 78 | Load LLC hit : 121 79 | Load Local HITM : 19 80 | Load Remote HITM : 0 81 | Load Remote HIT : 0 82 | Load Local DRAM : 22 83 | Load Remote DRAM : 0 84 | Load MESI State Exclusive : 0 85 | Load MESI State Shared : 22 86 | Load LLC Misses : 22 87 | Load access blocked by data : 0 88 | Load access blocked by address : 0 89 | LLC Misses to Local DRAM : 100.0% 90 | LLC Misses to Remote DRAM : 0.0% 91 | LLC Misses to Remote cache (HIT) : 0.0% 92 | LLC Misses to Remote cache (HITM) : 0.0% 93 | Store Operations : 77658 94 | Store - uncacheable : 0 95 | Store - no mapping : 0 96 | Store L1D Hit : 34054 97 | Store L1D Miss : 43604 98 | Store No available memory level : 0 99 | No Page Map Rejects : 815 100 | Unable to parse data source : 0 101 | 102 | ================================================= 103 | Global Shared Cache Line Event Information 104 | ================================================= 105 | Total Shared Cache Lines : 4 106 | Load HITs on shared lines : 70125 107 | Fill Buffer Hits on shared lines : 70106 108 | L1D hits on shared lines : 0 109 | L2D hits on shared lines : 0 110 | LLC hits on shared lines : 19 111 | Locked Access on shared lines : 0 112 | Blocked Access on shared lines : 0 113 | Store HITs on shared lines : 45153 114 | Store L1D hits on shared lines : 1556 115 | Store No available memory level : 0 116 | Total Merged records : 45172 117 | 118 | ================================================= 119 | c2c details 120 | ================================================= 121 | Events : cpu/mem-loads,ldlat=30/P 122 | : cpu/mem-stores/P 123 | : dummy:HG 124 | Cachelines sort on : Total HITMs 125 | Cacheline data grouping : offset,iaddr 126 | ``` 127 | Without digging too much in the output, the suspicious parts are 128 | ```bash 129 | ================================================= 130 | Global Shared Cache Line Event Information 131 | ================================================= 132 | Total Shared Cache Lines : 4 133 | Load HITs on shared lines : 70125 134 | Fill Buffer Hits on shared lines : 70106 135 | L1D hits on shared lines : 0 136 | L2D hits on shared lines : 0 137 | LLC hits on shared lines : 19 138 | Locked Access on shared lines : 0 139 | Blocked Access on shared lines : 0 140 | Store HITs on shared lines : 45153 141 | Store L1D hits on shared lines : 1556 142 | Store No available memory level : 0 143 | Total Merged records : 45172 144 | ``` 145 | presence of shared lines and Store HITs! Who says sharing is a good thing?! 146 | 147 | Let's come back to the Java profiling side: we know we have some CPU-bound related problem 148 | and both OS and Java profiler(s) agree on that, but, how to get the best of both worlds 149 | while getting the right level of detail to understand what's going on? 150 | 151 | ## Down to the Rabbit Hole 152 | 153 | Thanks to https://builds.shipilev.net/perfasm/, we have a nice tool we can use to further narrow 154 | the issue (we need a JVM capable of `PrintAssembly`, see [How to print disassembly from JIT Code?](https://jpbempel.github.io/2012/10/16/how-to-print-disassembly-from-JIT-code.html) 155 | and [PrintAssembly output explained!](https://jpbempel.github.io/2015/12/30/printassembly-output-explained.html)). 156 | 157 | This approach emulate what JMH does under the hood with its handy `perfasm` profiler 158 | ie [LinuxPerfAsmProfiler](https://github.com/openjdk/jmh/blob/master/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfAsmProfiler.java) 159 | 160 | Let's run it: 161 | ```bash 162 | $ timeout 20s perf record --delay 10000 --event cycles java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+PrintNMethods -XX:+PrintNativeNMethods -XX:+PrintSignatureHandlers -XX:+PrintAdapterHandlers -XX:+PrintStubCode -XX:+LogCompilation -XX:LogFile=hotspot.log -XX:+DebugNonSafepoints -jar benchmarks/target/type-pollution-benchmarks-0.1-SNAPSHOT.jar 2 163 | ``` 164 | And then, process the collected samples (and compilation logs) with: 165 | ```bash 166 | $ java -jar benchmarks/tools/perfasm.jar "delay=1000;length=10000000;hotThreshold=0.01" hotspot.log perf.data 167 | ``` 168 | Which relevant output 169 | ```asm 170 | ....[Hottest Region 1].............................................................................. 171 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (101 bytes) 172 | 173 | 0x00007fb54f2d0d97: jne 0x00007fb54f2d0da1 174 | 0x00007fb54f2d0d9d: mov %rax,0x20(%rsi) 175 | 0x00007fb54f2d0da1: je 0x00007fb54f2d0cd2 176 | 0x00007fb54f2d0da7: mov $0xffffffde,%esi 177 | 0x00007fb54f2d0dac: mov %r11,%rbp 178 | 0x00007fb54f2d0daf: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop } 179 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 180 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9) 181 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 182 | ; {runtime_call UncommonTrapBlob} 183 | 0.01% 0x00007fb54f2d0db4: movabs $0x800062248,%rax ; {metadata('io/type/pollution/benchmarks/Context')} 184 | 0x00007fb54f2d0dbe: push %rax 185 | 0.02% 0x00007fb54f2d0dbf: mov %rax,%rax 186 | 0.27% 0x00007fb54f2d0dc2: mov 0x28(%rsi),%rdi 187 | 23.61% 0x00007fb54f2d0dc6: mov (%rdi),%ecx 188 | 1.40% 0x00007fb54f2d0dc8: add $0x8,%rdi 189 | 0x00007fb54f2d0dcc: test %rax,%rax 190 | 0x00007fb54f2d0dcf: repnz scas %es:(%rdi),%rax 191 | 10.78% 0x00007fb54f2d0dd2: pop %rax 192 | 2.76% ╭ 0x00007fb54f2d0dd3: jne 0x00007fb54f2d0ddd 193 | 0.00% │ 0x00007fb54f2d0dd9: mov %rax,0x20(%rsi) 194 | 0.60% ↘ 0x00007fb54f2d0ddd: je 0x00007fb54f2d0d0f 195 | 0x00007fb54f2d0de3: mov $0xffffffde,%esi 196 | 0x00007fb54f2d0de8: mov %r8,%rbp 197 | 0x00007fb54f2d0deb: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop } 198 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 199 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@4 (line 8) 200 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 201 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 202 | ; {runtime_call UncommonTrapBlob} 203 | 0.08% 0x00007fb54f2d0df0: movabs $0x800062448,%rax ; {metadata('io/type/pollution/benchmarks/InternalContext')} 204 | 0.18% 0x00007fb54f2d0dfa: push %rax 205 | 0.02% 0x00007fb54f2d0dfb: mov %rax,%rax 206 | 0x00007fb54f2d0dfe: mov 0x28(%rsi),%rdi 207 | 23.53% 0x00007fb54f2d0e02: mov (%rdi),%ecx 208 | 1.51% 0x00007fb54f2d0e04: add $0x8,%rdi 209 | 0.00% 0x00007fb54f2d0e08: test %rax,%rax 210 | 1.17% 0x00007fb54f2d0e0b: repnz scas %es:(%rdi),%rax 211 | 9.96% 0x00007fb54f2d0e0e: pop %rax 212 | 2.82% ╭ 0x00007fb54f2d0e0f: jne 0x00007fb54f2d0e19 213 | 0.00% │ 0x00007fb54f2d0e15: mov %rax,0x20(%rsi) 214 | 0.59% ↘ 0x00007fb54f2d0e19: je 0x00007fb54f2d0d22 215 | 0x00007fb54f2d0e1f: mov $0xffffffde,%esi 216 | 0x00007fb54f2d0e24: mov %r8,%rbp 217 | 0x00007fb54f2d0e27: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop } 218 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 219 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9) 220 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 221 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 222 | ; {runtime_call UncommonTrapBlob} 223 | 0x00007fb54f2d0e2c: cmp $0x67c70,%r10d ; {metadata('io/type/pollution/benchmarks/DuplicatedContext')} 224 | 0x00007fb54f2d0e33: jne 0x00007fb54f2d0e52 ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0} 225 | .................................................................................................... 226 | 79.33% 227 | ``` 228 | And luckly for us, on JDK (11, the one used to run the application), there are not many parts 229 | making uses of `repnz scas` ie [MacroAssembler::check_klass_subtype_slow_path](https://github.com/openjdk/jdk11/blob/37115c8ea4aff13a8148ee2b8832b20888a5d880/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L5514), 230 | responsible to emitting the slow path code for instanceof/checkcast (not only, more on later) 231 | type checks. 232 | 233 | ## What if...this agent was already implemented? 234 | 235 | In a better world, where we can run upfront the agent on cases as suspicious as this one, 236 | what we can get is: 237 | ``` 238 | $ timeout 10s java -javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar -jar benchmarks/target/type-pollution-benchmarks-0.1-SNAPSHOT.jar 2 239 | -------------------------- 240 | Type Pollution Statistics: 241 | -------------------------- 242 | 1: io.type.pollution.benchmarks.DuplicatedContext 243 | Count: 126152118 244 | Types: 245 | io.type.pollution.benchmarks.InternalContext 246 | io.type.pollution.benchmarks.Context 247 | Traces: 248 | io.type.pollution.benchmarks.ContextUtil.isDuplicatedContext(ContextUtil.java:9) 249 | class: io.type.pollution.benchmarks.InternalContext 250 | count: 63904149 251 | io.type.pollution.benchmarks.ContextUtil.isDuplicatedContext(ContextUtil.java:8) 252 | class: io.type.pollution.benchmarks.Context 253 | count: 62247969 254 | -------------------------- 255 | 2: io.type.pollution.benchmarks.NonDuplicatedContext 256 | Count: 21999 257 | Types: 258 | io.type.pollution.benchmarks.InternalContext 259 | io.type.pollution.benchmarks.Context 260 | Traces: 261 | io.type.pollution.benchmarks.ContextUtil.isDuplicatedContext(ContextUtil.java:9) 262 | class: io.type.pollution.benchmarks.InternalContext 263 | count: 11000 264 | io.type.pollution.benchmarks.ContextUtil.isDuplicatedContext(ContextUtil.java:8) 265 | class: io.type.pollution.benchmarks.Context 266 | count: 10999 267 | -------------------------- 268 | ``` 269 | As expected, it correctly reports 2 concrete types performing type checks (secondary super cache invalidations) against the same interfaces, 270 | with `DuplicatedContext` as the main offender, while `NonDuplicatedContext`, instead, doing it way less (count is ~ matching the forced warmup in the code). 271 | 272 | ## BONUS: Why async-profiler's flamegraph was broken? 273 | 274 | To answer this question we've opened [Unknown Java Frame on JDK-8180450 reproducer](https://github.com/jvm-profiling-tools/async-profiler/issues/673) 275 | which important parts are: 276 | - JIT's [sender frame computation](https://github.com/openjdk/jdk/blob/2baf2516e1d172268ec7c4c066a1b53bb0bf0779/src/hotspot/cpu/x86/frame_x86.cpp#L141) assumes `sp` to not change, but the type check logic perform `push/pop` operations on the stack that modify `sp` while running in Java 277 | - Using [perf map agent](https://github.com/jvm-profiling-tools/perf-map-agent) and running the application with `PreserveFramePointer` works, showing correct stack frames, because `perf` just care about the frame pointer 278 | 279 | `AsyncGetCallTrace`-based profilers need to perform their own mitigation/stack-recovery mechanism and 280 | sadly, while writing this guide, the current version of async-profiler ([v2.8](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.8)) has disabled most of the mitigations, 281 | to save crashes while trying to recover the right stack-frame: just switching to [v2.7](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.7) will make the profiler able again 282 | to correctly report the guilty method(s). 283 | 284 | ```bash 285 | $ ./profiler.sh -d 10 -t -f async-profiler.jfr -e cpu 286 | # JFR format store additional info eg BCI, Line Of Code 287 | $ java -cp build/converter.jar jfr2flame --threads --lines async-profiler.jfr async-profiler-lines.html 288 | ``` 289 | That correctly reports: 290 | [![async-profiler per-thread generated flame graph](images/async/async-profiler-lines.png)](https://htmlpreview.github.io/?https://github.com/RedHatPerf/type-pollution-agent/blob/master/benchmarks/images/async/async-profiler-lines.html) 291 | 292 | 293 | 294 | 295 | -------------------------------------------------------------------------------- /benchmarks/images/async/async-profiler-lines.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | 20 |

Flame Graph

21 |
  
22 |
Produced by async-profiler
23 | 24 |
25 |

Matched:

26 |

 

27 | 300 | -------------------------------------------------------------------------------- /benchmarks/images/async/async-profiler-lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHatPerf/type-pollution-agent/6b91058065e4700163e4d03577d4e6f1d45be9fd/benchmarks/images/async/async-profiler-lines.png -------------------------------------------------------------------------------- /benchmarks/images/async/async-profiler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 |

CPU profile

22 |
  
23 |
Produced by async-profiler
24 | 25 |
26 |

Matched:

27 |

 

28 | 281 | -------------------------------------------------------------------------------- /benchmarks/images/async/async-profiler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHatPerf/type-pollution-agent/6b91058065e4700163e4d03577d4e6f1d45be9fd/benchmarks/images/async/async-profiler.png -------------------------------------------------------------------------------- /benchmarks/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.forked.franz 8 | type-pollution-parent 9 | 0.1-SNAPSHOT 10 | 11 | 12 | type-pollution-benchmarks 13 | 0.1-SNAPSHOT 14 | jar 15 | 16 | 17 | 1.35 18 | 19 | 20 | 21 | 22 | org.openjdk.jmh 23 | jmh-core 24 | ${jmh-version} 25 | 26 | 27 | org.openjdk.jmh 28 | jmh-generator-annprocess 29 | ${jmh-version} 30 | provided 31 | 32 | 33 | 34 | 35 | 36 | 37 | maven-jar-plugin 38 | 3.2.2 39 | 40 | 41 | 42 | true 43 | io.type.pollution.benchmarks.Main 44 | 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-shade-plugin 51 | 3.3.0 52 | 53 | 54 | package 55 | 56 | shade 57 | 58 | 59 | benchmark 60 | 61 | 63 | org.openjdk.jmh.Main 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-compiler-plugin 73 | 74 | 10 75 | 10 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /benchmarks/reports/async-profiler.jfr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHatPerf/type-pollution-agent/6b91058065e4700163e4d03577d4e6f1d45be9fd/benchmarks/reports/async-profiler.jfr -------------------------------------------------------------------------------- /benchmarks/reports/perfasm.txt: -------------------------------------------------------------------------------- 1 | PrintAssembly processed: 31535 total address lines. 2 | Perf output processed (skipped 1.000 seconds): 3 | Column 1: cycles (69834 events) 4 | 5 | Hottest code regions (>1.00% "cycles" events): 6 | 7 | ....[Hottest Region 1].............................................................................. 8 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (101 bytes) 9 | 10 | 0x00007fb54f2d0d97: jne 0x00007fb54f2d0da1 11 | 0x00007fb54f2d0d9d: mov %rax,0x20(%rsi) 12 | 0x00007fb54f2d0da1: je 0x00007fb54f2d0cd2 13 | 0x00007fb54f2d0da7: mov $0xffffffde,%esi 14 | 0x00007fb54f2d0dac: mov %r11,%rbp 15 | 0x00007fb54f2d0daf: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop } 16 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 17 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9) 18 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 19 | ; {runtime_call UncommonTrapBlob} 20 | 0.01% 0x00007fb54f2d0db4: movabs $0x800062248,%rax ; {metadata('io/type/pollution/benchmarks/Context')} 21 | 0x00007fb54f2d0dbe: push %rax 22 | 0.02% 0x00007fb54f2d0dbf: mov %rax,%rax 23 | 0.27% 0x00007fb54f2d0dc2: mov 0x28(%rsi),%rdi 24 | 23.61% 0x00007fb54f2d0dc6: mov (%rdi),%ecx 25 | 1.40% 0x00007fb54f2d0dc8: add $0x8,%rdi 26 | 0x00007fb54f2d0dcc: test %rax,%rax 27 | 0x00007fb54f2d0dcf: repnz scas %es:(%rdi),%rax 28 | 10.78% 0x00007fb54f2d0dd2: pop %rax 29 | 2.76% ╭ 0x00007fb54f2d0dd3: jne 0x00007fb54f2d0ddd 30 | 0.00% │ 0x00007fb54f2d0dd9: mov %rax,0x20(%rsi) 31 | 0.60% ↘ 0x00007fb54f2d0ddd: je 0x00007fb54f2d0d0f 32 | 0x00007fb54f2d0de3: mov $0xffffffde,%esi 33 | 0x00007fb54f2d0de8: mov %r8,%rbp 34 | 0x00007fb54f2d0deb: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop } 35 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 36 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@4 (line 8) 37 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 38 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 39 | ; {runtime_call UncommonTrapBlob} 40 | 0.08% 0x00007fb54f2d0df0: movabs $0x800062448,%rax ; {metadata('io/type/pollution/benchmarks/InternalContext')} 41 | 0.18% 0x00007fb54f2d0dfa: push %rax 42 | 0.02% 0x00007fb54f2d0dfb: mov %rax,%rax 43 | 0x00007fb54f2d0dfe: mov 0x28(%rsi),%rdi 44 | 23.53% 0x00007fb54f2d0e02: mov (%rdi),%ecx 45 | 1.51% 0x00007fb54f2d0e04: add $0x8,%rdi 46 | 0.00% 0x00007fb54f2d0e08: test %rax,%rax 47 | 1.17% 0x00007fb54f2d0e0b: repnz scas %es:(%rdi),%rax 48 | 9.96% 0x00007fb54f2d0e0e: pop %rax 49 | 2.82% ╭ 0x00007fb54f2d0e0f: jne 0x00007fb54f2d0e19 50 | 0.00% │ 0x00007fb54f2d0e15: mov %rax,0x20(%rsi) 51 | 0.59% ↘ 0x00007fb54f2d0e19: je 0x00007fb54f2d0d22 52 | 0x00007fb54f2d0e1f: mov $0xffffffde,%esi 53 | 0x00007fb54f2d0e24: mov %r8,%rbp 54 | 0x00007fb54f2d0e27: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop } 55 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 56 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9) 57 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 58 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 59 | ; {runtime_call UncommonTrapBlob} 60 | 0x00007fb54f2d0e2c: cmp $0x67c70,%r10d ; {metadata('io/type/pollution/benchmarks/DuplicatedContext')} 61 | 0x00007fb54f2d0e33: jne 0x00007fb54f2d0e52 ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0} 62 | .................................................................................................... 63 | 79.33% 64 | 65 | ....[Hottest Region 2].............................................................................. 66 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (55 bytes) 67 | 68 | nul chk table [0x00007fb54f2d1128,0x00007fb54f2d1140] = 24 69 | ---------------------------------------------------------------------- 70 | io/type/pollution/benchmarks/Main.applicationStack(Lio/type/pollution/benchmarks/Context;I)Z [0x00007fb54f2d0c60, 0x00007fb54f2d0ec8] 616 bytes 71 | [Entry Point] 72 | [Verified Entry Point] 73 | [Constants] 74 | # {method} {0x00007fb52d0bf8b8} 'applicationStack' '(Lio/type/pollution/benchmarks/Context;I)Z' in 'io/type/pollution/benchmarks/Main' 75 | # parm0: rsi:rsi = 'io/type/pollution/benchmarks/Context' 76 | # parm1: rdx = int 77 | # [sp+0x30] (sp of caller) 78 | 0.68% 0x00007fb54f2d0c60: mov %eax,-0x14000(%rsp) 79 | 2.38% 0x00007fb54f2d0c67: push %rbp 80 | 1.48% 0x00007fb54f2d0c68: sub $0x20,%rsp ;*synchronization entry 81 | ; - io.type.pollution.benchmarks.Main::applicationStack@-1 (line 37) 82 | 0.00% 0x00007fb54f2d0c6c: mov %rsi,%r8 83 | 0.38% 0x00007fb54f2d0c6f: cmp $0x1,%edx 84 | ╭ 0x00007fb54f2d0c72: je 0x00007fb54f2d0c98 ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} 85 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@2 (line 37) 86 | 1.29% │ 0x00007fb54f2d0c74: mov %edx,%r11d 87 | 0.00% │ 0x00007fb54f2d0c77: dec %r11d 88 | 0.00% │ 0x00007fb54f2d0c7a: cmp $0x1,%r11d 89 | 0.00% │ 0x00007fb54f2d0c7e: je 0x00007fb54f2d0ce7 ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} 90 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@2 (line 37) 91 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 92 | 0.25% │ 0x00007fb54f2d0c80: add $0xfffffffe,%edx 93 | 0.79% │ 0x00007fb54f2d0c83: callq 0x00007fb54f2d0c60 ; ImmutableOopMap{} 94 | │ ;*invokestatic applicationStack {reexecute=0 rethrow=0 return_oop=0} 95 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 96 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 97 | │ ; {static_call} 98 | 0.02% │ 0x00007fb54f2d0c88: add $0x20,%rsp 99 | 0.01% │ 0x00007fb54f2d0c8c: pop %rbp 100 | 3.87% │ 0x00007fb54f2d0c8d: mov 0x108(%r15),%r10 101 | 0.01% │ 0x00007fb54f2d0c94: test %eax,(%r10) ; {poll_return} 102 | 3.97% │ 0x00007fb54f2d0c97: retq 103 | ↘ 0x00007fb54f2d0c98: mov %rsi,%r11 104 | 0x00007fb54f2d0c9b: mov 0x8(%rsi),%r10d ; implicit exception: dispatches to 0x00007fb54f2d0e7d 105 | 0x00007fb54f2d0c9f: movabs $0x800000000,%rsi 106 | 0x00007fb54f2d0ca9: add %r10,%rsi 107 | 0x00007fb54f2d0cac: mov 0x20(%rsi),%r10 108 | 0x00007fb54f2d0cb0: movabs $0x800062248,%r8 ; {metadata('io/type/pollution/benchmarks/Context')} 109 | 0x00007fb54f2d0cba: cmp %r8,%r10 110 | 0x00007fb54f2d0cbd: jne 0x00007fb54f2d0d3a ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 111 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@4 (line 8) 112 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 113 | .................................................................................................... 114 | 15.14% 115 | 116 | ....[Hottest Region 3].............................................................................. 117 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (63 bytes) 118 | 119 | 0x00007fb54f2d0ccc: jne 0x00007fb54f2d0d78 ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 120 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9) 121 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 122 | 0x00007fb54f2d0cd2: mov 0x8(%r11),%r10d 123 | 0x00007fb54f2d0cd6: cmp $0x67840,%r10d ; {metadata('io/type/pollution/benchmarks/NonDuplicatedContext')} 124 | 0x00007fb54f2d0cdd: jne 0x00007fb54f2d0e2c ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0} 125 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9) 126 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 127 | 0x00007fb54f2d0ce3: xor %eax,%eax 128 | 0x00007fb54f2d0ce5: jmp 0x00007fb54f2d0c88 129 | 0.11% 0x00007fb54f2d0ce7: mov 0x8(%rsi),%r11d ; implicit exception: dispatches to 0x00007fb54f2d0e8c 130 | 0.49% 0x00007fb54f2d0ceb: movabs $0x800000000,%rsi 131 | 0x00007fb54f2d0cf5: add %r11,%rsi 132 | 0x00007fb54f2d0cf8: mov 0x20(%rsi),%r11 133 | 0.17% 0x00007fb54f2d0cfc: movabs $0x800062248,%r10 ; {metadata('io/type/pollution/benchmarks/Context')} 134 | 0.42% 0x00007fb54f2d0d06: cmp %r10,%r11 135 | 0x00007fb54f2d0d09: jne 0x00007fb54f2d0db4 ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 136 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@4 (line 8) 137 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 138 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 139 | 0.02% 0x00007fb54f2d0d0f: movabs $0x800062448,%r10 ; {metadata('io/type/pollution/benchmarks/InternalContext')} 140 | 0.00% 0x00007fb54f2d0d19: cmp %r10,%r11 141 | 0x00007fb54f2d0d1c: jne 0x00007fb54f2d0df0 ;*checkcast {reexecute=0 rethrow=0 return_oop=0} 142 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9) 143 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 144 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 145 | 0.01% 0x00007fb54f2d0d22: mov 0x8(%r8),%r11d 146 | 0.84% 0x00007fb54f2d0d26: cmp $0x67840,%r11d ; {metadata('io/type/pollution/benchmarks/NonDuplicatedContext')} 147 | 0x00007fb54f2d0d2d: jne 0x00007fb54f2d0e3f ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0} 148 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9) 149 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 150 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 151 | 0x00007fb54f2d0d33: xor %eax,%eax 152 | 0x00007fb54f2d0d35: jmpq 0x00007fb54f2d0c88 153 | 0x00007fb54f2d0d3a: movabs $0x800062248,%rax ; {metadata('io/type/pollution/benchmarks/Context')} 154 | 0x00007fb54f2d0d44: push %rax 155 | 0x00007fb54f2d0d45: mov %rax,%rax 156 | 0x00007fb54f2d0d48: mov 0x28(%rsi),%rdi 157 | .................................................................................................... 158 | 2.06% 159 | 160 | ....[Hottest Region 4].............................................................................. 161 | c2, level 4, io.type.pollution.benchmarks.Main::lambda$main$0, version 114 (49 bytes) 162 | 163 | ; - java.lang.Thread::isInterrupted@2 (line 1033) 164 | ; - io.type.pollution.benchmarks.Main::lambda$main$0@11 (line 22) 165 | ; {optimized virtual_call} 166 | 0x00007fb54f2d241c: test %eax,%eax 167 | 0x00007fb54f2d241e: jne 0x00007fb54f2d2481 168 | 0x00007fb54f2d2420: mov (%rsp),%r8 169 | 0x00007fb54f2d2424: nopl 0x0(%rax,%rax,1) 170 | 0x00007fb54f2d242c: data16 data16 xchg %ax,%ax ;*invokevirtual isInterrupted {reexecute=0 rethrow=0 return_oop=0} 171 | ; - java.lang.Thread::isInterrupted@2 (line 1033) 172 | ; - io.type.pollution.benchmarks.Main::lambda$main$0@11 (line 22) 173 | 0.62% ↗ 0x00007fb54f2d2430: mov %r8,%rbp 174 | 0.00% │ 0x00007fb54f2d2433: mov %r8,%rsi 175 | 0.00% │ 0x00007fb54f2d2436: mov $0x6,%edx 176 | │ 0x00007fb54f2d243b: callq 0x00007fb54784a780 ; ImmutableOopMap{rbp=Oop } 177 | │ ;*invokestatic applicationStack {reexecute=0 rethrow=0 return_oop=0} 178 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 179 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 180 | │ ; - io.type.pollution.benchmarks.Main::applicationStackBase@3 (line 32) 181 | │ ; - io.type.pollution.benchmarks.Main::lambda$main$0@18 (line 23) 182 | │ ; {static_call} 183 | 0.00% │ 0x00007fb54f2d2440: mov %rbp,%r8 184 | 0.00% │ 0x00007fb54f2d2443: mov 0x320(%r15),%rsi 185 | 0.00% │ 0x00007fb54f2d244a: mov 0x320(%r15),%r10 186 | 0.53% │ 0x00007fb54f2d2451: cmp %rsi,%r10 187 | │ 0x00007fb54f2d2454: jne 0x00007fb54f2d240e ;*invokestatic currentThread {reexecute=0 rethrow=0 return_oop=0} 188 | │ ; - io.type.pollution.benchmarks.Main::lambda$main$0@8 (line 22) 189 | 0.00% │ 0x00007fb54f2d2456: mov 0x270(%r15),%r10 190 | 0.00% │ 0x00007fb54f2d245d: mov 0x14(%r10),%r11d 191 | 0.55% │ 0x00007fb54f2d2461: test %r11d,%r11d 192 | ╰ 0x00007fb54f2d2464: je 0x00007fb54f2d2430 193 | 0x00007fb54f2d2466: mov $0x1,%ebp ;*invokevirtual isInterrupted {reexecute=0 rethrow=0 return_oop=0} 194 | ; - java.lang.Thread::isInterrupted@2 (line 1033) 195 | ; - io.type.pollution.benchmarks.Main::lambda$main$0@11 (line 22) 196 | 0x00007fb54f2d246b: mov $0xffffff4d,%esi 197 | 0x00007fb54f2d2470: mov %r8,(%rsp) 198 | 0x00007fb54f2d2474: data16 xchg %ax,%ax 199 | 0x00007fb54f2d2477: callq 0x00007fb547849e00 ; ImmutableOopMap{[0]=Oop } 200 | ;*ifne {reexecute=1 rethrow=0 return_oop=0} 201 | ; - io.type.pollution.benchmarks.Main::lambda$main$0@14 (line 22) 202 | .................................................................................................... 203 | 1.72% 204 | 205 | ....[Hottest Region 5].............................................................................. 206 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (9 bytes) 207 | 208 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9) 209 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 210 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 211 | ; {runtime_call UncommonTrapBlob} 212 | 0x00007fb54f2d0e2c: cmp $0x67c70,%r10d ; {metadata('io/type/pollution/benchmarks/DuplicatedContext')} 213 | ╭ 0x00007fb54f2d0e33: jne 0x00007fb54f2d0e52 ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0} 214 | │ ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9) 215 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 216 | │ 0x00007fb54f2d0e35: mov $0x1,%eax 217 | │ 0x00007fb54f2d0e3a: jmpq 0x00007fb54f2d0c88 218 | 0.63% │ 0x00007fb54f2d0e3f: cmp $0x67c70,%r11d ; {metadata('io/type/pollution/benchmarks/DuplicatedContext')} 219 | │ 0x00007fb54f2d0e46: jne 0x00007fb54f2d0e60 ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0} 220 | │ ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9) 221 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38) 222 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41) 223 | 0.63% │ 0x00007fb54f2d0e48: mov $0x1,%eax 224 | │ 0x00007fb54f2d0e4d: jmpq 0x00007fb54f2d0c88 225 | ↘ 0x00007fb54f2d0e52: mov $0xffffffc6,%esi 226 | 0x00007fb54f2d0e57: mov %r11,%rbp 227 | 0x00007fb54f2d0e5a: nop 228 | 0x00007fb54f2d0e5b: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop } 229 | ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0} 230 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9) 231 | .................................................................................................... 232 | 1.27% 233 | 234 | ....[Hottest Regions]............................................................................... 235 | 79.33% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104 (101 bytes) 236 | 15.14% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104 (55 bytes) 237 | 2.06% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104 (63 bytes) 238 | 1.72% c2, level 4 io.type.pollution.benchmarks.Main::lambda$main$0, version 114 (49 bytes) 239 | 1.27% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104 (9 bytes) 240 | 0.21% [kernel.kallsyms] native_write_msr (0 bytes) 241 | 0.01% [kernel.kallsyms] perf_event_task_tick (60 bytes) 242 | 0.01% [kernel.kallsyms] xhci_update_erst_dequeue (3 bytes) 243 | 0.01% [kernel.kallsyms] xhci_ring_ep_doorbell (0 bytes) 244 | 0.01% [kernel.kallsyms] xhci_irq (0 bytes) 245 | 0.01% [kernel.kallsyms] read_tsc (0 bytes) 246 | 0.01% [kernel.kallsyms] cpuacct_account_field (7 bytes) 247 | 0.01% [kernel.kallsyms] check_preemption_disabled (35 bytes) 248 | 0.01% [kernel.kallsyms] asm_sysvec_apic_timer_interrupt (0 bytes) 249 | 0.01% [kernel.kallsyms] sync_regs (0 bytes) 250 | 0.00% [kernel.kallsyms] __intel_pmu_enable_all.constprop.0 (0 bytes) 251 | 0.00% [kernel.kallsyms] update_cfs_group (27 bytes) 252 | 0.00% [kernel.kallsyms] timerqueue_add (19 bytes) 253 | 0.00% libjvm.so VMError::is_error_reported (8 bytes) 254 | 0.00% [kernel.kallsyms] __intel_pmu_enable_all.constprop.0 (0 bytes) 255 | 0.16% <...other 101 warm regions...> 256 | .................................................................................................... 257 | 100.00% 258 | 259 | ....[Hottest Methods (after inlining)].............................................................. 260 | 97.80% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104 261 | 1.72% c2, level 4 io.type.pollution.benchmarks.Main::lambda$main$0, version 114 262 | 0.21% [kernel.kallsyms] native_write_msr 263 | 0.01% [kernel.kallsyms] perf_event_task_tick 264 | 0.01% [kernel.kallsyms] xhci_update_erst_dequeue 265 | 0.01% [kernel.kallsyms] xhci_ring_ep_doorbell 266 | 0.01% [kernel.kallsyms] xhci_irq 267 | 0.01% [kernel.kallsyms] cpuacct_account_field 268 | 0.01% [kernel.kallsyms] read_tsc 269 | 0.01% [kernel.kallsyms] __intel_pmu_enable_all.constprop.0 270 | 0.01% [kernel.kallsyms] update_cfs_group 271 | 0.01% [kernel.kallsyms] check_preemption_disabled 272 | 0.01% [kernel.kallsyms] asm_sysvec_apic_timer_interrupt 273 | 0.01% [kernel.kallsyms] update_irq_load_avg 274 | 0.01% [kernel.kallsyms] trigger_load_balance 275 | 0.01% [kernel.kallsyms] __update_load_avg_se 276 | 0.01% [kernel.kallsyms] sync_regs 277 | 0.00% [kernel.kallsyms] update_load_avg 278 | 0.00% [kernel.kallsyms] reweight_entity 279 | 0.00% libjvm.so VMError::is_error_reported 280 | 0.14% <...other 74 warm methods...> 281 | .................................................................................................... 282 | 100.00% 283 | 284 | ....[Distribution by Source]........................................................................ 285 | 99.52% c2, level 4 286 | 0.45% [kernel.kallsyms] 287 | 0.03% libjvm.so 288 | 0.00% libc.so.6 289 | .................................................................................................... 290 | 100.00% 291 | 292 | 293 | -------------------------------------------------------------------------------- /benchmarks/src/main/java/io/type/pollution/benchmarks/Context.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.benchmarks; 2 | 3 | public interface Context { 4 | // some public API 5 | } 6 | -------------------------------------------------------------------------------- /benchmarks/src/main/java/io/type/pollution/benchmarks/ContextUtil.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.benchmarks; 2 | 3 | import java.util.Objects; 4 | 5 | class ContextUtil { 6 | 7 | public static boolean isDuplicatedContext(Context context) { 8 | Context actual = Objects.requireNonNull(context); 9 | return ((InternalContext) actual).isDuplicated(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /benchmarks/src/main/java/io/type/pollution/benchmarks/DuplicatedContext.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.benchmarks; 2 | 3 | class DuplicatedContext implements InternalContext { 4 | @Override 5 | public boolean isDuplicated() { 6 | return true; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /benchmarks/src/main/java/io/type/pollution/benchmarks/InternalContext.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.benchmarks; 2 | 3 | interface InternalContext extends Context { 4 | boolean isDuplicated(); 5 | } 6 | -------------------------------------------------------------------------------- /benchmarks/src/main/java/io/type/pollution/benchmarks/Main.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.benchmarks; 2 | 3 | import org.openjdk.jmh.annotations.CompilerControl; 4 | 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.Executors; 7 | 8 | public class Main { 9 | private static final int STACK_DEPTH = 8; 10 | 11 | public static void main(String[] args) { 12 | // type pollution :( 13 | var ctx = new NonDuplicatedContext(); 14 | for (int i = 0; i < 11000; i++) { 15 | applicationStackBase(ctx); 16 | } 17 | int numThreads = Integer.parseInt(args[0]); 18 | ExecutorService es = Executors.newFixedThreadPool(numThreads); 19 | for (int i = 0; i < numThreads; i++) { 20 | es.submit(() -> { 21 | var dupCtx = new DuplicatedContext(); 22 | while (!Thread.currentThread().isInterrupted()) { 23 | applicationStackBase(dupCtx); 24 | } 25 | }); 26 | } 27 | es.shutdown(); 28 | } 29 | 30 | @CompilerControl(CompilerControl.Mode.DONT_INLINE) 31 | public static boolean applicationStackBase(Context ctx) { 32 | return applicationStack(ctx, STACK_DEPTH); 33 | } 34 | 35 | @CompilerControl(CompilerControl.Mode.DONT_INLINE) 36 | public static boolean applicationStack(Context ctx, int depth) { 37 | if (depth == 1) { 38 | return ContextUtil.isDuplicatedContext(ctx); 39 | } else { 40 | depth--; 41 | return applicationStack(ctx, depth); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /benchmarks/src/main/java/io/type/pollution/benchmarks/NonDuplicatedContext.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.benchmarks; 2 | 3 | class NonDuplicatedContext implements InternalContext { 4 | @Override 5 | public boolean isDuplicated() { 6 | return false; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /benchmarks/src/main/java/io/type/pollution/benchmarks/RequireNonNullCheckcastScalability.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.benchmarks; 2 | 3 | import org.openjdk.jmh.annotations.*; 4 | import org.openjdk.jmh.infra.Blackhole; 5 | 6 | import java.util.Objects; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | @State(Scope.Thread) 10 | @Measurement(iterations = 10, time = 200, timeUnit = TimeUnit.MILLISECONDS) 11 | @Warmup(iterations = 10, time = 200, timeUnit = TimeUnit.MILLISECONDS) 12 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 13 | @Fork(2) 14 | /** 15 | * To have more fun, run this with: 16 | * -prof perfasm 17 | * -prof jfr 18 | * -prof "async:output=flamegraph;dir=/tmp;libPath=/libasyncProfiler.so" 19 | * 20 | * and 21 | * 22 | * --jvmArgs="-javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar" 23 | * 24 | * Interesting case is: 25 | * -p typePollution=true -p fixed=false 26 | */ 27 | public class RequireNonNullCheckcastScalability { 28 | private Context ctx; 29 | 30 | @Param({"false", "true"}) 31 | public boolean typePollution; 32 | 33 | @Param({"false", "true"}) 34 | public boolean fixed; 35 | 36 | @Setup 37 | public void init(Blackhole bh) { 38 | if (typePollution) { 39 | ctx = new NonDuplicatedContext(); 40 | // let's warm it enough to get it compiled with C2 (by default) 41 | for (int i = 0; i < 11000; i++) { 42 | boolean result = fixed ? fixedIsDuplicated(ctx) : wrongIsDuplicated(ctx); 43 | bh.consume(result); 44 | } 45 | // deopt on warmup 46 | } 47 | ctx = new DuplicatedContext(); 48 | } 49 | 50 | private static boolean wrongIsDuplicated(Context ctx) { 51 | return ContextUtil.isDuplicatedContext(ctx); 52 | } 53 | 54 | private static boolean fixedIsDuplicated(Context ctx) { 55 | return Objects.requireNonNull((InternalContext) ctx).isDuplicated(); 56 | } 57 | 58 | @Benchmark 59 | @Threads(1) 60 | @CompilerControl(CompilerControl.Mode.DONT_INLINE) 61 | public boolean isDuplicated1() { 62 | return isDuplicated(); 63 | } 64 | 65 | @Benchmark 66 | @Threads(2) 67 | @CompilerControl(CompilerControl.Mode.DONT_INLINE) 68 | public boolean isDuplicated2() { 69 | return isDuplicated(); 70 | } 71 | 72 | private boolean isDuplicated() { 73 | if (fixed) { 74 | return fixedIsDuplicated(ctx); 75 | } 76 | return wrongIsDuplicated(ctx); 77 | } 78 | } -------------------------------------------------------------------------------- /benchmarks/tools/perfasm.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHatPerf/type-pollution-agent/6b91058065e4700163e4d03577d4e6f1d45be9fd/benchmarks/tools/perfasm.jar -------------------------------------------------------------------------------- /example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.forked.franz 8 | type-pollution-parent 9 | 0.1-SNAPSHOT 10 | 11 | 12 | type-pollution-example 13 | 0.1-SNAPSHOT 14 | jar 15 | 16 | 17 | 18 | 19 | maven-jar-plugin 20 | 3.2.2 21 | 22 | 23 | 24 | true 25 | io.type.pollution.example.Main 26 | 27 | 28 | 29 | 30 | 31 | org.apache.maven.plugins 32 | maven-compiler-plugin 33 | 34 | 8 35 | 8 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example/src/main/java/io/type/pollution/example/Main.java: -------------------------------------------------------------------------------- 1 | package io.type.pollution.example; 2 | 3 | import java.io.Serializable; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | import java.util.function.Consumer; 7 | 8 | interface I1 { 9 | void do1(); 10 | } 11 | 12 | interface I2 { 13 | 14 | void do2(); 15 | } 16 | 17 | interface I3 extends I1, I2 { 18 | } 19 | 20 | class A implements I1 { 21 | 22 | @Override 23 | public void do1() { 24 | 25 | } 26 | } 27 | 28 | class B implements I3 { 29 | 30 | @Override 31 | public void do2() { 32 | 33 | } 34 | 35 | @Override 36 | public void do1() { 37 | 38 | } 39 | } 40 | 41 | class C implements I3 { 42 | 43 | @Override 44 | public void do2() { 45 | 46 | } 47 | 48 | @Override 49 | public void do1() { 50 | 51 | } 52 | } 53 | 54 | public class Main { 55 | 56 | public static void main(String[] args) { 57 | int numThreads = 2; 58 | int loopCount = 1_000_000_000; 59 | ExecutorService es = Executors.newFixedThreadPool(numThreads); 60 | Object a = new A(); 61 | I3 b = new B(); 62 | I3 c = new C(); 63 | for (int i = 0; i != numThreads; i++) { 64 | es.submit(() -> { 65 | for (int j = 0; j != loopCount; j++) { 66 | foo(b); 67 | goo(b); 68 | consumeAsI2(I2::do2, b); 69 | castToI1(c); 70 | castToI2(c); 71 | consumeAsI2(I2::do2, c); 72 | tryCastTo(a, I2.class); 73 | } 74 | }); 75 | } 76 | es.shutdown(); 77 | } 78 | 79 | public static void tryCastTo(Object o, Class toCast) { 80 | if (toCast.isInstance(o)) { 81 | toCast.cast(o); 82 | } 83 | } 84 | 85 | public static boolean foo(I3 i) { 86 | return I1.class.isInstance(i); 87 | } 88 | 89 | public static boolean goo(I3 i) { 90 | // same line of code!! 91 | if (i instanceof I2 && I3.class.isAssignableFrom(i.getClass())) { 92 | return true; 93 | } 94 | return false; 95 | } 96 | 97 | public static void consumeAsI2(Consumer innocentConsumer, I2 i) { 98 | innocentConsumer.accept(i); 99 | } 100 | 101 | public static void castToI1(Object o) { 102 | I1.class.cast(o).do1(); 103 | } 104 | 105 | public static void castToI2(Object o) { 106 | ((I2) o).do2(); 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.forked.franz 7 | type-pollution-parent 8 | 0.1-SNAPSHOT 9 | pom 10 | 11 | 12 | UTF-8 13 | 14 | 15 | 16 | example 17 | agent 18 | benchmarks 19 | 20 | 21 | 22 | 23 | 24 | 25 | maven-compiler-plugin 26 | 3.10.1 27 | 28 | 1.8 29 | 1.8 30 | 31 | 32 | 33 | maven-source-plugin 34 | 3.2.1 35 | 36 | 37 | 38 | jar-no-fork 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | --------------------------------------------------------------------------------