1 Object的内存构造和指针压缩理解一下

//hotspot的oop.hpp文件中class oopDescclass oopDesc {  friend class VMStructs;  private:  volatile markOop  _mark; //对象头局部  union _metadata {  // klassOop 类元数据指针    Klass*      _klass;       narrowKlass _compressed_klass;  } _metadata;
  • Object的实例数据内存应用三局部组成的,对象头理论数据区域内存对齐区
  • 对象头布局如下:次要和锁,hashcode,垃圾回收无关;因为锁机制的内容篇幅过长,这里就不多解释了;和锁相干的markWord(markOop)内存布局如下

  • 内存对齐区是什么? HotSpot VM的主动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。因而当对象实例数据局部没有对齐的话,就须要通过对齐填充来补全。
  • 内存对齐益处

    • 有利于内存的治理
    • 更快的CPU读取,CPU从内存获取数据,并不是一个个字节的读取,而是按CPU能解决的长度获取,如32位机,是4个字节的内存块;当只需其中两个字节时,则由内存处理器解决筛选。如果须要三个字节散布在两个不同内存块(四字节的内存块),则须要读取内存两次(如果是存在同一内存块只需一次读取)。而当对象按肯定的规定正当对齐时,CPU就能够起码地申请内存,放慢CPU的执行速度
  • 指针压缩

    • 在上图能够看到,在64位jvm里Object的MarkWord会比32位的大一倍;其实klassOop也扩充一倍占了64位(数组长度局部则是固定四字节)。指针的宽度增大,然而对于堆内存小于4G的,如同也用不到64位的指针。这能够优化吗?答案是就是指针压缩
    • 指针压缩的原理是利用jvm植入压缩指令,进行编码、解码
    • 哪些信息会被压缩

      • 会被压缩对象:类属性、对象头信息、对象援用类型、对象数组类型
      • 不被压缩对象:本地变量,堆栈元素,入参,返回值,NULL这些指针
    • 指针压缩开启,klassOop大小能够由64bit变成32bit;对象的大小能够看看上面的具体比照:JVM - 分析JAVA对象头OBJECT HEADER之指针压缩
    public static void main(String[] args){    Object a = new Object(); // 16B   敞开压缩还是16B,须要是8B倍数;12B+填充的4B    int[] arr = new int[10]; // 24B   敞开压缩则是16B}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个bit,为什么最大可应用内存不是4G而是32G
<br/>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程序链接起来。Object类中的registerNatives
    • public final native Class<?> getClass() 获取java的Class元数据
    • public native int hashCode() 获取对象的哈希Code
    • protected native Object clone() throws CloneNotSupportedException 取得对象的克隆对象,浅复制
    • public final native void notify() 唤醒期待对象锁waitSet队列中的一个线程
    • 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), 在Object根本办法里能够看到public boolean equals(Object obj) { return (this == obj);} 是应用 == 去比拟的。equals办法的益处是咱们能够重写该办法
  • Comparable.compareTo 是接口Comparable里的形象办法;如果对象实现该接口,可应用Collections.sort(List< T> 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);}

    List的sort 则调用了Arrays.sort

    List.javadefault 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 为null,则是调用 Arrays.sort(Object[] a) ;最终调用LegacyMergeSort(归并排序)办法解决

    Arrays.javapublic 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办法里的一段代码;最终底层是应用归并排序和compareTo来排序

    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;    }
  • Comparator也是一个接口,不过提供了更丰盛的操作,须要实现int compare(T o1, T o2)办法
    <br/>Comparator提供了罕用的几个静态方法thenComparing、reversed、reverseOrder(操作对象须要实现Comparator或者Comparable);可配合List.sort、Stream.sorted、Collections.sort应用。

    @Data@AllArgsConstructorstatic 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的col则是先判断Pair::getOne的大小,如果相等则判断Pair::getTwo大小来排序

    result:4-64-21-3----------------1-34-24-6

