Основы: объект

Java
Основы: объект

1 Структура памяти объекта и сжатие указателя

//hotspot的oop.hpp文件中class oopDesc
class oopDesc {
  friend class VMStructs;
  private:
  volatile markOop  _mark; //对象头部分
  union _metadata {  // klassOop 类元数据指针
    Klass*      _klass;   
    narrowKlass _compressed_klass;
  } _metadata;
  • Память данных экземпляра объекта состоит из трех частей:заголовок объекта,фактическая область данных,область выравнивания памяти
  • Структура заголовка объекта следующая: в основном это связано с блокировками, хэш-кодом и сборкой мусора; поскольку содержание механизма блокировки слишком длинное, здесь не будет объясняться; структура памяти markWord (markOop) связана с блокировками как следует
  • Выравнивание памятиЧто такое площадь?Система автоматического управления памятью HotSpot VM требует, чтобы начальный адрес объекта был целым числом, кратным 8 байтам, иными словами, размер объекта должен быть целым числом, кратным 8 байтам. Таким образом, когда часть данных экземпляра объекта не выровнена, ее необходимо заполнить завершением выравнивания.
  • выравнивание памятивыгода
    • Хорошо для управления памятью
    • Более быстрое чтение ЦП, ЦП получает данные из памяти не по одному, а в соответствии с длиной, которую ЦП может обработать, например, 32-битная машина, это 4-байтовый блок памяти; когда только когда есть два из них, выбор обрабатывается процессором памяти. Если вам нужно три байта, распределенных по двум разным блокам памяти (четырехбайтовые блоки памяти), вам нужно прочитать память дважды (если один и тот же блок памяти существует, вам нужно прочитать его только один раз). Когда объекты разумно выровнены в соответствии с определенными правилами, ЦП может запросить наименьший объем памяти и увеличить скорость выполнения ЦП.
  • сжатие указателя
    • Как видно на рисунке выше, в 64-битной jvm MarkWord of Object будет в два раза больше, чем в 32-битной, на самом деле klassOop тоже удваивается, чтобы занимать 64-битную (длина массива фиксированные четыре байта). Ширина указателя увеличивается, но для памяти кучи меньше 4G кажется, что 64-битные указатели не используются. Можно ли это оптимизировать? Ответ - сжатие указателя
    • Сжатие указателя — это использование сжатых инструкций JVM Implant, Encoding, Decoding
    • Какая информация сжимается
      • Объекты, которые будут сжаты: атрибуты класса, информация заголовка объекта, типы ссылок на объекты, типы массивов объектов.
      • Несжатые объекты: локальные переменные, элементы стека, входные параметры, возвращаемые значения, указатели NULL.
    • Когда сжатие указателя включено, размер klassOop можно изменить с 64-битного на 32-битный; размер объекта можно увидеть в следующем конкретном сравнении:JVM - Анализ сжатия указателя заголовка объекта JAVA OBJECT HEADER
    public static void main(String[] args){
        Object a = new Object(); // 16B   关闭压缩还是16B,需要是8B倍数;12B+填充的4B
        int[] arr = new int[10]; // 16B   关闭压缩则是24B
    }
    
    public class ObjectNum {
        //8B mark word
        //4B Klass Pointer   如果关闭压缩则占用8B
        //-XX:-UseCompressedClassPointers或-XX:-UseCompressedOops,
        int id;        //4B
        String name;   //4B  如果关闭压缩则占用8B
        byte b;        //1B  实际内存可能会填充到4B
        Object o;      //4B  如果关闭压缩则占用8B
    }
    
    • Почему память кучи не должна превышать 32G, когда включено сжатие указателя, а указатель использует 32 бита, почему максимальная используемая память не 4G, а 32G?
      JVM требует, чтобы начальная позиция объекта была выровнена с кратным 8 байтам Это может быть использовано для увеличения диапазона выбора адреса, который теоретически может быть увеличен до2^11 * 4G. Но jvm просто перемещает указатель влево на три позиции, поэтому2^3 * 4G = 32G. еслиБолее 32G, сжатие указателя не удастся. Если размер кучи GCНиже 4G, напрямую обрезать старшие 32 бита, избегая процесса кодирования и декодирования
    • Включить сжатие указателя-XX:+UseCompressedOops(Включено по умолчанию), чтобы отключить сжатие указателя:-XX:-UseCompressedOops

2 Несколько основных методов Object

  • собственный метод
    • private static native void registerNatives()Свяжите собственный метод, определенный Object, с программой Java.registerNatives в классе объектов
    • public final native Class<?> getClass()Получить метаданные класса java
    • public native int hashCode()Получить хэш-код объекта
    • protected native Object clone() throws CloneNotSupportedExceptionПолучить клон объекта, мелкая копия
    • public final native void notify()Разбудить поток, ожидающий в очереди ожидания блокировки объекта.
    • public final native void notifyAll()Подобно notify(), пробуждает все потоки в очереди waitSet, ожидающие блокировки объекта.
    • public final native void wait(long timeout)Снимите блокировку объекта и войдите в очередь waitSet блокировки объекта.
  • общий метод
    public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode());}
    public boolean equals(Object obj) { return (this == obj);}
    public final void wait(long timeout, int nanos) throws InterruptedException;
    //都是基于native void wait(long timeout)实现的
    public final void wait() throws InterruptedException;
    wait(long timeout, int nanos)、wait() 
    //jvm回收对象前,会特意调用此方法 
    protected void finalize() throws Throwable; 
    

