Java内存结构和类加载机制
慕课Java专栏学习手记:https://www.imooc.com/read/67
- JVM对Java类的使用总体上可以分为两部分:一是把静态的class文件加载到JVM内存,二是在JVM内存中进行java类的生命周期管理。二者一静一动,分别对应Java内存结构与类加载机制、JVM与垃圾回收。
JVM内存结构
JVM内存模型主要分为五个区:方法区(Method Area),虚拟机栈(VM Stack),本地方法栈(Native method stack),堆(Heap),程序计数器(Program Counter Register)。
-
堆
- 在虚拟机启动时创建,几乎所有对象实例都在这里创建,是垃圾收集器管理(GC)的主要区域。
- 线程共享。 -
方法区
- 主要用来存储JVM加载的类信息,包括类的方法(如类的接口/父类等)、常量、静态变量等,还包括运行时常量池(Runtime Constant Pool)
- 很少发生 GC(Garbage Collection,垃圾回收),偶尔发生的 GC 主要是对常量池回收和类型的卸载;
- 线程共享 -
虚拟机栈
- 主要用来存储一些线程独有信息,如局部变量等。又称为栈内存,每个方法执行的时候都会创建一个栈帧。
- 用于存储局部变量表,操作数栈,方法出口等信息,每一个方法被调用至完成的过程,就对应着一个栈帧在虚拟机栈从出栈到入栈的过程。在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。
- 线程私有
-本地方法栈
-与虚拟机栈类似,但是用于服务本地方法.
-线程私有
-程序计数器
-内存空间小,存储当前字节码指令所在的地址
-线程私有
各区域抛出的异常
堆:如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError 异常;
方法区:当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常;
虚拟机栈 / 本地方法栈:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;对于支持动态扩展的虚拟机,当扩展无法申请到足够的内存时会抛出 OutOfMemory 异常。
程序计数器:此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
JVM 的类加载机制是什么样的?有几类加载器
-
双亲委派加载机制
加载类的时候首先委托给父类 一直向上委托,如果父类可以返回完成类加载任务,子类就不加载,只有父类加载器无法完成此加载任务时,才自己手动加载 -
3类加载器
启动类加载器,Bootsrap ClassLoader 用于加载JAVA_HOME/lib目录下
扩展类加载器,Extension ClassLoader 用于加载JAVA_HOME\jre\lib\ext
应用程序类加载器 Application ClassLoader 加载用户路径的类库
除此之外,还可以通过继承 java.lang.ClassLoader 类实现自己的类加载器,可以打破双亲委派机制。 -
对于任意一个类,都需要由加载它的类加载器和这个类本身 (字节码进行描述)一同确立其在 Java 虚拟机中的唯一性。
双亲委派机制的好处
①效率性
一些经常使用的类,基础类,由父类加载后,子类就不需要再加载。越基础的类越由上层的加载器进行加载。
②安全性 防止核心类库被加载,比如String类只能由上层的加载器加载,如果有用户自定义了一个跟String全类名的类,那么由于这个机制的存在,系统只会加载系统的String类,不会加载用户自定义的String类
类的生命周期的七个阶段
- 加载
通过类的全限定名获取类的二进制字节文件 - 验证
验证类文件的相关信息是否符合约束要求,保证这些信息被当作代码运行后不会危害虚拟机的健康 - 准备
为类变量 static分配内存并初始化,需要注意的是 假如有个static int a=1; a在这里会被初始化为0,并未执行任何java方法 - 解析
将常量池内的符号引用替换为直接引用的过程 - 初始化
初始化阶段,控制权从jvm交到了应用程序,执行类构造器方法的过程,方法由编译器自动收集类中所有静态变量的赋值动作和静态语句块组合而成。父类定义的静态语句块要优于子类的执行。接口没有静态语句块但仍有方法。多个线程同时初始化一个类,只有一个线程会执行,会被正确加锁 - 使用
注意,new其实是使用类的阶段,站在整个类生命周期的角度,而并不是初始化 - 卸载
各阶段的作用如图