4 办法的重写和重载

  • 办法的重写是指子类定义和父类办法的名称、参数及程序统一的办法;须要留神的是,子类重写办法修饰符不能更加严格,就是说父类办法的修饰符是protected,子类不能应用private润饰而可用public,抛出的异样也不能比父类办法定义的更广
  • 办法的重载则是同一个类中定义和已有办法的名称统一而参数或者参数程序不统一的办法,(返回值不能决定办法的重载)
  • 重载的办法在编译时就可确定(编译时多态),而重写的办法须要在运行时确定(运行时多态,咱们常说的多态)
    <br/>多态的三个必要条件 1、有继承关系 2、子类重写父类办法 3、父类援用指向子类对象

5 构造方法是否可被重写

构造方法是每一个类独有的,并不能被子类继承,因为构造方法没有返回值,子类定义不了和父类的构造方法一样的办法。然而在同一个类中,构造方法能够重载

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

6 Object的equals和hashCode

equals是用来比拟两个对象是否相等的,能够重写该办法来实现自定义的比拟办法;而hashCode则是用来获取对象的哈希值,也能够重写该办法。当对象存储在Map时,是首先利用Object.hashCode判断是否映射在同一地位,若在同一映射位,则再应用equals比拟两个对象是否雷同。

7 equals一样,hashCode不一样有什么问题?

如果重写equals导致对象比拟雷同而hashCode不一样,是违反JDK标准的;而且当用HashMap存储时,可能会存在多个咱们自定义认为雷同的对象,这样会为咱们代码逻辑埋下坑。

8 Object.wait和Thread.sheep

Object.wait是须要在synchronized润饰的代码内应用,会让出CPU,并放弃对对象锁的持有状态。而Thread.sleep则简略的挂起,让出CPU,没有开释任何锁资源

9 finalize办法的应用

  • 如果对象重写了finalize办法,jvm会把以后对象注册到FinalizerThread的ReferenceQueue队列中。对象没有其余强援用被当垃圾回收时,jvm会判断ReferenceQueue存在该对象,则临时不回收。之后FinalizerThread(独立于垃圾回收线程)从ReferenceQueue取出该对象,执行自定义的finalize办法,完结之后并从队列移除该对象,以便被下次垃圾回收
  • finalize会造成对象延后回收,可能导致内存溢出,慎用
  • finally和finalize区别

    • finally是java关键字,用来解决异样的,和try搭配应用
    • 如果在finally之前return,finally的代码块会执行吗? <br/>try内的continue,break,return都不能绕过finally代码块的执行,try完结之后finally是肯定会被执行的
  • 类似的关键字final

    • final润饰类,该类不能被继承;润饰办法,办法不能被重写;润饰变量,变量不能指向新的值;润饰数组,数组援用不能指向新数组,然而数组元素能够更改
    • 如果对象被final润饰,变量有哪几种申明赋值形式?
    • fianl润饰一般变量:1、定义时申明 2、类内代码块申明 3、结构器申明
    • fianl润饰动态变量:1、定义时申明 2、类内动态代码块申明

10 创建对象有哪几种办法

  • 1、应用new创立
  • 2、使用反射获取Class,在newInstance()
  • 3、调用对象的clone()办法
  • 4、通过反序列化失去,如:ObjectInputStream.readObject()

11 猜猜创建对象的数量

  • String one = new String("Hello");

<br/> 两个对象和一个栈变量:一个栈变量one和一个new String()实例对象、一个"hello"字符串对象

  • 题外话:string.intern();intern先判断常量池是否存雷同字符串,存在则返回该援用;否则在常量池中记录堆中首次呈现该字符串的援用,并返回该援用。

<br/>如果是先执行 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()办法,如果字段是值类型(根本类型)则是复制该值,如果是援用类型则复制对象的援用而并非对象

    @Getterstatic 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,援用类型只是复制了援用值truetrue
  • 深复制:重写clone办法时应用序列化复制,(留神须要实现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());        }    }

关注公众号,大家一起交换

参考文章

  • Object类中的registerNatives
  • 在java中为什么不举荐应用finalize
  • 一个Java对象到底占用多大内存?
  • java对象在内存的大小
  • 为什么须要内存对齐以及对齐规定的简略剖析
  • JVM - 分析JAVA对象头OBJECT HEADER之指针压缩
  • 几张图轻松了解String.intern()