3 == , equals, Comparable.compareTo, Comparator.compara Четыре метода сравнения

Если порядок сортировки не указан, порядок сортировки по умолчанию в Java — по возрастанию, от меньшего к большему.

  • ==, (A) — это значение для сравнения между примитивными типами (B) также является значением для сравнения между примитивными и инкапсулированными типами (C) — это адрес памяти для сравнения между ссылочными типами
  • equals(Object o), что можно увидеть в базовом методе Objectpublic boolean equals(Object obj) { return (this == obj);}Используйте == для сравнения. Преимущество метода equals в том, что мы можем переопределить метод
  • Comparable.compareTo — абстрактный метод в интерфейсе Comparable, если объект реализует этот интерфейс, его можно отсортировать с помощью Collections.sort(List col). Далее посмотрим, как реализован исходный код
    Collections.java
    //Collections.sort(List<T> list),调用的是List的sort方法
    public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }
    
    Сортировка списка вызывает Arrays.sort
    List.java
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
    
    Если Comparator c равен нулю, вызовите Arrays.sort(Object[] a) ; наконец, вызовите метод LegacyMergeSort (сортировка слиянием) для обработки
    Arrays.java
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
    
    Фрагмент кода в методе LegacyMergeSort; последний нижний слой должен использоватьСортировка слияниеми сравнить, чтобы отсортировать
    Arrays.java
    ......
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low &&
                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }
    
  • Компаратор также является интерфейсом, но предоставляет более богатые операции, которые необходимо реализовать.int compare(T o1, T o2)метод
    Компаратор предоставляет несколько часто используемых статических методов thenComparing, reverseed, reverseOrder (объект операции должен реализовать Comparator или Comparable); его можно использовать с List.sort, Stream.sorted, Collections.sort.
    @Data
    @AllArgsConstructor
    static class Pair implements Comparator<Pair>, Comparable<Pair> {
        Integer one;
        Integer two;
        @Override
        public String toString() { return one + "-" + two; }
        @Override
        public int compareTo(Pair o) { return one.compareTo(o.one);  }
        @Override
        public int compare(Pair o1, Pair o2) {return o1.compareTo(o2);}
    }
    public static void main(String[] args) {
        List<Pair> col = Arrays.asList( new Pair(4, 6), new Pair(4, 2),new Pair(1, 3));
        col.sort(Comparator.reverseOrder());
        System.out.println("----------------");
        col.stream().sorted(Comparator.comparing(Pair::getOne).thenComparing(Pair::getTwo))
                .forEach(item ->  System.out.println(item.toString()) );
    }
    
    Collections.sort по умолчанию сортирует в порядке возрастания, вы можете видеть, что reverseOrder меняет порядок на противоположный; использование столбца thenComparing позволяет сначала оценить размер Pair::getOne, а если они равны, оценить размер Pair::getTwo для сортировки.
    result:
    4-6
    4-2
    1-3
    ----------------
    1-3
    4-2
    4-6
    

4 Переопределение и перегрузка методов

  • методпереписатьОтносится к определениям подклассов и методам родительского класса.Имена, параметры и порядок одинаковыметод; следует отметить, что подкласс переопределяет методмодификаторБолее строгого быть не может, то есть модификатор метода родительского класса защищен, а подкласс не может использовать приватную модификацию, но может использовать публичную, бросающуюаномальныйОн также не может быть определен шире, чем метод родительского класса.
  • методперегрузкаОпределены и существующие методы в том же классеСогласованные имена, но несогласованные параметры или порядок параметровметод (возвращаемое значение не может определить перегрузку метода)
  • Перегруженные методы могут быть определены во время компиляции (полиморфизм времени компиляции), в то время как переопределенные методы должны быть определены во время выполнения (полиморфизм времени выполнения, мы часто говорим полиморфизм).
    Три необходимых условия для полиморфизма 1. Существует отношение наследования 2. Подкласс переопределяет метод родительского класса 3. Ссылка родительского класса указывает на объект подкласса

5 Можно ли переопределить конструктор

Конструктор уникален для каждого класса и не может быть унаследован подклассами, поскольку конструктор не имеет возвращаемого значения, а подкласс не может определить тот же метод, что и конструктор родительского класса. Но внутри одного класса конструктор может быть перегружен

public class TestEquals {
    int i;
    public TestEquals() {   i = 0; }
    //构造方法重载
    public TestEquals(int i) {   this.i = i } 
}

6 Эквиваленты объекта и hashCode

equals используется для сравнения двух объектов. Этот метод можно переопределить для реализации пользовательского метода сравнения, а hashCode используется для получения хеш-значения объекта, и этот метод также можно переопределить. Когда объект хранится на карте, он сначала использует Object.hashCode, чтобы определить, отображается ли он в одном и том же месте.Если он находится в одном и том же бите отображения, затем используйте equals, чтобы сравнить, являются ли два объекта одинаковыми.

