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 :如下
- 内存对齐区是什么? 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”字符串目标
- 题外话: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()