cfff

Java虚拟机内存管理——内存空间划分

2018-09-14  本文已影响16人  夏尼采

一、类加载过程,双亲委派模型

1.Java中类加载分为3个步骤:加载、链接、初始化。

a. 验证。验证是保证JVM安全的重要步骤。JVM需要校验字节信息是否符合规范,避免恶意信息和不规范数据危害JVM运行安全。如果验证出错,则会报VerifyError。
b.准备。这一步会创建静态变量,并为静态变量开辟内存空间。
c.解析。这一步会将符号引用替换为直接引用。

2.双亲委派模型。

类加载器大致分为3类:启动类加载器、扩展类加载器、应用程序类加载器。
启动类加载器主要加载 jre/lib下的jar文件。
扩展类加载器主要加载 jre/lib/ext 下的jar文件。
应用程序类加载器主要加载 classpath下的文件。

所谓的双亲委派模型就是当加载一个类时,会优先使用父类加载器加载,当父类加载器无法加载时才会使用子类加载器去加载。这么做的目的是为了避免类的重复加载。

3.Java程序执行过程

.java ——编译——> .class
类加载器负责加载各个字节码文件(.class)
加载完.class后,由执行引擎执行,在执行过程中,需要运行时数据区提供数据


image.png

二.内存空间划分

JMVS规定了,Java虚拟机运行时数据区的划分,应当分为如下几块:

程序计数器、Java堆、Java虚拟机栈、本地方法栈、方法区

jvm.png

程序计数器(PC寄存器):线程私有。生命周期与线程相同,每创建一个线程就会创建出一个程序计数器。线程销毁,计数器就销毁。
每个线程有有一个私有的程序计数器,任何时间一个线程都只会有一个方法正在执行,也就是所谓的当前方法。程序计数器存放的就是这个当前方法的JVM指令地址。

java 虚拟机栈:线程私有,在创建线程时创建的,用来存储栈帧。是Java方法执行的内存模型。

局部变量表存放了编译期可知的各种基本数据类型、对象引用类型和returnAddress类型,它所需的内存空间在编译期间完成分配。

本地方法栈:跟JVM虚拟机栈比较类似,只不过它支持的是Native方法。

java 堆:用于存放几乎所有的对象实例和数组。

在Java堆中,可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),但无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。

方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。与Java堆一样,是各个线程共享的内存区域。

那么方法区具体存储了哪些东西呢?
(1)类型信息 :
   -- 这个类型的完整有效名
   -- 这个类的直接父类的完整有效名(除Object例外)
   -- 这个类型的修饰符(public absctract final)
   -- 这个类型的直接接口的游戏列表
   解释下什么叫完整有效名:代码中是 包名+ "." + 类名,但是类文件是"/"代替点: com/gaiay/mebilecard/GroupDetail。
(2)运行时常量池
   --全局共享
   --是方法区的一部分
   --作用是存储 Java 类文件常量池中的符号信息
   --可能出现 OutOfMemoryError 异常
  在class文件中,有部分很重要的信息就是常量池,用于存放在编译期就生成好的各种字面量和符号引用。这部分信息在类加载后被装入方法区的运行时常量池。Java虚拟机对class文件结构是有非常严格的规定的,calss文件中的 常量池 以及其他信息,必须保证每个字节存储哪种数据都必须与JVMS一致,才能被Java虚拟机认可、装载和执行,但是对于 运行时常量池,JVMS并没有细节上的约束,不同JVM厂商可以按照自己的需要去实现自己运行时常量池,一般来说 运行时常量池除了保存class文件中描述的符号引用外也会把符号引用翻译出来的直接引用一起存储在运行时常量池中。 那么这里所说的直接引用和间接引用指的是什么呢?好比当前类为A.java , 里面引用了一个String str , 和一个 你自己写的类 B b, 那么a就是个直接引用, b就是间接引用 。在编译期,str在class文件中的描述可能就是String类似这样的,而b就是类似 com.person.B.java (只是举个栗子,不要当真)。 然后在类加载时,会根据com.person.B.java 在方法区符号表中解析出实际的B.java的真实地址。      相比于class文件的常量池,既然叫做运行时常量池,那么必然是有动态性的。Java语言并不要求常量只能在编译期间才能产生,在运行时也可以通过代码产生新的常量,并将它们放入到运行时常量池当中。这种特性被开发者利用最大的就是 String#intern() 。
(3)Field信息
    -- 域名
    -- 域类型
    -- 域修饰符(public, private, protected,static,final volatile, transient的某个子集)
(4)方法信息
    -- 方法名
    -- 方法的返回类型(或 void)
    -- 方法参数的数量和类型(有序的)
    -- 方法的修饰符(public, private, protected, static, final, synchronized, native, abstract的一个子集)
    -- 除了abstract和native方法外,其他方法还有保存方法的字节码(bytecodes)操作数栈和方法栈帧的局部变量区的大小、异常表     
(5)static 变量(也叫类变量)
(6)final 常量

常量也会被保存在方法区中,同时也会复制一份到常量池中。保存在方法区中的常量是保存在使用他的类信息中, 而static 变量是保存在 声明 他的类信息中的。

(7)方法表
  JVMS并没有声明方法表一定要存在,他是Java虚拟机设计者为了提高方法的访问效率而实现的一种数据信息结构,他是实现(1)中用来存储类型信息的一部分,jvm对每个加载的非虚拟类(个人理解是非匿名类)的类型信息中都添加了一个方法表,方法表是一组对类实例方法的直接引用(包括从父类继承的方法)。jvm可以通过方法表快速激活实例方法。

上一篇下一篇

猜你喜欢

热点阅读