JVM学习笔记三、类加载机制

目录:

  • 类加载机制简介
  • 类加载机制流程

类加载机制简介

类加载机制就是虚拟机把描述类的数据从Class文件中加载到内存中的一种机制,并且在加载的过程中会对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。

类加载机制流程

类从被加载到虚拟机的内存中开始,直到类被卸载出内存为止,它的整个生命周期主要分为包括:加载、验证、准备、初始化、使用、卸载7个阶段。

其中准备、验证、解析这三个部分统称为连接。

而类加载过程是加载、验证、准备、解析、初始化这五个阶段。

其中加载、验证、准备、初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)

另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。


加载:

加载时类加载过程的第一个阶段,此阶段虚拟机需要完成以下三件事情:

1、获取二进制字节流,也就是获取Class文件。

 

Java二进制字节流可以获取的途径有:

  • 从ZIP包中读取,JAR,WAR,EAR格式的基础。
  • 从网络中获取,Applet应用。
  • 运行时计算生成,动态代理技术。
  • 由其他文件生成,JSP应用,由JSP文件生成对应的Class类。

 

2、把字节流代表的静态存储结构(Class文件)转化为方法区运行时的数据结构。

什么意思呢,我们要知道字节码它只是定义了类的一些元数据,实际执行的时候还是需要通过JVM来加载类的元数据。

而类其实就是有大大小小的方法构成的(你会说不是还有属性嘛,是的有属性,但属性不也是为方法服务的嘛),方法区就是JVM加载到内存的那些类的元数据集。

3、在Java堆里生成一个类对象,作为方法区的访问入口。

这个又如何理解呢,首先方法的运行是一条条指令构成的,但这些指令都是需要内存加载后才能执行,这样才能正确得到后续指令的引用。

如常量池的引用,#1、#2、#3这些最后都需要换算成具体的内存地址。

验证:

验证是连接阶段的第一步,确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

验证阶段大致分下面4个动作:

  • 文件格式验证:通过了这个阶段的验证后,字节流才会进入内存的方法区中进行存储。后面的验证会基于方法区的存储结构进行验证,而不再操作字节流进行验证。
  • 元数据验证:该阶段主要对字节码描述的信息进行语义分析,以保证符合Java语言规范要求。
  • 字节码验证:
    • 该阶段主要是通过数据流和控制流分析,确定程序语义是合法的。
    • 对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。
  • 符号引用验证:如常量池中的符号指向是否正确,符号引用验证的目的就是确保解析动作能正常执行。

在验证过程中大致会抛出如下几种错误:

  • IncompatibleClassChangeError。
  • Unsupported major.minor version。
  • IllegalAccessError。
  • NoSuchFieldError。
  • NoSuchMethodError。

准备:

准备阶段是为类变量分配内存并设置类变量初始化的阶段,这些变量所使用的内存当将在方法区中进行分配。

只对类变量进行内存分配(static修饰),不包括实例变量,实例变量将会在对象实例化是随着对象一起分配在Java堆中。

如只会初始化如下变量:

1 public static int I = 1;
2 public static final int J = 2;

解析:

解析的目的就是将常量池内的符号引用替换为直接引用。

符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。符号引用的字面量形式已经明确定义在Java虚拟机规范的Class文件格式中。

直接引用(Direct References):直接引用可以是直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。直接引用与虚拟机实现的内存布局相关,同一个符号引用在不用虚拟机实例上翻译出来的直接引用一般不同。如果有了直接引用,那引用的目标必定已经在内存中存在。

一句话总结:符号引用是以字面量的形式明确定义在常量池中;直接引用是指向目标的指针,或者相对偏移量。


解析动作主要对类或者接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行,分别对应于常量池:

  • CONSTANT_Class_info。
  • CONSTANT_Fieldref_info。
  • CONSTANT_Methodref_info。
  • CONSTANT_InterfaceMethodref_info。
  • CONSTANT_MethodType_info。
  • CONSTANT_Methodref_info。
  • CONSTANT_MethodHandler_info。
  • CONSTANT_invokeDynamic_info。

1、字段的解析:

1 class A extends B implements C, D {
2     private String str; 
3 }

解析字段的顺序:A -> C, D -> B -> java.lang.NoSuchFieldError

  • 先查找本类A,如果包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束。
  • 否则,在接口中查找。将会按照集成关系从下往上递归搜搜各个接口和它的父接口,如果接口中包含了简单名称和字段描述符都于目标相匹配的字段,则返回这个字段的直接引用,查找结束。
  • 否则,在父类中查找,如果在父类中包含了简单名称和字段描述符都于目标相匹配的字段,则返回这个字段的直接引用,查找结束
  • 否则,查找失败,抛出java.lang.NoSuchFieldError异常。

