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的实例数据内存使用三部分组成的,对象头实际数据区域内存对齐区
  • 对象头布局如下:主要和锁,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]; // 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个bit,为什么最大可使用内存不是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程序链接起来。对JNI方面了解不多,就不多解释了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)基本类型和封装类型比较也是值比较 对于引用类型之间的比较则是内存地址
  • 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.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 为null,则是调用 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方法里的一段代码;最终底层是使用归并排序和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)方法


    Comparator提供了常用的几个静态方法thenComparing、reversed、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());
    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-6
    4-2
    1-3
    ----------------
    1-3
    4-2
    4-6

4 方法的重写和重载

  • 方法的重写是指子类定义和父类方法的名称、参数及顺序一致的方法;需要注意的是,子类重写方法修饰符不能更加严格,就是说父类方法的修饰符是protected,子类不能使用private修饰而可用public,抛出的异常也不能比父类方法定义的更广
  • 方法的重载则是同一个类中定义和已有方法的名称一致而参数或参数顺序不一致的方法,(返回值不能决定方法的重载)
  • 重载的方法在编译时就可确定(编译时多态),而重写的方法需要在运行时确定(运行时多态,我们常说的多态)


    多态的三个必要条件 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的代码块会执行吗?
      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");


    两个对象和一个栈变量:一个栈变量one和一个new 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;但是它们具体的byte[] 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
  • 深复制:重写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对象的更多相关文章

  1. VBS基础篇 - wscript 对象

    一.wscript对象 描述:提供对 Windows 脚本宿主对象模型根对象的访问.详述:WScript 对象是 Windows 脚本宿主对象模型层次结构的根对象.它可在任何脚本文件中使用,不需要特定 ...

  2. VBS基础篇 - FileSystemObject对象

    文件系统是所有操作系统最重要的部分之一,脚本经常会需要对文件及文件夹进行访问和管理,在Vbs中对桌面和文件系统进行访问的顶级对象是FileSystemObject FSO包含的常见对象有:       ...

  3. VBS基础篇 - RegExp 对象

    正则表达式(RegExp)对象下面的代码说明了RegExp对象的用法: Function RegExpTest(patrn, strng) Dim regEx, Match, Matches '创建变 ...

  4. VBS基础篇 - Err对象

    Err对象是一个具有全局范围的内部对象,含有关于错误的所有信息.On Error Resume next 忽略运行时产生的所有错误On Error Goto 0 取消忽略错误措施主要方法有:Clear ...

  5. VBS基础篇 - Dictionary对象

    Dictionary是存储数据键和项目对的对象,其主要属性有Count.Item.Key,主要方法有Add.Exists.Items.Keys.Remove.RemoveAll. '建立字典 Dim ...

  6. vue基础篇---修改对象或数组的值,页面实时刷新

    这个问题估计大家很难想到,如果一个数组[1,2,3,4],然后我们v-for遍历,我们改变数组的值,arr[1] = 5 ,难道不应该改变么?按理说根据vue的特性应该是改变的,但是事实上确实数组已经 ...

  7. R语言基础篇——数据对象

    1.基本数据类型(numeric,logical,character,NA,double,complex,integer) 2.日期变量 常用函数 Sys.Date()-返回系统当前的日期,Sys.t ...

  8. 一步步学习javascript基础篇(3):Object、Function等引用类型

    我们在<一步步学习javascript基础篇(1):基本概念>中简单的介绍了五种基本数据类型Undefined.Null.Boolean.Number和String.今天我们主要介绍下复杂 ...

  9. VBS基础篇 - 对象(3) - FileSystemObject对象

    VBS基础篇 - 对象(3) - FileSystemObject对象   文件系统是所有操作系统最重要的部分之一,脚本经常会需要对文件及文件夹进行访问和管理,在Vbs中对桌面和文件系统进行访问的顶级 ...

  10. VBS基础篇 - 对象(5) - File对象

    VBS基础篇 - 对象(5) - File对象   描述:提供对文件所有属性的访问,从FSO对象的GetFile方法获得. 使用File对象        要用File对象模型来编程必须先用FileS ...

随机推荐

  1. VMDNAMD命令规则(转载)

    输出体系的整个带电量:measure sumweights $all weight charge 给PDB文件设置周期边界条件:pbc set {54 54 24 } -all 将此晶胞内原子脱除周期 ...

  2. OpenGL学习日志(2020.4之前)

    咳咳,原本这个日志是本机上随便写的一些记录,也没怎么注意可读性和格式,有用信息密度很小,所以实用价值并不大.暂时由于不可抗因素得先鸽一段落了... 后续的日志会升格为模块化的学习记录,(应该)将会有很 ...

  3. 区块链入门到实战(33)之Solidity – 数据类型

    在用任何语言编写程序时,都需要使用变量来存储各种信息.变量是内存空间的名称,变量有不同类型,例如整型.字符串类型等等.操作系统根据变量的数据类型分配内存. Solidity中,变量类型有以下几大类: ...

  4. Python输入input、输出print

    1.输入input input是用于输入数据给变量.通过键盘输入的是字符串,如果需要其他格式,需要做转换.比如int.float类型数据,int() 如下是一个例子: 如果a不进行int转换,那么输入 ...

  5. 尝试MatCap类型shader

    听说MatCap能在低端机上做出很漂亮的pbr效果,就尝试了一下. MatCap全称MaterailCapture,里面存的是光照信息,通过法线的xy分量去采样matcap,得到在该方向法线的光照信息 ...

  6. 关于js重名方法的先后调用问题

    当js中方法重名时,最后引入的js会覆盖前面的引入的js(就是说会调用最后引入的js中的方法)详情参照(main.js与white.js 的a())但是,当最后一个js中存在语法上的错误时(也可以是本 ...

  7. CSS中的包含块

    1.初始包含块,浏览器viewport大小 2.非根元素,position:relative/static,包含块为最近的块级框,表格单元或行内祖先框的内容区 3.非根元素,position:abso ...

  8. 线上环境去除console

    npm i -D babel-plugin-transform-remove-console babel.config.js // 获取 VUE_APP_ENV 非 NODE_ENV,测试环境依然 c ...

  9. 07_Python语法示例(基础语法,文件操作,异常处理)

    1.写程序在终端输出图形 ######## # # # # ######## print("#" * 8) print("#" + " " ...

  10. 10行实现最短路算法——Dijkstra

    今天是算法数据结构专题的第34篇文章,我们来继续聊聊最短路算法. 在上一篇文章当中我们讲解了bellman-ford算法和spfa算法,其中spfa算法是我个人比较常用的算法,比赛当中几乎没有用过其他 ...