JVM内存模型

2017-10-19  本文已影响0人  Richard_80ec

1、类的加载、连接和初始化

      加载:查找并加载类的二进制数据(字节码文件)

      连接:

             验证:确保被加载的类的正确性(手工生成class文件,可

                        能不符合JVM标准规范)

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

                     (这个时候并不涉及new对象的操作)

             解析:把类中的符号引用转换为直接引用(Child.run()转

                        换为pointer)

      初始化:为类的静态变量赋予正确的初始值(正确:用户赋予的

                    值)

      示例:MyTest

   1.1类的使用方式:

        所有的类或接口只有在java程序“首次主动使用”才会初始化

        主动使用:

               (1)创建类的实例new Test();

               (2)访问某个类的静态变量,或者为某个类的静态变量赋值:a =

                    Test.b; Test.b = a;

               (3)调用类的静态方法:Test.doSomething();

               (4)初始化一个类的子类:

                    class Parent{};class Childextends Parent{

                    public static int a= 3};

                    Child.a =4;

              (5) java虚拟机就启动时被标明为启动类的类(java TestTest

                   中有main方法)

              (6)反射Class.forName(“Test”);

         除此之外的使用都是被动使用,被动使用是不会触发初始化的。

   1.2类的加载:

        将.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,又 来封装在方法区内的数据结构,class对象是jvm在类加载时自动创建的。

  1.3类加载器

        Java虚拟机自带的类加载器:

       根类加载器(Bootstrap)(c++ java代码中无法获取该类)

       扩展类加载器(Extension)(java )

       系统类加载器(System)(也叫应用加载器)

       用户自定义的类加载器

   层次关系:

       根类加载器:用c++实现,并没有集成java.lang.ClassLoader类

       扩展类记载器:父类加载器为根类加载器,加载目录jre\lib\ext,如果把用户创建的jar包放在这个目录下,则会有扩展类加载器                                  进行加载。

       系统类加载器:父类加载器为扩展类加载器,从环境变量classpath或系统属性java.class.path所指定的目录加载类,是自定义                                   加载类的默认父类加载器。

      (1)核心类库由bootstrap加载器进行加载,获取到为空,其他都可获取,例Test2

      (2)调用一个类的静态变量,且这个静态变量在编译时期就可以确定,则不会进行类的初始化,当这个类在执行阶段才能确                     定,则会进行初始化,例Test4,Test3

      (3)加载顺序—启动类—父类—子类,例Test5,Test6

      (4)调用loadClass方法加载一个类,并不是对类的主动使用,并不会初始化该类。例Test7

  1.4类的加载机制—父委托机制

      (1)除了java自带的根类加载器,其余类加载器都有且仅有一个父类加载器,只有当父类加载器不能进行加载的时候,才会由                子类加载器进行加载。

      (2)父子加载器并非继承关系,子加载器并不一定继承父类加载器

      (3)安全考虑,每个类都会有确定的层次中的一个类加载器进行加载,自定义除外。

     (4)每个类都有自己的命名空间,命名空间由该类加载器及所有父类加载器所加载的类组成。同一个命名空间中,不会存在两                个完整名字相同的类,不同命名空间中,可能存在完整名字相同的类。

  1.5 用户自定义类加载器

        继承ClassLoader类,覆盖findClass方法,会由loadClass方法调用。

   1.6 JVM虚拟机执行整体顺序

2、JVM的内存结构

方法区和对是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。

程序计数器:当前线程所执行字节码指令的行号指示器,线程私有,执行java方法,计数器记录的是java代码的行号,执行native方法则为null(undefined);

虚拟机栈:线程私有,表示java方法执行的内存模型,每个栈帧对应的是一个调用的方法,包括局部变量表、动态链接、操作数栈、指向当前方法所属的类的运行时常量池、方法返回地址和附加信息。

栈帧是保存在虚拟机栈中的,栈帧是用来存储数据和存储部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素。

局部变量表:方法参数,方法内部定义的变量。

操作数栈:每一个方法的调用,都会建立一个操作数栈

动态链接:与静态解析对应,一部分的符号引用会在运行是转化为直接引用

方法返回地址:正常完成出口,异常完成出口

本地方法栈:线程私有,与虚拟机栈类似,为native方法服务

堆:线程共享,存储对象本身及数组

方法区:线程共享,类信息,静态变量,常量,编译后的代码等

运行时常量池:存放编译期生成的各种字面量和符号引用

2.1对象的创建—内存分配

(1)jvm遇到一个new指令,检查这个指令的参数是否能在常量池中定位到一个类的符号引用,然后检查这个类代表的类是否已被加载、解析和初始化。如果没有,则进行类加载。

(2)类的加载—先加载父类,先加载静态块,顺序加载。

(3)JVM开始为对象分配内存,两种方式:

指针碰撞:java堆内存规整,GC带有压缩整理功能

空闲列表:java堆内存空间并不规整。

解决多线程分配内存的问题:

失败重试:保证更新操作的原子性

本地线程缓存取:TLAB,在启动线程时,就会在堆中给该线程预留一个内存空间

(4)内存分配完后,首先内存空间都初始化为零值。若使用TLAB,则在分配TLAB时即已初始化成功。

(5)设置对象头(Object Header):如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。

(6)执行方法,将对象初始化为指定的值。

3、GC垃圾回收

3.1判断对象失效

(1)引用计数算法

给对象添加一个引用计数器,每当有一个地方引用它,计数器值就加1,当引用失效就减1,计数器为0的对象就是不可能再被引用的对象。

实现简单,但无法解决循环引用的问题

(2)可达性分析算法

通过一系列成为“GC ROOT”的对象作为起始点,向下搜索,走过的路径成为引用链,当一个对象到GC ROOT没有任何引用链相连时,则证明此对象是不可用的。

可作为GC ROOT的对象有:

虚拟机栈中引用的对象

方法区中静态属性引用的对象

方法区中常量引用的对象

本地方法栈中JNI(native方法)引用的对象

3.2垃圾回收算法

(1)标记—清除算法

先对不用的对象进行标记,然后清除

(2)复制算法

将内存按容量分为几块,在一个时间段只使用一块内存,当这块内存用完,则将内存清理干净,然后将可用的对象复制到另一个区域。


(3)标记—整理算法

复制算法在对象存活率较高时要进行较多的复制操作,效率将变低,而且浪费空间。

标记—整理的标记过程与标记—清除一样,但后续步骤不是对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

(4)分代收集算法

根据对象存活周期的不同将内存划分为几块,一般把java堆分为新生代和老年代。

在新生代中每次垃圾收集时都发现有大批对象死去,只有少量存活,就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代因为对象存活率搞,没有额外空间对它进行分配担保,就必须使用标记—清理或标记—整理算法来进行回收

上一篇下一篇

猜你喜欢

热点阅读