7 равно, в чем проблема с разным hashCode?

Если переписывание equals приводит к тому, что объекты сравниваются одинаково, но hashCode отличается, это нарушает спецификацию JDK; и при использовании HashMap для хранения может быть несколько объектов, которые мы определяем как одинаковые, что закопает яму для нашей логики кода. .

8 Object.wait и Thread.sheep

Object.wait необходимо использовать в синхронизированном модифицированном коде, он откажется от ЦП и откажется от состояния удержания блокировки объекта. Thread.sleep просто приостанавливается, отказывается от ЦП и не освобождает ресурсы блокировки.

9 Использование метода финализации

  • Если объект переопределяет метод finalize, jvm зарегистрирует текущий объект в очереди ReferenceQueue FinalizerThread. Когда у объекта нет других надежных ссылок для сборки мусора, jvm решит, что объект существует в ReferenceQueue и в настоящее время не будет переработан. После этого FinalizerThread (независимо от потока сборки мусора) берет объект из ReferenceQueue, выполняет пользовательский метод финализации и удаляет объект из очереди после окончания, чтобы его можно было собрать в следующий раз.
  • finalize приведет к тому, что объект будет перезапущен позже, что может привести к переполнению памяти, используйте его с осторожностью
  • Разница между finally и finalize
    • finally — это ключевое слово Java, используемое для обработки исключений в сочетании с попыткой
    • Если я вернусь раньше, наконец, будет ли выполнен блок кода finally?
      Продолжение, прерывание и возврат в попытке не могут обойти выполнение блока кода finally и, наконец, будут выполнены после завершения попытки.
  • Похожие ключевые слова
    • final изменяет класс, класс не может быть унаследован; изменяет метод, метод не может быть переопределен; изменяет переменную, переменная не может указывать на новое значение; изменяет массив, ссылка на массив не может указывать на новый массив, но элементы массива могут быть изменены
    • Если объект окончательно изменен, каковы способы объявления и назначения переменных?
    • Окончательная модификация обычных переменных: 1. Объявление при определении 2. Объявление блока кода в классе 3. Объявление конструктора
    • Окончательная модификация статических переменных: 1. Объявление во время определения 2. Объявление статических блоков кода внутри класса

10 Какие есть способы создания объектов

  • 1. Используйте новый для создания
  • 2. Используйте отражение для получения класса в newInstance()
  • 3. Вызвать метод clone() объекта
  • 4. Полученные путем десериализации, такие как:ObjectInputStream.readObject()

11 Угадай количество созданных объектов

  • String one = new String("Hello");
    два объектаипеременная стека: первая переменная стека и новый объект экземпляра String(), строковый объект "hello"
  • Отступление: string.intern(); intern сначала оценивает, существует ли такая же строка в пуле констант, и возвращает ссылку, если она существует; в противном случае ссылка на строку, которая первой появляется в куче, записывается в пул констант, и ссылка возвращается.
    Если он выполняется первымString s = "hello" ;Это эквивалентно выполнению intern(); сначала создайте «hello» в пуле констант, сохраните ссылку A в пуле констант и верните ее в s. В этот момент String("hello").intern() вернет ссылку A пула констант.
    String one = "hello";
    String two = new String("hello");
    String three = one.intern();
    System.out.println(two == one);
    System.out.println(three == one);
    
    result:
    false  // one虽然不等于two;但是它们具体的char[] value 还是指向同一块内存的
    true  // one 和 three 引用相同

12 Проблема копирования объекта

  • ссылка на объекткопия назначенияскопированный эталонный объект,A a = new A(); A b = a;В этот момент a и b указывают на объекты в одной и той же памяти.
  • С помощью метода Object.clone(), если поле имеет тип значения (базовый тип), копируется значение, а если это ссылочный тип, вместо объекта копируется ссылка на объект
    @Getter
    static class A implements Cloneable{
        private B b; 
        private int index;
        public A(){
            b = new B(); index = 1000;
        }
        public A clone()throws CloneNotSupportedException{  return (A)super.clone(); }
    }
    static class B{
    }
    public static void main(String[] args) throws Exception{
        A a = new A();
        A copyA = a.clone();
        System.out.println( a.getIndex() == copyA.getIndex() );
        System.out.println( a.getB() == copyA.getB() );
    }
    
    //返回结果都是true,引用类型只是复制了引用值
    true
    true
    
  • Глубокая копия: используйте сериализованную копию при переопределении метода клонирования (обратите внимание, что необходимо реализовать Cloneable, Serializable)
    public A clone() throws CloneNotSupportedException {
            try {
                ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                ObjectOutputStream out = new ObjectOutputStream(byteOut);
                out.writeObject(this);
                ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
                ObjectInputStream inputStream = new ObjectInputStream(byteIn);
                return (A) inputStream.readObject();
            } catch (Exception e) {
                e.printStackTrace();
                throw new CloneNotSupportedException(e.getLocalizedMessage());
            }
        }
    

Обратите внимание на публичный номер и общайтесь вместе

Справочная статья