共计 7659 个字符,预计需要花费 20 分钟才能阅读完成。
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]; // 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.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)
办法
<br/>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()); 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-6 4-2 1-3 ---------------- 1-3 4-2 4-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()办法,如果字段是值类型 (根本类型) 则是复制该值,如果是援用类型则复制对象的援用而并非对象
@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 类中的 registerNatives
- 在 java 中为什么不举荐应用 finalize
- 一个 Java 对象到底占用多大内存?
- java 对象在内存的大小
- 为什么须要内存对齐以及对齐规定的简略剖析
- JVM – 分析 JAVA 对象头 OBJECT HEADER 之指针压缩
- 几张图轻松了解 String.intern()