When a new version of Java comes out, the main features are always actively discussed. But there is work that usually remains "invisible": small optimizations in the standard library. They help us by stealthily speeding up our code and demanding nothing in return, and we don't even know anything about them!
This situation is corrected by Tagir Valeev (lany), talking about such optimizations. First, he spoke at Joker 2019 with a talk "Java 9-14: small optimizations", you can watch his video . Then, since the audience really liked it, at JPoint 2020 he developed the topic . And now we decided to make a post for Habr from the second report so that it could not only be seen, but also read.
Further, under the cut, the text will go on behalf of the speaker.
Introduction
We will only look at the most basic things that everyone uses directly or indirectly: strings, collections, and reflection. We are not covering APIs since Java 8. All performance improvements are free when you run your Java 8 code on a newer JVM.
. , , , . Intel Core i7-6820HQ Windows 10. , . , +UseParallelGC. , « , ».
String.hashCode
String.hashCode. , - « »:
@Benchmark
public int calcHashCode() {
return " ".hashCode();
}
Java 12 Java 13 :
- 4 ? , . , . : « » « »:
@Benchmark
public int calcHashCode() {
return " ".hashCode();
}
@Benchmark
public int calcHashCode2() {
return " ".hashCode();
}
, Java 12, «» , «» . Java 13 . ?
-. Java 9 Java 12 hashCode() ( -, Compact Strings):
/** Cache the hash code for the string */
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}
- , . 4 - 0. - . , - , 0 . — , , .
JPoint 2015 « java.lang.String». Java 8 « » - 0, « » . , , String .
, .
layout String Java 8. , 64- JVM String 4 , , . 1 .
, 32- JVM . , - , .
Java 9 , , . , , Latin-1, UTF-16 . 3 , VM, 3 0. , ?
Java 13, String.hashIsZero:
. - :
private int hash; // Default to 0
private boolean hashIsZero; // Default to false;
public int hashCode() {
int h = hash;
if (h == 0 && !hashIsZero) {
h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
if (h == 0) {
hashIsZero = true;
} else {
hash = h;
}
}
return h;
}
hashIsZero? , hashIsCalculated true, .
, hashCode() , . , , -, 0 ( ). , . hashIsCalculated , hashIsCalculated true , 0, .
, String: , , .
, , , . .
String.concat
+, String.concat(). , . :
@Param({"", "is a very very very very very very very very cool conference!"})
String data;
@Benchmark
public String concat() {
return "JPoint ".concat(data);
}
@Benchmark
public String plus() {
return "JPoint " + data;
}
:
, String.concat() Java 8-14 ~3 , + 15 , ~19 . ?
Java. . , breaking change. String.concat() , : , - 0, . , : , .
, .
, . Java 8 String.concat() , +, Java 9 . Java 12 , Java 13 Java 14 String.concat() + 10%.
String.concat(). Java 9 JEP 280 — invokedynamic-. + , String.concat() JEP 280 .
Java 9 :
, Java . , , String.concat(). ?
String.concat() Java 12:
public String concat(String str) {
if (str.isEmpty()) {
return this;
}
if (coder() == str.coder()) {
byte[] val = this.value;
byte[] oval = str.value;
int len = val.length + oval.length;
byte[] buf = Arrays.copyOf(val, len);
System.arraycopy(oval, 0, buf, val.length, oval.length);
return new String(buf, coder);
}
int len = length();
int olen = str.length();
byte[] buf = StringUTF16.newBytesFor(len + olen);
getBytes(buf, 0, UTF16);
str.getBytes(buf, len, UTF16);
return new String(buf, UTF16);
}
, , . , , .
public String concat(String str) {
if (str.isEmpty()) {
return this;
}
return StringConcatHelper.simpleConcat(this, str);
}
static String simpleConcat(Object first, Object second) {
String s1 = stringOf(first);
String s2 = stringOf(second);
// start "mixing" in length and coder or arguments, order is not
// important
long indexCoder = mix(initialCoder(), s2);
indexCoder = mix(indexCoder, s1);
byte[] buf =(indexCoder);
// prepend each argument in reverse order, since we prepending
// from the end of the byte array
indexCoder = prepend(indexCoder, buf, s2);
indexCoder = prepend(indexCoder, buf, s1);
return newString(buf, indexCoder);
}
Java 13 simpleConcat(), . , , mix() prepend(). newArray():
static byte[] newArray(long indexCoder) {
byte coder = (byte)(indexCoder >> 32);
int index = (int)indexCoder;
return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, index << coder);
}
, . . Java 12 newArray() copyOf(), .
.
, , 11 — . , JDK-8247605, Java 16.
simpleConcat():
static String simpleConcat(Object first, Object second) {
String s1 = stringOf(first);
String s2 = stringOf(second);
// start "mixing" in length and coder or arguments, order is not
// important
long indexCoder = mix(initialCoder(), s2);
indexCoder = mix(indexCoder, s1);
byte[] buf =(indexCoder);
// prepend each argument in reverse order, since we prepending
// from the end of the byte array
indexCoder = prepend(indexCoder, buf, s2);
indexCoder = prepend(indexCoder, buf, s1);
return newString(buf, indexCoder);
}
, s1 s2 — . , . . , , . — :
...
String s1 = stringOf(first);
String s2 = stringOf(second);
if (s1.isEmpty()) {
// newly created string required, see JLS 15.18.1
return new String(s2);
}
if (s2.isEmpty()) {
// newly created string required, see JLS 15.18.1
return new String(s1);
}
...
, , , , . , .
Java 16, :
( , ) , , — ~6 .
: , , . , target- Java 9 .
invokedynamic — , .
TreeMap.computeIfAbsent
, Java 8 map:
- putIfAbsent()
- computeIfAbsent()
- computeIfPresent()
- compute()
- merge()
map, , putIfAbsent(), , , . , , . , computeIfAbsent():
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
computeIfAbsent() map, . , null, .
. , : get() - , , put() , . 2 , .
, , map. . Java 8, , map :
TreeMap . — , - . , , .
2017 , Java 10. Code Review, . 2019 , merge(), . Java 15, , :
, . map computeIfAbsent() , . computeIfPresent() , . map , , 10-20 % .
. , , , — .
public BigInteger fibo(int arg) {
if (arg < 1) {
throw new IllegalArgumentException();
}
if (arg <= 2) {
return BigInteger.ONE;
}
return fibo(arg - 1).add(fibo(arg - 2));
}
public static void main(String[] args) {
Fibo fibo = new Fibo();
System.out.println(fibo.fibo(100));
}
100. , . :
Map<Integer, BigInteger> map = new HashMap<>();
private BigInteger calcFibo(int arg) {
if (arg < 1) {
throw new IllegalArgumentException();
}
if (arg <= 2) {
return BigInteger.ONE;
}
return fibo(arg - 1).add(fibo(arg - 2));
}
public BigInteger fibo(int arg) {
BigInteger value = map.get(arg);
if (value == null) {
value = calcFibo(arg);
map.put(arg, value);
}
return value;
}
, , HashMap. calcFibo() , , , .
, :
public static void main(String[] args) {
Fibo fibo = new Fibo();
System.out.println(fibo.fibo(100));
// 354224848179261915075
System.out.println(fibo.map.get(100));
// 354224848179261915075
}
. 100, map. , map:
System.out.println(fibo.map.size());
// 100
100 . , , .
Java 7. . IntelliJ IDEA if fibo() computeIfAbsent(), :
public BigInteger fibo(int arg) {
return map.computeIfAbsent(arg, this::calcFibo);
}
, . null. . map 185. , .
map , . , get() put(), - .
computeIfAbsent() , :
- -.
- , .
- mappingFunction().
- null, null.
- .
- -.
- size 1.
- , 3.
, map, . . - , , . 5 , .
, map, 1. HashMap . , HashMap .
Java 9, , . : ConcurrentModificationException. HashMap .
- computeIfAbsent(), TreeMap HashMap, , , TreeMap . Java 8-14, Java 15 , .
, computeIfAbsent() , . ,
The mapping function should not modify this map during computation.
ArrayList.removeIf
removeIf() Java 8 , :
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
, , Iterator.remove() , , removed, , . Java 8 , .
default- , . ArrayList Java 8, Java 9 . , .
: ArrayList, 0 size-1 :
data = new ArrayList<>();
for (int i = 0; i < size; i++) {
data.add(i);
}
, , . , :
removeAll: list.removeIf(x -> true);
removeHalf: list.removeIf(x -> x % 2 == 0);
removeLast: list.removeIf(x -> x == size - 1);
removeFirst: list.removeIf(x -> x == 0);
removeNone: list.removeIf(x -> false);
, ArrayList.removeIf() Java 8 1000 :
, removeAll removeHalf removeFirst. , ArrayList . , . . , , .
ArrayList subList, , :
. . removeAll 20 , removeHalf — 6 . removeLast removeFirst subList .
«» ? Java 8 subList(0, size).removeIf() , default- . , , .
Java 9:
subList , , Java 9 subList . , . : removeLast removeNone. , subList removeFirst Java 8. .
removeIf() Java 8 :
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
int removeCount = 0;
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
if (filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces
// left by removed elements
final boolean anyToRemove = removeCount > 0;
if (anyToRemove) {
final int newSize = size - removeCount;
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
this.size = newSize;
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
return anyToRemove;
}
, BitSet, , . , BitSet, ArrayList . , ArrayList . . . default-, .
BitSet . , GC .
, removeLast . BitSet :
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
, , , . .
removeAll , newSize 0. , .
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
Java 9? -, , , . subList.
boolean removeIf(Predicate<? super E> filter, int i, final int end) {
Objects.requireNonNull(filter);
int expectedModCount = modCount;
final Object[] es = elementData;
// Optimize for initial run of survivors
for (; i < end && !filter.test(elementAt(es, i)); i++)
;
// Tolerate predicates that reentrantly access the collection for
// read (but writers still get CME), so traverse once to find
// elements to delete, a second pass to physically expunge.
if (i < end) {
…
} else {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return false;
}
}
, i , .
, — , false. , , , .
, - :
if (i < end) {
final int beg = i;
final long[] deathRow = nBits(end - beg); // new long[((n - 1) >> 6) + 1];
deathRow[0] = 1L; // set bit 0
for (i = beg + 1; i < end; i++)
if (filter.test(elementAt(es, i)))
setBit(deathRow, i - beg); // bits[i >> 6] |= 1L << i;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
modCount++;
int w = beg;
for (i = beg; i < end; i++)
if (isClear(deathRow, i - beg)) // (bits[i >> 6] & (1L << i)) == 0;
es[w++] = es[i];
shiftTailOverGap(es, w, end);
return true;
} else { … }
, BitSet « ». nBits(), setBit() isClear long . , , .
BitSet , . removeLast , .
, . , , BitSet , . .
Java 9. Java 9 , . , . .
hashSet.removeIf()
ArrayList, HashSet. - . HashSet : [], [0], [0, 1] . .:
HashSet<List<Integer>> set;
@Setup
public void setup() {
set = IntStream.range(0, 1000)
.mapToObj(i -> IntStream.range(0, i).boxed().collect(Collectors.toList()))
.collect(Collectors.toCollection(HashSet::new));
}
, HashSet . :
@Benchmark
public Set<List<Integer>> removeHalf() {
Set<List<Integer>> copy = new HashSet<>(set);
copy.removeIf(list -> list.size() > 500);
return copy;
}
@Benchmark
public Set<List<Integer>> noRemove() {
return new HashSet<>(set);
}
, . Java 8:
, + , . , , .
Java 9 , 10 % . ? , HashSet.removeIf() ? . Collection.removeIf(), HashSet.iterator().remove(). HashSet.iterator().remove() HashMap.keySet().iterator().remove(). , HashSet HashMap default-, set. HashMap.KeyIterator.remove(), remove(). , HashMap : keySet().iterator(), valueSet().iterator(), entrySet().iterator(), , .
HashIterator. , ? :
removeNode(), , . Java 9 , , .
, . , . - , , .
, , . , , HashMap LinkedHashMap, .
HashMap.containsKey
Java 15 Map. :
HashMap<List<Integer>, String> emptyMap;
HashMap<List<Integer>, String> nonEmptyMap;
List<Integer> key;
@Setup
public void setup() {
emptyMap = new HashMap<>();
nonEmptyMap = new HashMap<>();
nonEmptyMap.put(Collections.emptyList(), "");
key = IntStream.range(0, 500).boxed().collect(Collectors.toList());
}
@Benchmark
public boolean containsInEmpty() {
return emptyMap.containsKey(key);
}
@Benchmark
public boolean containsInNonEmpty() {
return nonEmptyMap.containsKey(key);
}
HashMap. , . -, , 500 . :
Java 14 500 , , Java 15 3 . , -, .
, . HashMap getNode(), - . Java 15 , , .
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
…
}
return null;
}
Java 15 - getNode(). , - , . - , .
final Node<K,V> getNode(Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & (hash = hash(key))]) != null) {
…
}
return null;
}
, containsKey(), get() getOrDefault(). .
computeIfPresent() . null, . .
. , map computeIfPresent().
, computeIfPresent() , getNode(), , , . - computeIfPresent() , null .
Class.getSimpleName()
Reflection. , , . : class.getName(), class.getCanonicalName(), class.getSimpleName(). Class.getName() , , JVM-. Class.getCanonicalName() , , Java-. Class.getSimpleName() . , :
Java 8 getName() , , . getCanonicalName() getSimpleName() , , . , .
Java 11 , . getCanonicalName() getSimpleName() 1,3 , getName(). , .
public String getName() {
String name = this.name;
if (name == null)
this.name = name = getName0();
return name;
}
// cache the name to reduce the number of calls into the VM
private transient String name;
private native String getName0();
getName() . getName(), .
public String getCanonicalName() {
if (isArray()) {
String canonicalName = getComponentType().getCanonicalName();
if (canonicalName != null)
return canonicalName + "[]";
else
return null;
}
if (isLocalOrAnonymousClass())
return null;
Class<?> enclosingClass = getEnclosingClass();
if (enclosingClass == null) { // top level class
return getName();
} else {
String enclosingName = enclosingClass.getCanonicalName();
if (enclosingName == null)
return null;
return enclosingName + "." + getSimpleName();
}
}
getCanonicalName() Java 10, , , . getSimpleName() . , Java 8 , Java. , , java.lang.Class, «» .
Java 8 32- JVM 112 , SoftReference reflection. reflection, , . Java 8 ReflectionData SoftReference. 32- JVM 32 , Reflection , 224 , 8 Reflection.
. - getSimpleName() getCanonicalName() . - , . ReflectionData. ReflectionData .
// reflection data that might get invalidated
// when JVM TI RedefineClasses() is called
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
. , JVMTI-, ReflectionData , ReflectionData.
private transient volatile SoftReference<ReflectionData<T>> reflectionData;
// Incremented by the VM on each call to JVM TI RedefineClasses()
// that redefines this class or a superclass.
private transient volatile int classRedefinedCount;
// Lazily create and cache ReflectionData
private ReflectionData<T> reflectionData() {
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;
ReflectionData<T> rd;
if (reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
// else no SoftReference or cleared SoftReference or stale ReflectionData
// -> create and replace new instance
return newReflectionData(reflectionData, classRedefinedCount);
}
-, SoftReference, . -, redefinedCount, . , int, .
, ReflectionData GC , classRedefinedCount .
private ReflectionData<T> newReflectionData(
SoftReference<ReflectionData<T>> oldReflectionData,
int classRedefinedCount) {
while (true) {
ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
// try to CAS it...
if (Atomic.casReflectionData(
this, oldReflectionData, new SoftReference<>(rd))) {
return rd;
}
// else retry
oldReflectionData = this.reflectionData;
classRedefinedCount = this.classRedefinedCount;
if (oldReflectionData != null &&
(rd = oldReflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
}
}
CAS-, , .
Java 11 ReflectionData :
private static class ReflectionData<T> {
volatile Field[] declaredFields;
…
volatile Class<?>[] interfaces;
+ // Cached names
+ String simpleName;
+ String canonicalName;
+ static final String NULL_SENTINEL = new String();
…
}
(, ), , ( - ). .
public String getCanonicalName() {
ReflectionData<T> rd = reflectionData();
String canonicalName = rd.canonicalName;
if (canonicalName == null) {
rd.canonicalName = canonicalName = getCanonicalName0();
}
return canonicalName == ReflectionData.NULL_SENTINEL ?
null : canonicalName;
}
Java 11 getCanonicalName() . getSimpleName(). , ReflectionData, , 1,3 .
Class.getConstructor
getConstructor(). :
public static class X {
public X() {}
}
public static class X1 {
public X1() {}
public X1(int p1) {}
public X1(int p1, int p2) {}
public X1(int p1, int p2, int p3) {}
public X1(int p1, int p2, int p3, int p4) {}
public X1(int p1, int p2, int p3, int p4, int p5) {}
}
public static class X2 {
public X2() {}
public X2(int p1) {}
public X2(int p1, int p2) {}
public X2(int p1, int p2, int p3) {}
public X2(int p1, int p2, int p3, int p4) {}
public X2(int p1, int p2, int p3, int p4, int p5) {}
public X2(int p1, int p2, int p3, int p4, int p5, int p6) {}
public X2(int p1, int p2, int p3, int p4, int p5, int p6, int p7) {}
public X2(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8) {}
public X2(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, int p9) {}
public X2(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, int p9, int p10) {}
}
, :
@Benchmark
public Constructor<?> getConstructorX() throws NoSuchMethodException {
return X.class.getConstructor();
}
@Benchmark
public Constructor<?> getConstructorX1() throws NoSuchMethodException {
return X1.class.getConstructor();
}
@Benchmark
public Constructor<?> getConstructorX2() throws NoSuchMethodException {
return X2.class.getConstructor();
}
Java 8, , :
90 , JDK Java 9:
, . , . , :
. Java 8 , Java 9 . ?
getConstructor() getConstructor(), Java 8 :
private Constructor<T> getConstructor0(Class<?>[] parameterTypes, int which) throws NoSuchMethodException
{
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
for (Constructor<T> constructor : constructors) {
if (arrayContentsEq(parameterTypes, constructor.getParameterTypes())) {
return getReflectionFactory().copyConstructor(constructor);
}
}
throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
}
. ReflectionData privateGetDeclaredConstructors(). find-first , . , reflection , , . , , , .
getParameterTypes(): . , ( Reflection API , , ).
, Constructor , :
public final class Constructor<T> extends Executable {
…
@Override
public Class<?>[] getParameterTypes() {
return parameterTypes.clone();
}
…
}
, , , , . . Class java.lang, Constructor java.lang.reflect, . , « », . Java 9 getConstructor0():
private Constructor<T> getConstructor0(Class<?>[] parameterTypes, int which) throws NoSuchMethodException
{
ReflectionFactory fact = getReflectionFactory();
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
for (Constructor<T> constructor : constructors) {
if (arrayContentsEq(parameterTypes, fact.getExecutableSharedParameterTypes(constructor))) {
return constructor;
}
}
throw new NoSuchMethodException(methodToString("<init>", parameterTypes));
}
« » ReflectionFactory:
package jdk.internal.reflect;
public class ReflectionFactory {
…
private final JavaLangReflectAccess langReflectAccess;
private ReflectionFactory() {
this.langReflectAccess = SharedSecrets.getJavaLangReflectAccess();
}
…
public Class<?>[] getExecutableSharedParameterTypes(Executable ex) {
return langReflectAccess.getExecutableSharedParameterTypes(ex);
}
…
}
, «». «» JavaLangReflect, java.base. jdk.internal , .
SharedSecrets, , jdk.internal.access , «» JDK, JavaLangReflect:
package jdk.internal.access;
public class SharedSecrets {
private static JavaLangReflectAccess javaLangReflectAccess;
public static void setJavaLangReflectAccess(JavaLangReflectAccess jlra) {
javaLangReflectAccess = jlra;
}
public static JavaLangReflectAccess getJavaLangReflectAccess() {
return javaLangReflectAccess;
}
…
}
JavaLangReflectAccess — , :
package jdk.internal.access;
/** An interface which gives privileged packages Java-level access to
internals of java.lang.reflect. */
public interface JavaLangReflectAccess {
/** Gets the shared array of parameter types of an Executable. */
public Class<?>[] getExecutableSharedParameterTypes(Executable ex);
…
}
AccessibleObject:
package java.lang.reflect;
public class AccessibleObject implements AnnotatedElement {
static {
// AccessibleObject is initialized early in initPhase1
SharedSecrets.setJavaLangReflectAccess(new ReflectAccess());
}
…
}
AccessibleObject JVM, .
— java.lang.reflect, :
package java.lang.reflect;
/** Package-private class implementing the
jdk.internal.access.JavaLangReflectAccess interface, allowing the java.lang
package to instantiate objects in this package. */
class ReflectAccess implements jdk.internal.access.JavaLangReflectAccess {
…
public Class<?>[] getExecutableSharedParameterTypes(Executable ex) {
return ex.getSharedParameterTypes();
}
…
}
, — :
:
, . , , Java .
9 , Java 9-16, , JDK, . , Java 8 , . !
— . , Joker 2020 , -. , .
— Java Champion -1 Java, JVM, concurrency, file-io memory Stack Overflow. Java-, , .