【《深入理解Java虚拟机》】JVM的秘密—Part1

0.【Java内存区域】

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

对象访问

1.【GC(垃圾回收)】

【堆区GC】

【方法区GC】

【垃圾收集算法】

【垃圾收集器】

【内存分配与回收策略】

2.【JVM性能监控与故障处理工具】

3.【JVM加载执行系列】

3.1 类文件结构

3.2 【虚拟机类加载机制】

3.3【类加载过程】

3.4 【类加载器】

0.【Java内存区域】

  • Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

      线程共享区域:方法区(Method Area)、堆(Heap)

      线程私有区域:虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、程序计数器(Program couter Register)

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

 《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

  • 对象访问

1.【GC(垃圾回收)】

在Java内存运行时区域中,其中程序计数器、虚拟机栈、本地方法栈这三个区域是线程私有的,随着线程生死。栈中的栈帧随着方法的进入和推出执行入栈和出栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这三个区域的内存分配和回收都具备确定性。

Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存可能也不一样,只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,GC主要关注的就是这部分内存。

【堆区GC】

判断对象是否已死?

答:通过对象与GC Root之间是否有引用链存在来判断对象是否已死。这个过程会有2次标记过程,第1次标记会进行筛选,筛选的条件是此对象有没有必要执行finalize方法。

  • 没必要:当对象内部没有覆盖finalize方法,或JVM已经调用finalize方法。
  • 有必要:这个对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低优先级的finalizer线程区执行。这里这个所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。GC 将对F-Queue中的对象进行第二次小规模的标记。

finalize方法是对象逃逸被回收的唯一一次机会。

PS:任何一个对象的finalize()方法只会被系统自动调用一次。如果对象面临下一次回收,他的finalize()方法不会再被执行

可以作为GC Root的对象有:

  • 虚拟机栈(栈帧中的本地变量表)中的引用
  • 方法去中的类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中的JNI(即一般说的Native方法)的引用的对象

强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)

  • 强引用:在程序代码之中普遍存在的,类似“Obj o=new Obj()”,只要强引用还存在,垃圾收集器永远不会回收被引用的对象
  • 软引用:用来描述一些还有用,但是并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收,如果这次回收还是没有足够的内存,才会抛出内存溢出异常。
  • 弱引用:被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  • 虚引用:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被回收器回收时收到一个系统通知。
public class FinalizeEscapeGC {

    public static FinalizeEscapeGC SAVE_HOOK=null;

    public void isAlive() {

        System.out.println("yes,i am still alive:");

    }

    @Override

    protected void finalize() throws Throwable {

        // TODO Auto-generated method stub

        super.finalize();

        System.out.println("finalize method executed!");

        FinalizeEscapeGC.SAVE_HOOK = this;//对象重新添加GC Root

    }

    public static void main(String[] args) throws Throwable {

        SAVE_HOOK=new FinalizeEscapeGC();

        //obj escape dead first

        SAVE_HOOK=null;

        System.gc();



        Thread.sleep(500);

        if(SAVE_HOOK !=null) {

            SAVE_HOOK.isAlive();

        }else {

            System.out.println("no,i am dead");

        }


        //obj escape dead second

        SAVE_HOOK=null;

        System.gc();



        Thread.sleep(500);
        
        if(SAVE_HOOK !=null) {

            SAVE_HOOK.isAlive();

        }else {

            System.out.println("no,i am dead");

        }

    }

}

【方法区GC】

永久代的垃圾回收内容:废弃常量和无用的类

判定一个类是否是“无用的类”:

  • 该类所有的实例都已经回收,也就是Java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的Java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

【垃圾收集算法】

  1. 标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。有2个缺点:一个是效率问题,标记和清除过程的效率都不高。第二个是空间问题:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致:当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
  2. 复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。============================================         现在的商业虚拟机都是采用这种复制收集算法回收新生代 :Eden : Survivor  =  8:1   每次使用Eden和其中一块Survivor区,当回收时,将Eden和Survivor还存活着的对象一次性地拷贝到另外一块Survivor空间上, 最后清理掉Eden和刚才用过的Survivor空间。当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。
  3. 标记-整理算法:针对于老年代—-存活率比较高的区域来说,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
  4. 分代收集算法:根据对象的存活周期的不同将内存话费为几块。一般把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。新生代:

【垃圾收集器】

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

PS:上图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。