2、类方法的解析:

1 class A extends B implements C, D {
2     private void inc();
3 }

解析顺序:A -> B(父类递归) -> C, D(接口递归) -> java.lang.NoSuchMethodError

  • 如果在类方法表中发现class_index中索引的A是一个接口,哪就直接抛出java.lang.IncompatiableClassChangeError异常。
  • 如果通过了第一步,先查找本类A,是否由简单名称和描述符都于目标相匹配的方法,如果有则返回方法的直接引用,查找结束。
  • 否则,父亲中递归查找是否又简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
  • 否则,在类实现的接口列表及它们的父接口之中查找是否有简单名名称和描述符都与目标相匹配的方法,如果存在匹配的方法,说明类C是一个抽象类,这时查找结束,抛出java.lang.AbastractMethodError异常。
  • 否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError。

3、接口方法的解析:

解析顺序:本接口 -> 父接口递归查找 -> java.lang.NoSuchMethodError ->

  • 与类的方法解析不同,如果在接口方法表中发现class_index中的索引A是个类而不是接口,那就直接抛出java.lang.IncompatiableClassError异常。
  • 否则,先查找本接口,是否有简单名称和描述符都与目标匹配的方法,如果有则返回这个方法的直接引用,查找结束。
  • 否则,在接口的父接口中递归查找,直到java.lang.Object类(查找范围包括Object类)为止,看是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
  • 否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError异常。

初始化:

1、<clinit>类的初始化:静态变量,静态块的初始化。所有的类变量初始化语句和类型的静态初始化器。Java在编译之后会在字节码文件中生成<clinit>方法,称之为类构造器,类构造器同实例构造器一样,也会对静态语句块,静态变量进行初始化。

2、<init>对象的初始化:Java在编译之后会在字节码文件中生成<init>方法,称之为实例构造器。该实例构造器会对语句块,变量进行初始化,并调用父类的构造器。

<clinit>方法是在类加载过程中执行的,而<init>是在对象实例化执行的,所以<clinit>一定比<init>先执行。所以整个顺序就是:

  • 父类静态变量初始化。
  • 父类静态语句块。
  • 子类静态变量初始化。
  • 子类静态语句块。
  • 父类变量初始化。
  • 父类语句块。
  • 父类构造函数。
  • 子类变量初始化。
  • 子类语句块。
  • 子类构造函数。

原文地址:https://www.cnblogs.com/bzfsdr/p/13619895.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


jinfo 命令可以用来查看 Java 进程运行的 JVM 参数,命令如下:[root@admin ~]# jinfo --helpUsage: jinfo [option] &lt;pid&gt; (to connect to running process) jinfo [option] &lt;executable &lt;core&gt; (to connect to a core file) jinfo [option] [serve
原文链接:https://www.cnblogs.com/niejunlei/p/5987611.htmlJava Virtual Machine Stacks,线程私有,生命周期与线程相同,描述的是Java方法执行的内存模型:每一个方法执行的同时都会创建一个栈帧(Stack Frame),由于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法的执行就对应着栈帧在虚拟机栈中的入栈,出栈...
java 语言, 开发者不能直接控制程序运行内存, 对象的创建都是由类加载器一步步解析, 执行与生成与内存区域中的; 并且jvm有自己的垃圾回收器对内存区域管理, 回收; 但是我们已经可以通过一些工具来在程序运行时查看对应的jvm内存使用情况, 帮助更好的分析与优化我们的代码;jps查看系统中有哪些java进程jps 命令类似与 linux 的 ps 命令,但是它只列出系统中所有的 Java 应用程序。 通过 jps 命令可以方便地查看 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息
1.jvm的简单抽象模型:  2.类加载机制     双亲委派模型是为了防止jdk核心类库被篡改,如果需要打破可以重写Classloader.loadClass方法。r 双亲委派模型:一个类加载器收到一个类的加载请求,他会先判断自身是否已存在该类,如果不存在上抛给上一级类加载器ClassLoad
堆外内存JVM启动时分配的内存,称为堆内存,与之相对的,在代码中还可以使用堆外内存,比如Netty,广泛使用了堆外内存,但是这部分的内存并不归JVM管理,GC算法并不会对它们进行回收,所以在使用堆外内存时,要格外小心,防止内存一直得不到释放,造成线上故障。堆外内存的申请和释放JDK的ByteBuffe
1.springboot和tomcat2.springcloud的请求如何通过网关鉴权?3.springmvc启动时组件的加载顺序?4.mybatis如何同时更新三条记录5.hibernate实现级联更新6.一个web程序应用程序启动时的加载流程7.如何向www.baidu.com地址发出请求时,并获取相应?8.???9.谈谈你对tcp/iptelnetudp协
堆设置-Xms256M:初始堆大小256M,默认为物理内存的1/64-Xmx1024M:最大堆大小1024M,默认为物理内存的1/4,等于与-XX:MaxHeapSize=64M-Xmn64M:年轻代大小为64M(JDK1.4后支持),相当于同时设置NewSize和MaxNewSize为64M-XX:NewSize=64M:初始年轻代大小-XX:MaxNewSize=256M:最大年轻代大小(默认
一.概述收集算法(JVM之垃圾回收-垃圾收集算法)是内存回收的抽象策略,垃圾收集器就是内存回收的具体实现。JVM规范对于垃圾收集器的应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器差别较大,这里只看HotSpot虚拟机。就像没有最好的算法一样,垃圾收集器
Java中的堆是JVM所管理的最大的一块内存空间,主要用于存放各种类的实例对象,如下图所示: 在Java中,堆被划分成两个不同的区域:新生代(Young)、老年代(Old)。新生代(Young)又被划分为三个区域:Eden、S0、S1。 这样划分的目的是为了使JVM能够更好的管理堆内存中的对象,包
JVM深入理解JVM(4)——如何优化JavaGC「译」 PostedbyCrowonAugust21,2017本文翻译自SangminLee发表在Cubrid上的”BecomeaJavaGCExpert”系列文章的第三篇《HowtoTuneJavaGarbageCollection》,本文的作者是韩国人,写在JDK1.8发布之前,虽然有些地
 JVM深入理解JVM(2)——GC算法与内存分配策略 PostedbyCrowonAugust10,2017说起垃圾收集(GarbageCollection,GC),想必大家都不陌生,它是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行在JVM上的语言,如Scala等)程序员在提升开
