右边几种方式会触发类的初始化 使用new关键字实例化对象时
调用一个类的静态方法时
使用反射获取对象时,如果对象没有初始化,会先进行初始化
当初始化一个类时,如果父类还没初始化,会先初始化父类
虚拟机启动时,会先初始化主类(main方法)
被动引用
右边几种方式不会触发类的初始化 通过子类调用父类的静态字段,子类不会初始化
通过数组引用类,不会触发此类的初始化
调用类的常量,不会触发类初始化,因为常量是存在类的常量池中,会转为调用常量的那个类中
实例初始化过程
父类的类构造器clinit
子类的类构造器clinit
父类的实例变量和实例代码块
父类的构造函数
子类的实例变量和实例代码块
子类的构造函数
如果一个类的成员变量用static修饰,则它被称为类变量(静态变量),否则它被称为实例变量。
Java的实例变量和实例代码块都是按照顺序执行的,并且实例变量和实例代码块优先于构造函数执行。
java的静态变量和静态代码块是按照顺序执行的。
实例初始化不一定要在类初始化结束之后才开始初始化,比如static StaticDemo st=new StaticDemo(); 实际上是把实例初始化嵌入到了静态初始化流程中。
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。 沙箱机制:
保证类的唯一性,防止自己写的代码污染Java原生的方法。
比如自定义一个java.lang,String类,JVM从Bootstrap启动器开始寻找String类,但是其中找不到我们写的方法,这就会抛出ClassNotFoundException
三、JVM内存结构图 运行流程图(精简)
运行流程图(详细) JDK7、8内存空间区别
四、程序计数器
线程私有
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器 的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处 理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因 此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程 之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。
此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
可以使用javap 来查看类的字节码文件
程序计数器在当前指令执行时记录下一个指令的标记,方便当前指令完成后快速切换