Sanne / type-pollution-agent

Agent to detect type pollution due to instanceof/checkcast misuses

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Java agent to diagnose JDK-8180450

What is JDK-8180450?

See the issue for full details... 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. CPU will be expended updating the cache, but the cached value won't help avoiding the slow path. 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).

What is this?

It's a Java agent which can be used to identify code which may be suffering from this problem. 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. 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). 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.

Build

$ # From the root dir
$ mvn package

Run

$ # From the root dir
$ java -javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar -jar example/target/type-pollution-example-0.1-SNAPSHOT.jar 

The output, with a default agent configuration, is:

--------------------------
Type Pollution Statistics:
--------------------------
1:      io.type.pollution.example.B
Count:  5364580
Types:
        io.type.pollution.example.I2
        io.type.pollution.example.I3
        io.type.pollution.example.I1
Traces:
        io.type.pollution.example.Main.goo(Main.java:71)
                class: io.type.pollution.example.I2
                count: 1822574
                class: io.type.pollution.example.I3
                count: 1800163
        io.type.pollution.example.Main.foo(Main.java:66)
                class: io.type.pollution.example.I1
                count: 1741843
--------------------------
2:      io.type.pollution.example.C
Count:  3642662
Types:
        io.type.pollution.example.I2
        io.type.pollution.example.I1
Traces:
        io.type.pollution.example.Main.castToI2(Main.java:83)
                class: io.type.pollution.example.I2
                count: 1836278
        io.type.pollution.example.Main.castToI1(Main.java:79)
                class: io.type.pollution.example.I1
                count: 1806384
--------------------------
  • Count: is the number of observed successful checkcast/instanceof/Class::isAssignableFrom/Class::cast/Class::isInstance against a different (interface) type from the last seen
  • Types: is the list of different (interface) types observed
  • Traces: is a list of top method stack traces along with the interface class seen and related count (ie observed secondary super cache invalidations).

note Traces format is compatible with Idea IntelliJ Stack Trace Viewer

The report is an ordered list which types are ordered by increasing Count ie io.type.pollution.example.B is the top type based on it and more likely the one with the highest chance to cause scalability issues.

But what about OpenJDK JIT optimizations that could save any check to be performed?

Due to the complexities of JIT mechanism (Inlining heuristic, method and bytecode Type Profiling, etc etc) the agent cannot detect if a potential treat translates into a real (scalability) performance issue: the smart and wise user should create system/JMH benchmarks to analyze the results of changes and narrow the scope of the agent to ignore the innocent traces.

To narrow the scope of the tracing, users can add a comma separated list of package prefixes (not full qualified class names!!!) to the agent configuration and the agent will place tracing around the usual suspects byte-code instructions just on these:

eg

$ java -javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar=io.other -jar example/target/type-pollution-example-0.1-SNAPSHOT.jar

# IT WOULD PRINT...NOTHING! No packages starting with io.other exists :P 

Said that, cleaning up every instanceof/checkcast misuses is the best way to get rid of any potential scalability issue, without relying on how JIT optimizes specific tests.

Full stack traces are available?

Yes! To enable full-stack trace sampling (now defaulted at 10 ms sampling for each concrete type) just add

-Dio.type.pollution.full.traces=true

And, the output will become:

--------------------------
Type Pollution Statistics:
--------------------------
1:      io.type.pollution.example.B
Count:  5364580
Types:
        io.type.pollution.example.I2
        io.type.pollution.example.I3
        io.type.pollution.example.I1
Traces:
        io.type.pollution.example.Main.goo(Main.java:71)
                class: io.type.pollution.example.I2
                count: 1822574
                class: io.type.pollution.example.I3
                count: 1800163
        io.type.pollution.example.Main.foo(Main.java:66)
                class: io.type.pollution.example.I1
                count: 1741843
Full Traces:
        --------------------------
        io.type.pollution.example.Main.foo(Main.java:66)
        io.type.pollution.example.Main.lambda$main$0(Main.java:55)
        java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
        java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        java.base/java.lang.Thread.run(Thread.java:829)
        --------------------------
        io.type.pollution.example.Main.goo(Main.java:71)
        io.type.pollution.example.Main.lambda$main$0(Main.java:56)
        java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
        java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        java.base/java.lang.Thread.run(Thread.java:829)
--------------------------        
# Rest omitted for brevity....

About

Agent to detect type pollution due to instanceof/checkcast misuses

License:Apache License 2.0


Languages

Language:Java 100.0%