运行时数据区  线程独有本地方法栈、虚拟机栈、程序计数器这些与线程对应的数据区会随着线程开始和结束创建和销毁  整体公有元数据区(又称方法区)、堆区会随着虚拟机启动而创建,随着虚拟机退出而销毁 
java整个堆大小设置:Xmx和Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍。永久代PermSize和MaxPermSize设置为老年代存活对象的1.2-1.5倍年轻代Xmx的设置为老年代存活对象的1-1.5倍老年代的内存大小设置为老年代存活对象的2-3倍BTW: Sun官方建议年轻代
栈顶缓存(Top-of-StackCashing)技术基于栈式架构得虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然使用更多的入栈和出栈指令,这同时也就意味着将需要更多的指令分派次数和内存读写次数 由于操作数是存储在内存重的,因此频繁地执行内存读/写操作必然影响速度。 综上
自用。同样的代码在不同的平台生成的机器码是不一样的,为什么java代码生成的字节码文件,能在不同的平台运行?因为不同版本的jdk里面的虚拟机会屏蔽不同操作系统在底层硬件与指令上的区别。栈:线程栈,局部变量存放栈内存区域。线程(分配一个栈)运行分配栈将局部变量放入内存。怎么放:栈
jconsole监控:1.java启动命令加上参数java-Djava.rmi.server.hostname=172.16.17.247-Dcom.sun.management.jmxremote-Dcom.sun.management.jmxremote.port=2099-Dcom.sun.management.jmxremote.authenticate=false-Dcom.sun.management.jmxremote.ssl=false -XX:+Unlock
类加载器分类publicclassStackStruTest{publicstaticvoidmain(String[]args){//对用户自定义个类来说:默认使用系统类加载器进行加载-----AppClassLoaderClassLoaderclassLoader=StackStruTest.class.getClassLoader();System.out.p
堆体系结构一个JVM实例只存在一个堆内存,堆内存的大小是可调节的。类加载器读取类文件后,需要把类、方法、常量、变量放在堆内存中,保存所有引用类型的真实信息,以方便执行器指向,堆内存分为三个部分:年轻代、老年代、永久代。Java7之前,堆内存在逻辑上分为:年轻代、老年代、永久代。物
JVM深入理解JVM(5)——虚拟机类加载机制 PostedbyCrowonAugust21,2017在Class文件中描述的各种信息,最终都需要加载到虚拟机中之后才能运行和使用。而虚拟机中,而虚拟机如何加载这些Class文件?Class文件中的信息进入到虚拟机中会发生什么变化?本文将逐步解答这
保存(持久化)对象及其状态到内存或者磁盘Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在