Приезжай на Яву, научу копать яму, и уговорю быть добрым

Java JVM Spring

Оригинал: Miss Sister Taste (идентификатор публичной учетной записи WeChat: xjjdog), добро пожаловать, пожалуйста, сохраните источник для перепечатки.

Обработка чисел — это основа языка. Если даже 1+1 становится ненадежным, вся программа становится ненадежной.

Рассмотрим этот фрагмент кода:

Integer a = 1;
System.out.println(a);
Integer b = 2;
System.out.println( a.intValue() == b.intValue() );
System.out.println(a.equals(b));

Результат выполнения оказался таким:

-996
true
true

В это время вы осмеливаетесь продолжать писать код?

image-20210725153558913.png

Почему это происходит?

Проще говоря, мы что-то изменили с помощью рефлексии.

Следующий код изменит логику выполнения некоторых базовых операций, и конечно он относится к одной из категорий зарытых ям. Давайте сначала посмотрим на его поведение.

public class StaticBlock {
    static {
        try {
            Class<?> cls = Integer.class.getDeclaredClasses()[0];
            Field f = cls.getDeclaredField("cache");
            f.setAccessible(true);
            Integer[] cache = ((Integer[]) f.get(cls));
            for (int i = 0; i < cache.length; i++) {
                cache[i] = -996;
            }
        } catch (Exception e) {
            e.printStackTrace();
            //silence
        }
    }
}

Программа использует рефлексию, модифицированнуюIntegerсерединаcacheСодержимое переменной приводит к тому, что число в ней становится фиксированным значением. Здесь мы используем-996, то есть никогда не будет 996.

Пока вы найдете способ запустить этот код, класс-оболочка Java Integer бесполезен.

image-20210725153838869.png

Мы можем это сделать, ключ лежит в переменной кеша.

цифровой кэш

В Java есть 8 основных типов.Ввиду объектно-ориентированных характеристик Java им также соответствует 8 типов упаковки, таких как int и Integer.Значение типа упаковки может быть нулевым, и во многих случаях они могут быть присвоены друг другу.

Судя по следующему небольшому коду, его работа претерпела множественные упаковки и распаковки.

public Integer cal() {
 Integer a = 1000;
 int b = a * 10;
 return b;
}

Давайте посмотрим на это на уровне байт-кода.

public java.lang.Integer read();
    descriptor: ()Ljava/lang/Integer;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: sipush        1000
         3: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         6: astore_1
         7: aload_1
         8: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        11: bipush        10
        13: imul
        14: istore_2
        15: iload_2
        16: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        19: areturn

Можно видеть, что такая простая операция многократно включает такие методы, как valueOf и intValue, что указывает на то, что процесс ее вычисления менее эффективен, чем простые числовые операции.

Среди них метод valueOf используется для переноса обычных чисел в Integer, и мы отслеживаем его методы.

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

Чтобы повысить эффективность преобразования, соответствующая связь между i и Integer кэшируется внутри Integer! Таким образом, в следующий раз, когда вы используете его, вы можете найти его напрямую. Переменная кэша — это место для хранения этой промежуточной информации. Если бы мы изменили его с помощью отражения, Integer вел бы себя хаотично!

Более

IntegerCache, который кэширует объекты Integer между низким и высоким уровнем, может быть доступен через-XX:AutoBoxCacheMaxдля изменения верхнего предела.

String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");

Интересно, что у Long тоже есть такой Cache, но его верхняя и нижняя границы фиксированы, как у Byte и Short.

static final Long cache[] = new Long[-(-128) + 127 + 1];S

Double и Float убогие, можно только напрямую новый, а такое кеширование делать нельзя.

В целом, Integer совершенно особенный. Следующий код, даже если мы не изменим магию отражения, его результат все еще не определен.

Integer n1 = 123;
Integer n2 = 123;
Integer n3 = 128;
Integer n4 = 128;

System.out.println(n1 == n2);
System.out.println(n3 == n4);

Это потому, что при нормальных обстоятельствах он выводит true, false; и когда мы используемAutoBoxCacheMaxУвеличьте его верхний предел, он выведет true, true. Конечно же, чтобы сравнивать объекты друг с другом, надежнее использовать равные.

End

Глядя на эту яму по грудь, мои чувства действительно неописуемы. С общей точки зрения этого кода, если провести нормальный ревью, увидеть проблему несложно, но всегда есть кейсы.

Если этот код выложить в сеть, даже если симпатичный одноклассник случайно отправит его на склад во время практики, последствия будут разрушительными. Цель этого кода относительно проста, но если мы немного усложним логику модификации кэш-массива, изменение значения одной переменной будет срабатывать только при определенных условиях, что является фатальным.

image-20210725153939374.png

В конце концов, даже сонар не может быть просканирован, а в jdk все еще есть куча частных переменных, которые ждут, чтобы мы их исследовали!

Об авторе:Мисс сестра вкус(xjjdog), публичная учетная запись, которая не позволяет программистам идти в обход. Сосредоточьтесь на инфраструктуре и Linux. Десять лет архитектуры, десятки миллиардов ежедневного трафика, обсуждение с вами мира высокой параллелизма, дающие вам другой вкус. Мой личный WeChat xjjdog0, добро пожаловать в друзья для дальнейшего общения.