一、内存结构概述
- 内存简图
- 读数据流程:
- 字节码文件由类加载子系统加载到方法区
- 每个部分的详细结构:
- 类加载子系统
- Loading(加载)、Linking(链接)、Initiallzation(初始化)
- 加载需要加载器:
- BottStrap ClassLoader(引导类加载器)
- Extension ClassLoader(扩展类加载器)
- Application ClassLoader(系统类加载器)
- 还可以自定义类的加载器
- 验证:
- Verify(验证)
- Prepare(准备)
- Resolve(解析)
- 初始化
- 静态变量的显示初始化
- Loading(加载)、Linking(链接)、Initiallzation(初始化)
- 运行时数据区:
- 运行时内存区的详细结构:
- PC Registers(PC寄存器/程序计数器)
- 每一个线程一份
- Stack Area(栈)
- 每一个线程使用一份
- 每一个线程使用的一份(小长发行)栈帧(Stack Frame)
- Native Method Stac(本地方法栈)
- 调用本地方法的API
- Heap Area(堆区)
- 对象存储在堆区
- 占用最大的空间
- JC重点考虑的一片空间
- 多个线程所共享的
- Method Area(方法区)
- 存放类的信息、常量、域的信息、方法信息等
- 只有Hotspot虚拟机才有
- 执行引擎:
- 执行引擎的详细结构:
- Interpreter(解释器)
- JIT Compiler(即时编译器)
- Garbage Collection(垃圾回收器)
- 作用:
- 将指令翻译成机器指令
- 与操作系统打交道
- 执行引擎的详细结构:
- 类加载子系统
- 如果手写一个虚拟机需要考虑哪些结构呢?
- 类加载器和执行引擎
二、类加载器与类加载的过程
- 类加载子系统的作用:
- 从文件系统中或网络中加载Class文件在内存中生成大的class实例,Class文件在文件的开头有特定的文件标识(开头都是 CA FE BA EE)
- ClassLoader只负责class文件的加载,由Exection Engine决定是否可以运行
- 加载的类信息存放在方法区的内存空间;方法区中还会存放运行时常量池的信息(对应这class实例中的常量池)、字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
- 字节码文件是物理磁盘上的文件
- 物理磁盘中的class文件加载到内存当中是一二进制流的形式传输的
- 类的加载过程:
- 类加载的过程图:
- 加载的说明:
- 通过一个类的全限定名(全类名)获取定义此类的二进制字节流
- 将字节流对应的静态存储就够转换为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,做为方法区这个类的各种数据的访问入口
- 链接:
- 验证(Verification)
- 确保当前的Class文件中的信息符合虚拟机的要求,保证被加载类的正确性,不会危害虚拟机自身安全
- 四种验证:
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
- 准备(Preparation)
- 为类变量分配内存,并为该变量赋默认的初始值,即零值
- final修饰的static,在编译的时候就会分配,准备阶段显示初始化
- 不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象一起分配到堆空间当中
- 解析(Resolution)
- 将常量池内的符号引用转化为直接引用
- 验证(Verification)
- 初始化:
- 初始化过程就是执行类加载器方法<Clinit>()的过程
- 类构造器方法<Clinit>与类构造器<init>不同
- 类构造器方法不需要定义
- 是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而来(没有类变量和静态代码块不会生成Clinit方法)
- 构造器方法指令的执行顺序是按照语句在源文件中的顺序执行的
- 虚拟机必须保证一个类的<Clinit>()方法在多线程下同步加锁
- 加载的说明:
- 补充:.class文件的加载方式:
- 从本地系统中直接加载
- 通过网络获取,典型场景 Web Applet
- 从zip压缩包中获取,成为日后jar、war格式的基础
- 运行时计算生成,使用最多的是:动态代理技术
- 由其他文件生成,典型的应用场景:JSP应用
- 从专有数据库中提取.class文件,比较少见
- 从加密文件中获取,典型的防Class文件被反编译的保护措施
- 类加载的过程图:
- 类加载子系统的作用:
三、类加载器的分类
- JVM支持两类型的类加载器
- 引导类加载器(BootStrap ClassLoader)
- 又称启动类加载器,使用C/C++语言实现且套在JVM的内部
- Java的核心类库有引导类加载器加载
- 没有父类的加载器
- 加载扩展类和应用程序类加载器,为他两的父类加载器
- 出去安全考虑只加载,包名为:java/javax/sun等开头的类
- 自定义类加载器(User-Defined ClassLoader)使用Java语言编写
- 扩展类加载器
- Java语言编写
- 派生于ClassLoader
- 父类加载器为启动类加载器
- 加载java.ext,dirs系统属性所指定的目录中加载类库,或从JDK中的jre/lib/ext子目录下加载类库。
- 如果用户创建的JAR放在此目录下(jre/lib/ext),也会有扩展类加载器加载
- 应用程序类加载器(系统类加载器)
- Java语言编写
- 派生于ClassLoader
- 父类加载器为扩展类加载器
- 加载环境变量classpath(自定义的类)或系统属性Java.class.path指定路径下的类库
- 程序中默认的类加载器
- Java应用的类都是由他来完成加载
- 用户自定义的类加载器
- 为什么要自定义类的加载器:
- 隔离加载类:防止出现类的加载,将不同的中间键隔离
- 修改类加载的方式
- 扩展加载源
- 防止源码泄露
- 自定义类的加载器实现步骤:
- 继承java.lang.ClassLoarder类的方式,实现自己的类加载器
- JDK1.2之前需要继承ClassLoader类并重写loadclass方法,但是JDK1.2以后不建议重写loadclass方法,而建议把自己定义的类加载器逻辑写在findclass方法中。如果指定路径中的字节码文件进行了加密,则需要在此方法中进行解密操作。
- 为什么要自定义类的加载器:
- 扩展类加载器
- 引导类加载器(BootStrap ClassLoader)
- JVM支持两类型的类加载器
四、双亲委派机制
- Java虚拟机对class文件采取的是按需加载的方式,当需要的时候,才会加载class文件到内存中生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,他是一种任务委派模式。
- 工作原理:
- 如果一个类加载器收到了类加载请求,他并不会先去自己加载,而是把这个请求委托给父类加载器加载
- 如果父类加载器还存在父类加载器,就会一直向上请求,递归依次递归,最终倒带顶层的启动类加载器
- 如果父加载器可以完成加载任务就成功返回,否则子类加载器就会自己尝试去加载
- 优势:
- 避免类的重复加载
- 保护程序安全,防止核心API被随意篡改
- 沙箱安全机制:
- 对Java核心源代码的保护,这种机制称为沙箱安全机制
五、其他
- 在JVM中表示两个class对象是否为同一个类的两个必要条件是:
- 类的完整类名必须 一致,包括包名
- 加载这个类的类加载器(ClassLoader实例)必须相同
- Java程序对类的使用方式:
- 主动使用:
- 创建类的实例
- 访问某个类或接口的静态变量 ,或者对该静态变量赋值
- 调用类的静态方法
- 反射
- 初始化一个类的子类
- Java虚拟机启动时,被标明为启动类的类
- JDK7开始提供的静态语言支持
- 被动使用;
- 除了上面的七种主动使用的情况,其他情况都是被动使用;类的被动使用都不会导致类的初始化
- 主动使用:
- 在JVM中表示两个class对象是否为同一个类的两个必要条件是:
本文含有隐藏内容,请 开通VIP 后查看