运行时常量池,类加载器的作用,多态的原理,类的加密和解密
加载
1,类加载器根据类的全限名 通过不同渠道 以二进制流的方法获取字节码信息
2,加载完后,jvm会将字节码信息保存到方法区里
3,jvm在方法区里会生成一个instanceKlass对象,保存类的所有信息(基本信息,常量池,字段,方法,虚方法表(实现多态的基础))
4,还会在堆里生成一份与方法区中数据类似的java.lang.class对象,作用是在java代码中获取类的信息。方法区和堆里的信息相互关联。jdk8以后静态字段数据存储在栈里。
为什么在内存和栈里都要创建一个对象保存类的数据??
instanceKlass是C++编写的,java不能直接操作,但是java.lang.class是java封装后的对象,代码好获取。java.lang.class剔除了instanceKlass中的一些信息,剔除了一些开发不用的信息。
类加载器
类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。
每个 Java 类都有一个引用指向加载它的 ClassLoader。
数组类不是通过 ClassLoader 创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的
类加载器的企业应用场景
企业级应用:SPI机制,类的热部署,tomcat类的隔离
面试中:什么是类的双亲委派机制,怎么打破双亲委派,自定义类加载器
解决线上问题:使用arthas不停机,解决线上故障
链接
1,验证
校验字节码信息是否满足《java虚拟机规范》,不满足就滚。程序员不用参与
常见校验内容
- 文件格式验证(Class 文件格式检查,校验文件开头)
- 元数据验证(字节码语义检查,比如:类一定有父类)
- 字节码验证(程序执行指令语义检查,比如:跳转的地方不能为空)
- 符号引用验证(比如是否引用了其他类的private方法)
2,准备
给静态(static)变量赋初始值
为什么一开始有初始值??
因为怕原来的地址上有残留值
编译器认为你后面不会变了,就直接给赋值了
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段
3,解析
解析阶段主要是将常量池中的符号引用替换为直接引用。虚拟机将常量池内的符号引用替换为指向内存的直接引用的过程。
符号引用就是在字节码文件中使用编号来访问常量池中的内容
符号引用:比如说#5
初始化
与程序员有关的阶段。给静态(static)变量赋最终值
执行静态代码块中的代码,并为静态变量赋值
从字节码角度分析就是执行了字节码文件中的clinit部分的字节码指令
当一个类编译为字节码文件,字节码文件的方法信息包括:init(构造方法),main方法,clinit(初始化阶段执行)。clinit方法中的执行顺序与java编写的顺序一致
类继承的情况:
数组的创建不会导致数组中元素的类进行初始化
final修饰的变量如果赋值的内容需要执行指令才能得到结果,会执行clinit方法进行初始化
类加载器
一,类加载器的分类
分两类:
- jvm虚拟机底层源码(C++)实现的,程序员一般不用看,很难修改
- java代码实现的,JDK默认提供各种渠道的类加载器,程序员也可以自定义
如何自定义类加载器??
继承ClassLoader类即可
二,双亲委派机制
java虚拟机中有多个类加载器,到底由哪个类加载器来加载呢??
双亲委派机制就解决了一个类到底由哪个类加载器来加载的问题
首先加载类要保证类加载的安全性吧(防止恶意代码),也要避免重复加载吧
为了解决上述两个问题,就提出了双亲委派机制
就是两个流程:向上查找,向下加载
如果要加载一个类,先从下面的应用程序类加载器开始(向上查找),检查是否已经加载过了,加载过了直接返回类,没有就委托给父类加载器查找,依次往上。如果三个加载器都没加载过,那就从启动类加载器开始,依次往下尝试加载类(向下加载),如果在加载器路径中,那就加载成功。
如果自己创建一个String类,能被加载吗。不行,会返回启动类加载器中的String类
如何主动去加载一个类呢??
类加载器的小细节
启动类加载器在java代码中是不可获得的,所以扩展类加载器的parent是null
面试