美文网首页
深入理解JAVA虚拟机(读书笔记)

深入理解JAVA虚拟机(读书笔记)

作者: 天渊hyominnLover | 来源:发表于2018-08-15 11:06 被阅读8次

    前言

    深入理解JAVA虚拟机读书笔记及代码记录

    Chapter Two:Java内存区域与内存溢出异常

    2.1 运行时数据区内存分配概述

    • 程序计数器:当前线程执行字节码的行号指示器,唯一不受OutOfMemoryError影响的内存区域
    • 虚拟机栈:每个方法执行时会在栈内存中创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口灯信息,其中局部变量表存储基本数据类型、对象引用和returnAddress类型,线程请求的栈深度大于虚拟机允许的深度则抛出StackOverFlowError
    • 本地方法栈:与虚拟机栈相似,用于执行native方法,同样会抛出StackOverFlowError
    • java堆:存放对象实例(在栈上分配和标量替换技术中,并不是全部对象都在堆中分配),是GC的主要管理区域,分为新生代和老生代,其中新生代包括Eden、FromSurvivor和ToSurvivor区域
    • 方法区:另称为永久带(Perm Gen)用于存储加载后的类信息、常量池、静态变量和JIT编译后的代码,但永久代内存回收机制比较落后,容易出现内存溢出问题,因此这部分内存在JDK8后已经被移植到堆中,称为元空间(Meta Space)
    • 直接内存区(direct memory):NIO类可直接使用native函数库直接分配堆外内存进行Channel和Buffer的使用,称为直接内存区,这部分内存不受虚拟机GC管理

    2.2 JVM对象特征

    1. 创建对象

    虚拟机为新建对象分配内存空间采用如下两种分配算法:

    • 指针碰撞:堆内存完全规整,已用内存空间和未用内存空间中间由分界指针划开,新建内存时只需要将指针向未用内存区域挪动即可;带Compact过程的GC(Serial、ParNew等)采用该分配算法
    • 空闲列表:堆内存随机分布,有一个列表用于记录堆中哪些内存未占用,新建对象时直接从列表上取一块内存进行使用;基于Mark-Sweep算法的GC(CMS等)采用该分配算法

    如何保证分配内存过程的原子性:

    • CAS同步:虚拟机采用CAS方式进行失败重试来分配内存
    • TLAB:把内存分配动作按照线程划分在不同区域,各线程在堆中预先分配一小块内存(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存就在哪个线程的TLAB上进行分配,当TLAB用完并分配新的TLAB时才重新采用CAS方式

    创建对象的整个过程:

    graph TD
    确保类已被加载并初始化-->获取对象大小
    获取对象大小-->A{是否在TLAB中分配对象}
    A{是否在TLAB中分配对象}-->|否|直接在Eden中分配对象
    A{是否在TLAB中分配对象}-->|是|TLAB中分配
    直接在Eden中分配对象-->CAS方式分配空间
    CAS方式分配空间-->将分配的内存空间赋0值
    TLAB中分配-->将分配的内存空间赋0值
    将分配的内存空间赋0值-->编辑对象头
    编辑对象头-->对象引用入栈
    对象引用入栈-->初始化对象
    

    2. 对象在内存中的结构

    一个对象在内存中包括3个区域:对象头、实例数据、对齐填充(padding):

    • 对象头:第一部分(Mark Word)包括HashCode、GC分代年龄、锁状态标志、线程当前持有锁、偏向线程ID、偏向时间戳等,在32位和64位JVM中大小分别为32bit和64bit;Mark Word在不同的状态下(状态用2bit的锁标志位来标记),存储结构不同,以下是5中状态时存储的不同内容:
    存储内容 标志位 状态
    Hashcode和分代年龄 01 未锁定
    指向锁记录的指针 00 轻量级锁定
    指向重量级锁的指针 10 重量级锁定
    11 GC标记
    偏向线程ID、偏向时间戳、分代年龄 01 可偏向

    对象头第二部分是类型指针,用于确定这个类是哪个类的实例

    如果对象是数组,还要有一块记录数组长度的数据

    • 实例数据:对象实际存储的数据,相同宽度的成员变量一般分配到一起
    • 对其填充:通常作为占位符,因为Hotspot要求对象大小必须是8的整倍数

    3. 对象访问定位

    目前市面上的JVM对于对象地址的定位有两种处理方式:

    • 句柄访问:栈中保存的引用指向的是堆中的句柄池,引用存储的是某个对象句柄的地址,句柄中包含对象数据当前实际地址和对象类型数据地址
    • 直接指针访问:栈中的引用直接指向堆中对象数据的实际地址

    目前Hotspot用的是第二种方式

    相关文章

      网友评论

          本文标题:深入理解JAVA虚拟机(读书笔记)

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