JAVA虚拟机JVM-1.内存区域

Posted 名字可以起这么长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA虚拟机JVM-1.内存区域相关的知识,希望对你有一定的参考价值。

运行时数据区域

包含:程序计数器,java虚拟机栈,本地方法栈,java堆,方法区,运行时常量池。具体相关结构如下图。

 

区域 是否公用 相关概念 其他描述
程序计数器 线程私有 当前线程执行的字节码行号指示器 如果当前线程执行的java方法,那么计数器
java虚拟机栈 线程私有 java方法执行的线程内存模型 方法执行的时候创建的栈帧用户保存局部变量表,操作数栈,动态链接,方法出口等信息。可能抛出StackOutflowError异常。
本地方法栈 线程私有 本地方法执行的线程内存模型 本地Native方法是指可以调用操作系统底层的方法。可能抛出StackOutflowError异常。
java堆 线程共享区域 存放java的对象实例(GC回收的主要区域) JAVA堆是物理上不连续空间,但是逻辑上要连续的空间,并且大小可以进行配置,空间不足抛出OutOfMemoryError异常
方法区 线程共享区域 存放已经被虚拟机加载的类型信息,常量,静态常量,即时编译器编译后的代码缓存等数据 可以选择GC回收,也可以选择不回收。空间不足抛出OutOfMemoryError异常
运行时常量池 线程共享区域 属于方法区的一部分区域 Class文件除了标注类的版本,字段,方法,接口等描述信息外,还有就是常量池表,用于存放编译期生成的各种字面量和符号饮用。

直接内存:

NIO,引入的基于通道channel与缓冲区buffer的IO模型,可以通过Native方法直接分配堆外的内存,然后通过java堆中的DirectBuffer对象作为这块内存的引用进行操作。这样做的好处是能提高IO性能,避免了堆内存和Native堆中来回复制数据。这个内存不会收到堆内存大小的影响,但是会受到电脑内存的大小的限制。NIO相关的内容在线程章节会详细分析。

 

对象创建过程(虚拟机类加载机制)

对象创建过程包括:加载,验证,准备,解析,初始化。(图示为类的生命周期)

 

 加载,验证,准备,初始化和卸载这五个阶段的顺序是确定的,解析阶段不一定,某些情况下可以在初始化之后进行解析,以便为了支持Java语言的运行时绑定特性(动态绑定或者晚期绑定)

加载:三件事

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

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

3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

验证:

主要是为了确保Class文件的字节流中的信息符合Java规范。

验证包括:文件格式验证、元数据验证、字节码验证、符号引用验证。

准备:

准备阶段是正式为类中定义的变量分配内存并设置类初始值的阶段。

重点:

1.首先这个时候进行分配的内存仅仅包含类变量,不包含实例变量,实例变量会通过对象实例过程,在java堆中分配内存。

2.这里面设置的初始值是指“0”值。比如int a = 123;a在准备阶段的值为0,而不是123。

3.如果类字段的属性是ConstantValue,那么准备阶段就会把值初始化为指定的值。(final修饰)

解析:

解析阶段是java虚拟机将常量池内的符号引用替换为直接引用的过程。

符号引用:以一组符号来描述所引用的目标。

直接引用:可以是直接指向目标的指针,可以是相对偏移量,也可以是间接定位到目标的句柄。

解析包含:1.类或接口解析、2.字段解析、3.方法解析、4.接口方法解析。

初始化:

初始化阶段就是执行类构造器<clint>()方法的过程。

  • <clint>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句快中的语句合并产生的。收集的顺序是由语句在源文件中的出现的顺序决定的。
  • JAVA虚拟机会在执行子类的<clint>()方法之前确定执行过其父类的<clint>()方法。
  • <clint>()对于类或者接口并不是必须的,没有赋值操作或者static代码块的话编译器不会为其类生成<clint>()方法。
  • 接口和类的区别,执行接口的<clint>()方法不需要先执行父接口的<clint>()方法,只有父类的接口定义的变量被使用的时候,父类接口才会被初始化。
  • <clint>()方法在多线程的情况下会加同步锁,只会允许其中一个线程去执行这个方法。

 必须立即对类进行初始化操作的情况

  1. 遇到new、getstatic、putstatic或者invokestatic这四条字节码的时(new对象,读取或者设置静态字段,调用静态方法的时候)。
  2. 调用java.lang.reflect包的方法对类型进行反射的时候
  3. 子类初始化要优先初始化其父类
  4. 虚拟机启动的时候,指定的优先执行的主类(main函数入口)
  5. 动态语言支持,如果一个java.lang.invoke.MethodHandle实例最后解析的结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄的时候。
  6. 当接口中定义了default方法,如果实现类发生初始化,那么接口要先被初始化。

类加载器

类加载器虽然是只是用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。对于任意一个类,都必须由加载它的类加载器和这个类本身共同确立其在java虚拟机中的唯一性。

重点:判断两个类是否相等,必须保证这两个类由同一个类加载器加载。即使这两个类来源同一个Class文件,同一个虚拟机加载,如果加载器不同,那么这两个类也不相等。

双亲委派模型

从JAVA虚拟机角度看,存在两种不同的类加载器,一个是启动类加载器(C++语言实现的ClassLoader),一个是其他类加载器(java实现的java.lang.ClassLoader)。

  • 启动类加载器:负责加载<JAVA_HOME>\\lib目录下的类或者被-Xbootclasspath参数指定的路径中存放的,且java可以识别的类库。
  • 扩展类加载器:负责加载<JAVA_HOME>\\lib\\ext目录中的类或者被java.ext.dirs系统变量指定的路径中所有的类库。
  • 应用程序加载器:负责加载用户类路径上所有的类库。

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都有自己的父类加载器。

工作过程:如果一个类收到加载请求,首先不会加载这个类,而是把这个请求委派给父类加载器完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传到最顶层的启动加载器中,只有父类加载器反馈自己无法完成加载请求的时候,子类才会尝试自己去完成加载。

 

以上是关于JAVA虚拟机JVM-1.内存区域的主要内容,如果未能解决你的问题,请参考以下文章

JVM学习资料

[JVM-1]Java内存模型

Java虚拟机 - 结构原理与运行时数据区域

java虚拟机和java内存区域概述

java虚拟机java内存区域与内存溢出异常

深入理解java虚拟机系列:java内存区域与内存溢出异常