JDK-8059357 : ClassVerifier redundantly checks constant pool entries multiple times
  • Type: Enhancement
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 8u20,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2014-09-29
  • Updated: 2019-08-15
  • Resolved: 2019-03-27
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 13
13 b14Fixed
Related Reports
Blocks :  
Relates :  
Relates :  
Description
If you run the updated jdk9-dev with JDK-8057846 and JDK-8057845 patches applied, and run the class loading benchmark with Nashorn generated classes:

$ ~/trunks/jdk9-dev/build/linux-x86_64-normal-server-release/images/j2sdk-image/bin/java -jar ~shade/8053904/benchmarks.jar -p file=~shade/8053904/classes.jar

(both files are available under the same names at cr.openjdk.java.net)

Then you will see this profile:
 http://cr.openjdk.java.net/~shade/8059357/nashorn-classload-profile-1.txt
 
Notice the significant time is spent in ClassVerifier::verify_invoke_instructions -> ClassVerifier::change_sig_to_verificationType processing. If you look at the ClassVerifier::verify_invoke_instructions code, then you will realize the code can do the Symbol resolution for the given CP index over and over again, when the invoke instruction is referencing the same method signature:

 u2 index = bcs->get_index_u2();
 ...
 Symbol* method_sig = cp->signature_ref_at(index);
 ...
  SignatureStream sig_stream(method_sig);
  int sig_i = 0;
  while (!sig_stream.at_return_type()) {
    sig_i += change_sig_to_verificationType(
      &sig_stream, &sig_types[sig_i], CHECK_VERIFY(this));
    sig_stream.next();
  }
  int nargs = sig_i;

This is not a problem for ConstantPool itself, because taking the Symbol by index is very fast. But it starts to become a problem after we continuously parse the individual symbols from the signature wit SignatureStream. You see this as the hotspot in the profile above. We also redundantly check SignatureVerifier::is_valid_method_signature(method_sig) over and over again.

To check I am not delusional, the very simple instrumentation patch can print the (cp index, method-under-verification hashcode) pairs to see if we indeed doing the same lookup for the same method over and over again:
 http://cr.openjdk.java.net/~shade/8059357/8059357-instrument.patch

...and these are the results:
 http://cr.openjdk.java.net/~shade/8059357/nashorn-verifier-dups.txt.gz (full log, 7 Mb)
 http://cr.openjdk.java.net/~shade/8059357/nashorn-verifier-dups-tail1000.txt

You can see there, we do up to 10K checks for the given (cpi, mhc) in this verification method. The average across the entire log is closer to 4.15 checks per (cpi, mhc).

I think we need to do this thing once to significantly speed up the Verifier. Possible options:
 a) Let ConstantPool cache the parsed signature Symbols and hold the "verified" flags for data that do not require verification context;
 b) Make Verifier to do a single pass over ConstantPool, and verify all MethodRef entries at once.

The rough estimate for performance improvement is at least 5-10% for the entire classloading, depending how much common code we can fold into doing once.
Comments
The benchmark is available at http://hg.openjdk.java.net/code-tools/jmh-jdk-microbenchmarks/file/a644f3612e8d/micros-jdk8/src/main/java/org/openjdk/bench/vm/runtime/VerifySignatures.java
26-03-2019

The split verifier's repeated translation of the same method signature into its verificationTypes over and over again could be avoid by having the verifier do the translation once per-class and save the result until verification of the class completed. At the start of class verification, the verifier would create and initialize a table containing the verificationTypes for all the method signatures in the constant pool. The table would be indexed by a method signature's constant pool index, and each indexed entry would contain a list of a particular signature's verificationTypes, including return type, for that constant pool index. To initialize the table, the verifier would loop through the current class's constant pool, and for each (Interface)MethodRef, look at its NameAndType entry, and pluck the signature from the NameAndType's Utf8 constant pool entry. Next, it would translate the signature's entries into verificationTypes and store the verificationTypes in the table, indexed by the Utf8 constant pool entry number. For example, if a MethodRef's NameAndType pointed to constant pool Utf8 entry 15 that had method signature "(ILjava/lang/String;)J" then the following row, with four entries (or five if a length is needed), would get entered into the table (where 0x10101 is the VerificationType encoding for int and 0x40201 is for long.): 15, 0x10101, Symbol* for "java/lang/String", 0x40201 The signature's stored VerificationType entries could then be retrieved when needed by verify_invoke_instructions(), instead of repeatedly calling change_sig_to_verificationType(). The table would get deallocated when class verification finished. Note that the number of signatures that need to stored could be computed by ClassFileParser and stored in the class's InstanceKlass, where it could be retrieved by the verifier. For each constant pool NameAndType, ClassFileParser checks for valid method or field signatures. This is where the count of the number of signatures could be computed. Knowing the enumber of signatures enables partial static creation of the table. For example, if the count is 50 then the table needs 50 rows, and the number of rows does not need to increase dynamically. A signature's list of VerificationTypes would not eat up a lot of memory because VerificationTypes are only eight bytes.
14-03-2019

[~redestad] I can work on this tomorrow. I'm checking to see if classFileParser.cpp already does the check. If so, then the verifier's check is redundant and can be removed.
21-02-2019

An eager sweep over the constant pool verifying all fields and method signatures means we'd not repeatedly verify the same signature at every callsite. It's pretty common you call the same method more than once in a class, so we'd potentially save quite a bit, and it's a relatively simple change. I was looking to pick this up and attempt a fix this week unless you want to give it a go, [~hseigel]?
21-02-2019

>> Make Verifier to do a single pass over ConstantPool, and verify all MethodRef entries at once. This could potentially be done in classFileParser.cpp. Then no additional cp pass would be needed. Although it looks like classFileParser.cpp already calls verifiy_legal_method_signature() to parse the signatures.
21-02-2019