1 Object的内存结构和指针紧缩了解一下

//hotspot的oop.hpp文件中class oopDesc
class oopDesc {
friend class VMStructs;
private:
volatile markOop  _mark; //目标头部分
union _metadata {  // klassOop 类元数据指针
Klass*      _klass;
narrowKlass _compressed_klass;
} _metadata;
  • Object的实g G L / –例数据内h y c + : | ~存运用s V ; V ( w h $ #三部分组成的,目标头实践数据区域内存对齐区
  • 目标头布局如下:主要和锁,hashcod$ d d 9 O ie,废物收回有关;因为锁机制的内容篇幅过长,这里就不多解释了;和锁相关的markWord(markOop)内存布局q [ W / * E :如下
    基础篇:Object对象
  • 内存对齐区是什么? HotSpot VM的自动内存办理体系要求目标起始地址必须是8字节的整数倍,换句话说便是目标的巨细必须是8字节的整数倍。因此当目标实例数据部分没有对齐的话,就需求经过对齐填充来补全。
  • 内存对齐好处

    • 有利于内存的办理
    • 更快的CPU读取,CPU从内存获取数据,并不是一个个字节的读取,而是按CPU能处理的长度获取,如32位机,是4个字节的内存块;当只需其中! M n两个字节时,则由内存处理器处理选择。假如需求三个字节散布在两个不同内存块(四字节的内存块),则需求读取内存两次(假如是存在同一内存块只需一次读取)。而当目标按一定的规则合理对齐时,CPU就能够最少地恳求内存,加速CPU的履行速度
  • 指针紧缩

    • 在上图能够看到,在64位jvm里Object的MarkWop 1 p o ] Zrd_ 4 E } H 8 p 1 H会比32位的大一倍;其实klac q `ssOop也扩展一倍占了64位(数组长度部分则是固定四字节)。指针的宽度增大,可是对于堆内存小于4G的,如同也用不到64位的指针。这能够优化吗?答案是便是指针紧缩
    • 指针紧缩的原理是运用jvm植入紧缩3 } 2 : ^指令,进行编码、解码
    • 哪些信息会被紧缩
      • 会被紧缩目标:类属性、目标头信息、目标引证类型、目标数组类型
      • 不被紧缩目标:本地变量,仓库元素,入参,回来值,NULL这些指针
    • 指针紧缩敞开v , 9 q | ; . X,klassOop巨细能够由64bit变成32bit;目标的巨细能够看看下面的详细对比:JVM – 剖析JAVA目标头OBJECT HEADER之指针紧缩
    public static void main(String[] args){
    Object a = new Object(); // 16B   封闭紧g C r m缩仍是16B,需求是8B倍数;* . } u o I c )12B+填充的4B
    int[] arr =A j y new int[10]; // 24B   封闭紧缩则是16B
    }
    p_ m B : ] ( ,ublic class ObjectNum {
    //8B mark word
    //4B Klass PL 5 9ointer   假如封闭紧缩则占用8B
    //-XX:-UseCompresb Z a C ^sedC6 5 T 0 e r Z }lassPointers或-XX:-UseComp& . ^ m & * : . *ressedOops,
    int id;        //4B
    String name;   //4B  假如封闭紧缩则占用8B
    byte b;        //1B  实践内存或许会填充到4B
    Object o8 S e } s w;      //4B  假如封闭紧缩则占用8B
    }
    
    • 为什么敞开指针紧缩时,堆内存最好不要超越32G,指针运用32个bit,为什么最大可运用内存不是4G而是32G

      jvm要L [ p ~ v 3 + g求目标起始方位 z ^ k对齐8字节的倍数,能够运用这点提高选址范围,理论上能够提高到2^11 * 4G。不过jvm仅仅将指针左移三位,因此2^3 * 4G = 32G。假如大于32G,指针紧缩会失效。假如GC堆巨细在 4G以下,直接砍掉高32位,避免了编码解码进程y E _ i
    • 启用指针紧缩-XX:+UseCompressedOops(默许敞开),禁止指针紧缩:-XX:-UseCompressedOops

2 Object的几种根3 M 7 [ ~ y本办法

  • 本地办法
    • private static native void registerNatives() 将Object界说的本地办法和java程序链接起来。Object类中的registerNatives
    • public final native Class<?> getClas8 K 6 V * Ns() 获取java的Class元数据
    • public native int hashCode() 获取目标的哈希Code
    • protected native Object c% B 9 Ylone() throws CloneNotSupportedException 取得目标的克隆目标,浅仿制
    • public final native void notify() 唤醒等候目标锁waitSet行列中的一个线程
    • public final native void notifyAll() 相似notify(),唤醒等候目标锁waitSet行列中的全部线B 5 N { ! ` C
    • public fina4 @ 8 5 / 6ll b [ W D native void wait(long timeout) 开释目标锁,进入目标锁的waitSet行列
  • 普通办法
    public StringR 4 $ - S toString() { return getClass().getName() + { 1 y"@"F d = F b O v j + Integer.toHexString(hashCode(J v w  ?));}
    public boolean equals(Object obj) { return (this == obj);}
    pubS Z n 0 P 3lic final void wait(long timeout, int nanos) throws Interruptel ( K x M c e NdException;
    //都是基于native void wait(long timeout)完成的
    public final void wait() throws InterruptedException;
    wait(long timeout, int nanos)、wait()
    //jvm收回目标前,会特意调用此办法
    protected void finalize() throwsN f % 1 T ] Throwable;
    

3 == 、 equals、Comparab$ X ^le5 B Z 1 9 – R |.compareTo、Comparator.compara 四种比较办法

如不指定排序次序g t {,java里的默许排序次序是升序的,从小到大

  • ==, (A)对于根本类型之间的比较是值 (B)根本J a 2 E – H Q类型和封装类型比较也是值比较 (C)p r {对于引证类型之间的比较则是内存地址
  • eqd l l X Quals(Object o), 在Object根本办{ i L 9法里能够看到public boolean equals(Object obj) { return (this == obj);} 是运用 == 去比较的。equals办法的好处是咱们能够重写该办法
  • Compao S I erable.compareT! S ! H ho 是接口Comparable里的抽象办法;假如目标完成该接口,可运用Collections.sort(List< T> colN L W 5 N ~ F h)进行排序。接下来看看源码怎^ % 9 ? j n ^ 4么完成的
    Collections.java
    //Collections.sort(List<T&} , N v 7 Q h gt; list),调用的是List的sort办法
    public static) 3 - a O ` d G <T extend c q % Sds Comparable<? super T>> void sort(List<A N A QT> list) {
    list.sort(null);
    }
    

    List的sort 则调用了Arrays.sort

    LV y sist.java
    default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<ET A 0 o> i = this.listIterator();#  s / %
    for (Object e : a) {
    i.next();
    i.set((E) e);
    }
    }
    

    假如Co: 5 ; z ! * p H hmparator c 为null,则是调用 Arrays.sort(Objectu 5 . / % I 5 .[] a) ;终究调用LegacyMergeSort(归并排序)办法处Z + ^ G

    Arrays.java
    public sta? ( B N 4 i Jtic <T> void sort(T[] a, Comparator&` 9 5 6 { Flt;? super T> c y 5 s A i) {
    if (c == nulg , V [ ? c B } ml) {
    sort(a);
    } else {
    if (LegacyMergeSort.userRequested)
    legacyMerg% ^ TeSort(a, cX m P |  h f);
    elsR R =e
    TimSort.sort(a, 0, a.length, c, null, 0, 0);
    }
    }
    

    LegacyMergeSort办法里的一段代码;终究底层是运用归并排序和compar( j R a B 5 SeTo来排序

    Arrays.java
    ......
    i1 b Y vf (length < I= E VNSERTIONSORT_THRE- t 5 v [ NSHOLD) {
    for (int i=low; i<high; i++)
    for (int j=i; j>low &&
    ((Comparable) dest[j-1]).compae ? %reTo(dest[j])>0; j--)
    swap(dest, j, j-1);
    return;
    }
    
  • Comparator也是一个接口,不过供给了更丰厚的操作,需求完成int compare(T o1, T o2)办法

    Comparator供给了常用的几个静态办法thenQ 9 * V T 4 u 4Comparing、reversed、reverseOrder(操作目标需求完成Comparator或者ComparabT 9 hle);可配合List.sort、Stream.sorted、Collections.sort运用。

    @Data
    @AllArgsConstructor
    stai U O Z k ytic class Pair implements Comparator<Pair>, Comparable<Pair> {
    Inte4 l c ! K ? Tger one;
    Integer two;
    @OverrideU u D n )
    public String toString()@ O . { 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(o2G E v);}
    }
    public static void main(String[] args) {
    List<Pair> col = Arrays.asList( new P9 / W ^ 7 = y (air(4, 6), new Pair(4,W W 7 2),A 8 Inew Pair(1, 3));
    col.sort(Comparator.revG N W ; t z P merseOrder());
    System.out.println("----------------");
    col.stream().sorted(Comparator.comparing(Pair::getOne).thenComparing(Pair::getTwo))
    .forEQ M v i Q % L Vach(item ->  Syste( o l - d F g s om.out.println(ik & 8 q h z i ;tem.toString()) );
    }
    

    Collections.sort默许_ j 9 E / U是升序排序的,能够看到reverseOrder将次序A ? , J b ?反过来了; 用了thenComparing的col则是先判别Pair::getOne的巨细,假如相等则判别Pair::getTwo巨细来排序

    result:
    4-6
    4-2
    1-3
    ----------------
    1-3
    4-2C ; . z * _
    4-6
    

4 办法的重写和重载

  • 办法的重写是指子类界说和父类办法的称号、参数及次序共同的办法;需求留意的是,子类重写办法润饰符不能愈加严厉,便是说父类办法的润饰符是protected,子类不能运用private润饰而可用public,抛出的异常也不能比父类办法界说的更广
  • 办法的重载则是同一个类中界说和已有办法的称号共同o + c i ] = a ! C而参数或者参数次序不共同的办法,(回来值不能决定办法的重载)
  • 重载的办法在编译时就可确认(编译时多态),而重写的办法需求在~ n 4 U L B 5 { A运行时确认(运行时多态8 U v m,咱们常说的多态)

    多态的三个必要条件 1、有继承关系Z [ U Z G d 2、子类重写父类办法 3、父类引证指向子类目标

5 结构办法是否可被@ T F t ^重写

结构办法~ o 4 T ^ = – 1是每一个类独有的,并不能被子类继承,因为结构办法没有回来值,子类界说不了和父类的结构办法相同的办法。可是 ! $ ^在同一个类中,结构办法能够重载

public class TestEquals {
int i;
public TestEi u J g ! p jquals() {   i = 0; }
//结构办法重载
publicR T s . U R U TestEquals(int i) {   this.i = i }{ B # ` 5
}

6 Object的equals和hashCodey 1 2

equals是用来比较两个目标是否相等的,能够重写该办法来完成自界说的比较办法;而hashW o R –Code则是用来获取目标的哈希值,也能够重写该办法。当目标存储在Map时,是首要运用^ g B Z c ^ W hObject.hashCode判别是否映射在同一方位,若在同一映射位,则再运用equals比较两个目标是否相同。

7 equals相同,has{ c 6 I hhCode不相同有什么问题?

假如重写equals导致目标比较相同而hashCode不相同,是违反JDK规范的;而且当用HashMap存储时,或许会存在多R O ( p e 个咱们自界说以为相同的目标,这样会为咱们代码逻辑埋下坑。

8 Object.wS C 0 $ I ^ait和Thread.sheep

Object.wait是需求在synchronized润饰的代码内运用,会让出CPU,并放弃对目标锁的持有状态。m . @ 2 y o @ ^ +而Thread.sleep则简略的挂起,让出CPU,没有开释任何锁资源

9 finalize办法的运用

  • 假如目标重写了finalize办法,jvm会把当前目标注册到FinalizerThread的Refey – S + q 4 crenceQueue行列中。目标没有其他强引证被当废物收回时,jvm会判别RefP V C IerenceQueue存在该目标,则暂时不收回。之后x ; | ^ Q RFinaliO h Y O ;zerThreas ; T d Dd(独立于废物收t 3 $ l回线程)从Re: * 4 o 0 ( f mferenceQueue取出该目标,履行自界说的finalize办法,完毕之后并从行列移除该目标,以v i _ u k | j便被下次废物收回
  • finalize会形成目标延后收回,或许导致内存溢W * – G (出,慎用
  • finally和finalize区别
    • fiT f =nally是java关键字,用来处理异常的,和try搭配运用
    • 假如在finally之O $ a n q N前return,finally的代码块会履行吗?
      try内的continue,break,re3 , $ @ .turn都] g Y c 2 T –不能绕过finally代码块的履行,try完毕之后fie | v F q o j &nally是一定会被履行的
  • 相似的关键字final
    • final润饰类,该类不能被继承;润饰办法,办法不能被重写;润饰变量[ C J d 6 r 7 y,变量不能指向新的值;润饰数组,数组引证不能指向新数组,可是数组元素能够更改
    • 假如目标被final润饰,变量有哪几种声明赋值方法?
    • fianl润饰普通变量:1、界说时声明 2、类内代码块声明 3、结构器声w v L n x P :
    • fianl润饰d @ s z = e静态变量:1、界说时声明 2、类内静态代码块声明

10 创立目标有哪几种办法

  • 1、运用new创立
  • 2、运用反射获取Class,在newIns 8 ! h } Q | vstance()
  • 3、调用目标的clone()办` A j
  • 4、经过反序列化得到,如:ObjectIp K xnputStream.readObject()

11 猜猜创立目标的数量

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

    两个目标和一个栈变量:一个栈变量one和一个new Strix ung()实例目标、一个”hello”字符串目标
基础篇:Object对象
  • 题外话:string.intern();intern先判别常量池是否存相同字符串,存在则回来该引证;否则在常量池中记W b 7 S 2 3 d录堆中首次出现该字符串的引证) B f * %,并回来该引证。

    假如是E * 9 _ . ` ; i S先履行 String* j Q G X s = "hello" ;相当于履行了intern();先在常量池创立”hello”,而且将引证A存入常7 P s . A 5 N量池,回来给s。此刻String(“hello”).intern()会回来常量池的引证A回来
    String one = "hello";
String two = new String("hello");
String three = one.intern();
System.C 1 9 # eouG ; nt_ M s a.prit Z S r + C 4ntlnz * P F # y(two == one);
System.oV ^ . c -ut.println(three == one);
result:
false  // one尽管不等于two;可是它们详细的char[] value 仍是指向同一块内存的
true  // one 和 three) & z 引证相同

12 目标仿制问, X 5 5 B 3

  • 引证目标的赋值仿制是仿制的引证目标,A a = new A(); A b = a;i ; f ? V ` =刻a和b指向同一块d v r Z @ {内存的目标
  • 运用OX – Mbject.clone()办法,假如字段是值类型(根本类型)则是仿制该值,假如是引证类型则仿制目标的引证而并非目标
    @Getter
    static class A implements Cloneable{
    private B b;
    private intg c E & - ~ c P indt 0 g /ex;
    public A(){
    b = new B(); index = 1000;
    }
    public A clone()throws CloneNotSuppor4 3 z k r | m K #tedExcep} t 5 0 B , Etion{  return (A)super.cl} m L 3 f vone(); }
    }
    static clA D + Aas5 e { u c s B{
    }
    public static void main(String[] args) throws Exception{
    A a = new A();
    A copyA =f 9 n S l a.clone();
    System.out.println( a.getIndex() == copyA.getI^ 7 g a Cnd5 e S P t dex() );
    System.out.println( a.getB() == copyA.getB() );
    }j ( 5
    
    //回来成果都是true,引证类型仅仅仿制了引证值
    true
    true
    
  • 深仿制:重写clone办法时运用序列化仿制,(留意需Y b M x K n N V A求完成CloneableW = s ? &,Serializable)
    public A clone() thrH 5 Q d 6 ^ ` rows CloneNotSup- 8  o N w LportedExc^ T i Jeption {
    try {
    ByteArrayOutputStream byteOut = nG ; 1 0 G kew ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(byteOut);
    out.writeObject(this);
    ByteArrayInputStream byteIn = neN Z N pw ByteArrayInputSta k X a t $ M Kream(byteOut.toBX l 0 h C ?yteArray());
    ObjectInputStream inputStream = new OJ . C sbjectInputSt} f Hream(byteIn);
    return (A) inputStream.readObject();
    } catD ? e bch (Exception e) {
    e.printStackTrace();
    throw newy i Y T v ? CloneNotSupportedException(e.getLocalizedMessage());
    }
    }
    

关注公众号,我们一同交流

参阅文章

  • Object类中的j n 9 F C P , UregZ { iisterNatives
  • 在java中为什么不引荐运用finalize
  • 一个Java目标究竟占用多大内存?
  • java目标在内存的巨细
  • 为什么需求内存对齐以及对齐规o Z ( 5 G则的简略分析
  • JVM – 剖析JAVA目标头OB, o M QJECT HEADER之指针紧缩
  • 几张图轻n G I s 4 Q松了解String.inter– J 7 $ ? x x + n()