并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能是交替执行),用户程序继续运行,而垃圾收集程序运行于另一个CPU上。

  • Serial收集器:最基本、最悠久的收集器。单线程收集器,针对于Client模式下的默认新生代收集器。单线程的意义并不仅仅是说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作, 更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程。Stop The World!《【《深入理解Java虚拟机》】JVM的秘密—Part1》
  • ParNew收集器:Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数:-XX:SurvivorRatio、–XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等,对象分配规则、回收策略等都于Serial收集器完全一样。运行在Server模式下的虚拟机中首选的新生代收集器。只能它与CMS收集器配合使用。《【《深入理解Java虚拟机》】JVM的秘密—Part1》
  • Parallel Scavenge 收集器:新生代收集器,使用复制算法,使用并行的多线程。目的是达到一个可控制的吞吐量。停顿时间越短就越适合需要与用户交互的程序,良好的相应速度能提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。–XX:MaxGCPauseMillis,–XX:GCTimeRatio
  • Serial Old收集器:单线程,使用“标记-整理”算法。在client模式下的虚拟机使用。《【《深入理解Java虚拟机》】JVM的秘密—Part1》
  • Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
  • 《【《深入理解Java虚拟机》】JVM的秘密—Part1》
  • CMS (Concurrent Mark Sweep)收集器:一种以获取最短回收停顿时间为目标的收集器。重视服务的相应速度。《【《深入理解Java虚拟机》】JVM的秘密—Part1》
  • G1收集器:《【《深入理解Java虚拟机》】JVM的秘密—Part1》《【《深入理解Java虚拟机》】JVM的秘密—Part1》
  •  

【内存分配与回收策略】

1.对象优先在Eden分配:大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。

-XX:+PrintGCDetails 这个参数告诉虚拟机在发生垃圾收集行为时,打印内存回收日志,并且在进程退出的时候输出当前内存各区域的分配情况。

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC 很频繁,一般回收速度比较快。

老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随着至少一次的Minor GC(但非绝对,在Parallel Scavenge收集器策略里就有直接进行Major GC的策略选择过程)。比MinorGC 速度慢10倍以上。

2.大对象直接进入老年代大对象即为需要大量连续内存空间的Java对象,最典型的就是那种很长的字符串及数组(byte[])。

-XX:PretenureSizeThreshold参数,只对Serial和Par New两款收集器有效。令大于这个设置值的对象直接进入老年代分配。目的就是避免在Eden区及2个Survivor区之间发生大量的内存拷贝。

3.长期存活的对象将进入老年代:JVM给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,将对象年龄设为1。对象在Survivor区中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认是15岁),就会晋升到老年代。

-XX:MaxTenuringThreshold=1 and -XX:MaxTenuringThreshold=15 两种设置。

4.动态对象年龄判定:如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

5.空间分配担保:在发生Minor GC时,JVM会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

2.【JVM性能监控与故障处理工具】

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

这是Mac JDK1.8 bin目录下的:

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

1.JPS(Java Process Status Tools):

可以列出正在运行的JVM进程,并显示JVM执行主类(Main Class,main()函数所在的类)的名称,以及这些进程的本地JVM唯一ID(LVMID,local virtual Machine Identifier)。对于本地虚拟机进程而言,LVMID与OS的进程ID是一致的。

如果同时启动了多个虚拟机进程,无法根据进程名称定位时,那就只能依赖JPS命令显示主类的功能才能区分了。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

2.jstat:虚拟机统计信息监视工具

用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,是运行期定位虚拟机性能问题的首选工具。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

如果是本地虚拟机进程,vmid和LVMID是一致的,如果是远程虚拟机进程,格式为:

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

exp:

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

注:S0、S1:Survivor1/2,E:Eden,O:Old,M:未知,YGC/FGC:次数,GCT/YGCT/FGCT:时间

3.jinfo:Java配置信息工具—>实时地查看和调整虚拟机地各项参数。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

4.jmap:(Memory Map for Java) java内存映像工具:用于生成堆转储快照(heapdump or dump文件),还可以查询finalize执行队列,Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

5.jhat:JVM Heap Analysis Tool 虚拟机堆转储快照分析工具:与jmap搭配使用,来分析jmap生成的堆转储快照。

jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结后,可以在browser中查看。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

6.jstack(Stack Trace for Java) Java堆栈跟踪工具   用于生成虚拟机当前时刻的线程快照(threaddump or Javacore文件)

线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,目的是定位线程出现长时间停顿的原因,如死锁、死循环、请求外部资源导致的长时间等待。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

3.【JVM加载执行系列】

3.1 类文件结构

代码编译:本地机器码==>字节码。各种不同平台的虚拟机与所有平台都统一使用的程序存储格式:字节码,是构成平台无关性的基石。实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java语言中的各种变量、关键字和运算符号的语义最终都是由多条字节码命令组合而成的。

Class文件是一组以8位字节为基础单位的二进制流。各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符。只有2中数据类型:无符号数和表。无符号数属于基本的数据类型,以u1\u2\u4\u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,可以描述数字、索引引用、数量值或按照UTF-8编码构成字符串值;表是由多个无符号数或其他表作为数据想构成的符合数据类型,通常以“_info”结尾。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

【魔数】:每个class文件的头4个字节,唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件。第5个和第6个字节是此版本号(Minor Version),第7个和第8个是主版本号(Major Version)。Java版本号从45开始,JDK 1.7以上大于等于51.0。

紧接着主次版本号之后的就是常量池入口(表类型数据项目),主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。

字面量:如文本字符串、被声明为final的常量值等。

符号引用:1.类和接口的全限定名;2.字段的名称和描述符;3.方法的名称和描述符

Java代码在Javac编译的时候,并不像C、C++那样“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

