美文网首页
JVM学习(持续更新)

JVM学习(持续更新)

作者: hekirakuno | 来源:发表于2019-10-06 17:01 被阅读0次

    本文一切来源于《深入理解java虚拟机》

    运行时数据区域
    程序计数器:
    当前线程所执行的字节码的行号指示器。因为java虚拟机的多线程是通过切换并分配CPU的执行时间实现的,在任何一个时刻一个CPU只会执行一条线程中的指令,因此为了保障切换后依旧可以恢复到正确的位置,所以每个线程都需要一个程序计数器。(它是唯一一个没有规定OutOfMemoryError情况的区域(简写为OOM))
    java虚拟机栈:
    每个java方法在执行的时候都会创建一个栈帧(存储局部变量表,操作数栈,动态链接,方法出口等),每一个方法从调用到结束的过程就是一个栈帧在虚拟机栈中从入栈到出栈的过程。
    局部变量表存储了编译期可知的基本数据类型(八种)对象引用(reference类型)returnAddress类型(指向了一条字节码指令的位置)
    局部变量表所需内存空间在编译期就分配完毕。
    本地方法栈
    与虚拟机栈大同小异,只是这里创建的都是JNI方法所需的栈帧罢了。

    以上三个都是与线程同生命周期的,因此垃圾回收基本不考虑他们,线程结束的时候他们就释放了。

    java堆
    线程共享的区域。
    几乎所有对象的实例都是在这里创建。它在虚拟机启动时创建,可以由-Xms和-Xmx指定初始堆内存和最大堆内存。它是垃圾收集的主要区域。
    内存回收的角度:分为新生代与老年代。
    内存分配的角度:分为TLAB与普通堆(指针碰撞/空闲列表)。
    方法区
    线程共享的区域
    主要存储运行时常量池,加载的类信息,静态变量,即时编译器编译后的代码等数据。
    运行时常量池可以实时添加,比如String的intern()方法。

    垃圾回收主要的目标就是java堆和方法区的废弃常量、无用的类

    java虚拟机运行时数据区

    对象的创建
    一个对象被创建过程如下:
    (1)当遇到一个new指令时,首先检查常量池中是否能定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被解析,加载,初始化过。如果没有,那么先进行类的加载;
    (2)类加载过后,虚拟机就会为新的对象实例分配内存,对象所需的内存大小在类加载完成后就可以确定了,分配内存的方法主要有两种:指针碰撞空闲列表,分别用于连续内存与不连续内存的场景;
    (3)为了解决并发问题,常常会使用一种叫做TLAB(本地线程分配缓冲)的策略。给每个线程提前分配一小块内存先,然后当它需要分配拟内存时,先在TLAB上分配拟,只有用完的时候才需要同步锁定;
    (4)分配内存之后,虚拟机会把分配到的内存空间的值都初始化为0值,如果使用了TLAB会在TLAB阶段就初始化;
    (5)设置对象头信息;
    (6)<init>初始化所有对象真正的值。
    对象的内存布局
    对象内存分为三部分:对象头,实例数据,对齐填充
    对象头分为两部分:运行时数据(hash码,gc分代年龄,锁状态id等),类型指针(指向类元数据的指针,判断它是哪个类的实例)
    实例数据:真正存储的有效信息,收到虚拟机分配策略影响顺序
    对齐填充:格式化,占位符罢了,非必要。(jvm要求对象大小为8字节的整数倍,对象头为8字节的整数倍,但是实例数据不一定是,所以需要补位调整)
    对象的访问定位
    还记得之前说过的一个线程栈帧里的局部变量表存储的数据吗?没错,基本数据类型,对象引用,returnAddress。其中对象引用(reference类型)就是我们操作堆上的具体对象的媒介了。
    主流的方式有两种:句柄访问直接指针
    句柄访问:java堆中会划出一块句柄池,主要存储的是堆中对象实例数据的指针和方法区中对象类型数据的指针。它的好处在于句柄的位置是稳定的,当垃圾回收后,实例位置改变,我们不需要修改reference本身。
    直接指针:reference存储的直接是对象实例数据的指针,结合对象头中的类型指针使用,它的速度更快。


    相关文章

      网友评论

          本文标题:JVM学习(持续更新)

          本文链接:https://www.haomeiwen.com/subject/usigyctx.html