JVM学习——(一)类加载机制&运行时数据区

一、类文件到虚拟机(类加载机制)

1.1 装载(Load)

类加载的第一步是对类的装载过程:

1)通过一个类的全限定名获取定义此类的二进制字节流。

2)将这个字节流代表的静态存储结构转化为方法区的运行时数据结构

3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

 

1.2 链接(Link)

类加载的第二步是链接,链接这一步中包含验证、准备、解析的步骤。

1.2.1 验证(Verify

保证被家族类的正确性

文件格式验证

元数据验证

字节码验证

符号引用验证

1.2.2 准备(Prepare)

为类的静态变量分配内存,并将其初始化为默认值。

1.2.3 解析(Resolve)

把类中的符号引用转换为直接引用。

 

1.3 初始化(Initialize)

类加载的第三步是初始化操作。

对静态变量、静态代码块执行初始化操作。

1.4 类加载机制图解

使用和卸载并不算是类加载过程中的阶段,只是画完整了一点。

二、类装载器(ClassLoader)

在装载(Load)阶段,其中第一步是通过类的全限定类名获取其定义的二进制字节流,需要借助类装载器完成。顾名思义,就是用来装载Class文件的。

2.1 分类

1)Bootstrap ClassLoader:负责加载$JAVA_HOME中jir/lib/rt.jar 里所有的class或Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader的子类。

2)Extension ClassLoader 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。

3)App ClassLoader 负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包。

4)Custom ClassLoader 通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcatjboss都会根据j2ee规范自行实现ClassLoader

2.2 图解类装载器

2.3 双亲委派机制

定义:如果一个类加载器在接到加载类的请求时,它首先不会自己去尝试加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。

优势:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。

比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载。因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,由各个类加载器自己去加载的话,那么系统中会存在多个不同的Object类。

破坏:可以继承ClassLoader类,然后重写其中的loadClass方法。

 

三、运行时数据区(Run-Time Data Areas)

在装载阶段的第2、3步可以发现有运行时数据、堆、方法区等名词。

其实说的就是类文件被类装载器装载进来之后,类中的内容(比如变量、常量、方法、对象这些数据)要被存储起来,存储的位置肯定是在JVM中有对应的空间。

3.1 图解

3.2 各部分简介

3.2.1 Method Area(方法区)

方法区是各个线程共享的内存区域,在虚拟机启动时创建。

用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它又有个别名叫Non-Heap(非堆),目的是和Java堆区分开来。

此时再回看装载阶段的第 2 步: (2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构:
注意:
1)方法区在JDK8就是Metaspace,在JDK6或7中就是Perm Space
2)Run-Time Constant Pool(运行时常量池)
 
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。
 
3.2.2 Heap(堆)
Java堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程所共享。
Java对象实例、数组都在堆上分配。
此时回看装载阶段的第 3 步: (3) Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口:
 
3.2.3 JavaVirtual Macheine Stacks(虚拟机栈)
虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。
换句话说,一个Java线程的运行状态,由一个虚拟机栈来保存。
所以虚拟机栈肯定是线程私有的,随着线程的创建而创建。
 
每一个被线程执行的方法,为该栈中的栈帧,也就是每个方法对应一个栈帧。
调用一个方法,就会向栈中压入一个栈帧;
一个方法调用完成,就会把该栈帧从栈中弹出。
 
3.2.4 The Pc Register(程序计数器)
 
一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的。
假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B执行了。然后当线程A再获得CPU执行权时,怎么继续执行呢?
这就是需要在线程中维护一个变量,记录线程执行到的位置。
 
程序计数器占用的内存空间很小,由于Java虚拟机的多线程是通过线程轮流切换,并分配处理器执行时间的方式来实现的。
在任何时刻,一个处理器只会执行一条线程中的指令。
因此为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,每条线程需要有一个独立的程序计数器(线程私有)。
 
如果线程正在执行java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是Native方法,则这个计数器为空。
 
3.2.5 Native Method Stacks(本地方法栈)
如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。
 

四、运行时数据区提炼总结

名称是否线程共享作用可能出现的异常

方法区

线程共享

存放类信息(版本、字段、方法、接口等)、常量、静态变量、即时编译后的代码等数据。

内存不足时,抛出OutOfMemoryError(内存不足错误)。

虚拟机栈

线程私有

存放局部变量表、操作数据栈、动态链接、方法出口等信息。

栈帧深度大于允许最大深度时,抛出StackOverflowError(栈溢出错误);

内存不足时,抛出OutOfMemoryError(内存不足错误)

线程共享

存放对象实例和数组。

内存不足时,抛出OutOfMemoryError(内存不足错误)

程序计数器

线程私有

记录当前线程所执行的字节码指令。

唯一不会出现OutOfMemoryError的区域。

本地方法栈

线程私有

java虚拟机栈类似,不过是为了JVM用到的Native方法服务。

栈帧深度大于允许最大深度时,抛出StackOverflowError(栈溢出错误);

内存不足时,抛出OutOfMemoryError(内存不足错误)

运行时常量池

线程共享

属于方法区的一部分。

存放编译器生成的各种字面量和符号引用。

内存不足时,抛出OutOfMemoryError(内存不足错误)

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值