在常量池结束之后,紧接着的2个字节代表访问标志,这个标志用于识别一些类或接口层次的访问信息,包含是类还是接口,是否定义public类型,是否定义为abstract类型,类是否被声明为final等。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

       访问标志之后,类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合是一组u2类型的数据集合,class文件中由这三项数据来确定这个类的继承关系

      字段表集合紧跟其后,字段表(field_info)用于描述接口或类中声明的变量,包含了类级变量或实例级变量,但不包含方法内部声明的变量。字段的作用域(public、private、protected修饰符)、是类级别变量还是实例级变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从祝内存读写)、可否序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。

     方法表集合,如同字段表一样。但是方法里的Java代码经过编译器编译称字节码指令之后,存放在方法属性表集合中一个名为“Code”的属性里面。与字段表集合相对应的,如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。但同样的,有可能会出现由编译器自动添加的方法,最典型的就是类构造器“<clinit>”方法和实例构造器“<init>”方法

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需说明所占用的位数。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

通过javap -verbose TestClass,为什么Args_size=1 和Locals = 1?

答:在任何实例方法中,都可以通过“this”关键字访问到此方法所属的对象。因此在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部变量表中也会预留出第一个slot位来存放对象实例的引用,从1开始。如果inc()方法定义为static,那么Args_size=0;

3.2 【虚拟机类加载机制】

       虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。这就是虚拟机的类加载机制。在Java语言里,类型的加载和连接过程都是在程序运行期间完成的,其动态扩展特性就是依赖运行期动态加载和动态连接这个特点实现。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

加载—验证—准备—初始化—卸载这5个阶段的加载顺序是确定的,而解析阶段则不一定。

什么时候加载由虚拟机的具体实现来决定。对于初始化阶段,有且只有4种情况必需立即对类进行“初始化”(加载,验证、准备):

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

Demo:

《【《深入理解Java虚拟机》】JVM的秘密—Part1》《【《深入理解Java虚拟机》】JVM的秘密—Part1》《【《深入理解Java虚拟机》】JVM的秘密—Part1》

对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

上述类中的SuperClass.value的引用直接转化为该NotInitalization类对自身常量池的引用,这个两个类在编译成class好之后就不存在任何联系。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

运行之后没有输出“superclass init”,说明没有触发SuperClass类的初始化阶段,它是一个虚拟机自动生成的、直接继承于java.lang.Object的子类,创建动作由字节码指令newarray触发。

【接口加载过程区别】:当一个类在初始化时,要求其父类全部都已经初始化过了,但是接口不需要其父接口都完成初始化,什么时候用什么时候初始化。

3.3【类加载过程】

【1.加载】 是Class Loading的一个阶段。在加载阶段:虚拟机需要完成以下3件事情:

 

【2.验证】: 是连接阶段的第一步。目的:为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机安全

 2.1 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个Java类型信息的要求。

     这个阶段的验证是基于字节流进行,经过这个阶段的验证之后,字节流才会进入内存的方法区中进行存储。

  • 通过一个类的全限定名来获取定义此类的二进制字节流,是开发期可控性最强的阶段。从哪里获取及怎样获取?《【《深入理解Java虚拟机》】JVM的秘密—Part1》
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区中这些数据的访问入口。

2.2 元数据验证: 

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

2.3 字节码验证:是整个验证过程中最复杂的一个阶段,主要工作是进行数据流和控制流分析。任务是保证被校验类的方法在运行时不会作出危害虚拟机安全的行为。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

2.4 符号引用验证:符号引用—->直接引用,这个转化动作将在连接的第3个阶段——解析阶段中发生。目的:确保解析动作能正常执行,如不能,则抛出一个java.lang.IncompatibleClassChangeError异常的子类。

      符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性的校验,通常校验内容如下:

     1.符号引用中通过字符串描述的全限定名是否能找到对应的类

     2.在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段

     3.符号引用中的类、字段和方法的访问性(private、protected、public、default)是否可以被当前类访问

     4. 。。。。。。

【3.  准备阶段】为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。

  《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

【4.  解析】 虚拟机将常量池内的符号引用替换为直接引用的过程,其关联关系如下:

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

【5. 初始化】 在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源======初始化阶段是执行类构造器<clinit>方法的给过程。

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

  • <clinit>方法对于类或接口并不是必须的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>方法。
  • 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口和类都会生成<clinit>方法。但是不同的是:执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量被使用时,父接口才被初始化。
  • 虚拟机会保证一个类的<clinit>方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行<clinit>方法,其他线程都需要等待,直到活动线程执行<clinit>方法完毕。

3.4 【类加载器】

   把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部实现,以便让程序自己决定如何去获取所需的类。 

对于任意一个类,都需要加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。也就是说,比较两个类是否“相等”。只有在这两个类是由同一个类加载器加载的前提下才有意义;否则,即使这两个类是来源于同一个Class文件,只要加载它们的类加载器不同,那么这两个类就必定不相等。包括:equals()、isAssignableFrom()、isInstance()方法返回的结果。

      3.4.1 【双亲委派模型】

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

《【《深入理解Java虚拟机》】JVM的秘密—Part1》

==============================后续见【《深入理解Java虚拟机》】JVM的秘密—Part2==========================

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/DreamSeeker_1314/article/details/81974430
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注