一、Jvm内存结构简述
先看一下jvm大致的结构,如图所示:
Jvm可以说主要由类的加载器子系统、运行时数据区和执行引擎这三部分组成。工作的大致步骤:类的加载器子系统把class文件加载成class对象保存到方法区中,其中包含一些静态变量的初始化,当程序工作的时候,按照字节码指令,执行引擎开始在数据区中取数,操作栈完成对应的工作。
二、类加载器与类的加载过程
- 类的加载器
作用:把class文件加载成class对象存放到方法区中;
类型:最常见的有三种类加载器,用户也可以自定义类加载器。
- 类的加载过程
1、加载阶段(生成一个实例)
先通过一个类的全限名称把一个类转换成二进制字节流;
把这个字节流代表的静态静态存储结构转换成方法区运行时数据结构;
在内存中生成一个class对象,作为方法区这个类的各种数据访问入口。
2、链接阶段
- 验证:检验字节流的格式,保证符合jvm规范
- 准备:为类变量分配内存空间并赋默认的初始值
- 解析:将常量池中的符号引用转换成直接引用的过程
3、初始化阶段
执行clinit方法,真正为类变量赋值和执行静态代码块;
执行init,完成class对象的初始化。
三、类加载器的分类
Jvm按照是否派生于抽象类ClassLoader,把类加载器分为两大类,即引导类加载器(Bootstrap ClassLoader)和自定义类加载器。不过无论怎么划分,在程序中我们常见的类加载器有以下如图所示,即我们常说的引导类加载器(Bootstrap ClassLoader)、扩展类(Extension ClassLoader)加载器和系统加载器(System ClassLoader)以及我们自己写的加载器(User Defined ClassLoader)。
我们也可以在代码里体验一下:
1.Bootstrap Class Loader
- 这个类加载器是由C/C++编写的,嵌在jvm内部的
- 它用来加载Java的核心类库(JAVA_HOME/jre/lib、resooure.jar、sun.boot.class.ptah路径下的类),用于提供Java自身需要的类
- 并不继承ClassLoader,没有父类加载器
- 加载扩展类和系统类加载器,并指定为他们的父类加载器
- 出于安全考虑,只加载Java、javax、sun等开头的类
2.Extension Class Loader
- Java语言编写、派生于ClassLoader类
- 父类加载器为BootstrapClassLoader
- 从java.ext.dirs系统属性指定的目录加载类库,或者从jdk安装目录jre/lie/ext子目录下加载类库。
3.System Class Loader/App Class Loader
- Java语言编写、派生于ClassLoader类
- 父类加载器为ExtensionClassLoaer
- 负责加载环境变量classpath或系统属性java.class.path指定下的类库 一般来说,Java程序里的类都是由它加载的
- 可以通过ClassLoader.getSystemClassLoader()方法来获取它的实例对象
4.User Defined Class Loader
一般我们程序里的class都是由上述三个加载器完成加载的,必要情况下,我们也可以自己定义类加载器。自己定义加载器的好处有:隔离加载类、修改类的加载方式、扩展加载源和防止源码泄露等。
四、双亲委派机制
- 是什么
Java虚拟机对class是采用按需加载的模式,也就是说当你需要用到时才去把class文件加载成class对象保存到内存中去,而且加载的时候,采用的就是双亲委派机制,即把请求交给父类加载器去执行,是一种任务委派模式。
- 工作原理
1、当前类加载器收到一个加载类的请求,不会第一时间自己加载,会向上委托给自己的父类加载器;
2、父类收到请求后,也是往上委托,直到最顶层的启动类加载器;
3、父类完成加载,若是不能加载,才会让子类加载器自己去加载。
(简而言之,让加载工作给父类做,父类完成不了,自己做)
代码举例:可以自己定义一个java.lang.String类,在其中定义一个静态代码块并且输出一个hello,在测试类new String,可以发现自己定义的静态代码块没有给执行,因为这个时候是系统加载器加载了核心类库的String。
- 有什么优势
1、避免了类的重复加载;
2、保护程序的安全性,避免核心api给随意篡改。
- 沙箱安全机制
可以自己定义一个java.lang.String,在里面写main方法,执行发现报错了。因为这个时候由于双亲委派机制,加载的是核心类库的String,核心类库的String没有main方法。这样就防止了别人恶意篡改核心api的代码,保护了我们程序的核心类库,我们称这种保护叫沙箱安全机制。
五、部分其他相关知识
- 比较两个class对象相同的条件
1、两个对象的类名、包括路径名必须一致;
2、加载这个类的加载器(指加载器对象)必须一致。
- 对类加载器的引用
当一个类型是由用户类加载器加载的时候,jvm会把这个加载器的一个引用作为类型信息的一部分一起保存到方法区中,当解析一个类型到另一个类型的引用的时候,jvm需要保证两个类型的加载器是相同的。
- 类的使用方式分为主动使用和被动使用
主动使用有以下场景:
1、创建类的实例
2、访问某个类或者接口的静态变量,或者对该静态变量赋值
3、反射( 比如:Class.forName(“java.lang.String)))
4、初始化一个类的子类
5、java虚拟机启动时被标明为启动类的类
6、java 7开始提供的动态语言支持
被动使用:除了主动引用外都属于被动引用,例如:
1、子类对象引用父类的静态字段
2、通过数组来引用类
3、引用类的常量
两者的区别:被动使用不会执行类的初始化,自然也不会执行static代码块