JVM读书日记

第一模块 自动内存管理

java内存区域与内存溢出异常

运行时数据模块

在这里插入图片描述

程序计数器
  1. 一块较小的内存空间,主要用来存储当前线程执行的字节码行号,是程序的流程的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都基于他实现。
  2. 因为虚拟机多线程之间是通过线程轮流切换、分配处理器时间的,所以没一个线程都要有自己的程序计数器,各个线程之间不能相互干扰,所以程序计数器是线程私有的。
  3. 如果虚拟机执行的是一个java方法那么他记录的就是行号,如果执行的是本地方法那么计数器就是null,他是唯一一个没有规定内存溢出的区域。
java虚拟机栈
  1. 也是线程私有,生命周期与线程相同,主要描述java方法执行的线程内的模型,每个方法被调用的时候都回创建一个栈帧,栈帧用来存储局部变量表、操作数栈、动态链接、方法的出口。每一个方法被调用都是一次出栈和入栈的过程。
  2. 局部变量表存储的是八大基本数据类型和对象的引用,其中long和double 占用两个局部变量槽。
  3. 内存区域规定了两种异常:一种是线程申请栈深度的超过了虚拟机所允许的深度就会抛出 StackOverflowError(栈溢出错误)。第二种是在java虚拟机允许动态扩展内存,栈扩展时无法申请到的足够的内存抛出OutOfMemoryError (内存不足错误 )。
本地方法栈
  1. 本地方法栈与虚拟机栈作用相同区别时本地方法栈时作用在本地方法(Native)上。
  2. HotSpot虚拟机把本地方法栈和虚拟机栈合二为一都时抛出上述两种异常。
Java堆
  1. 内存空间中最大的一块,主要作用是用来存储对象实例,几乎所有的对象实例都存储在这里,这里考虑逃逸分析、栈上分配、标量替换来考虑所以是几乎。
  2. java堆一般采用分代回收算法,所以一般分为新生代(Eden、From survivor、To survivor)、老年代、永久代(1.8被元数据区替换)等
  3. 规定上堆可以在物理空间上是不连续的内存,但是逻辑上要把他视作连续的。java堆的内存大小可以是固定的也可以是扩展的,通过参数-Xmx和-Xms设定。如果在java堆中没有内存完成实例化也没有可扩展的空间就会抛出OutOfMemoryErroe异常。
  4. 扩展 栈上分配:符合栈上分配条件,1 逃逸分析(指对象只在方法区内有用,其他线程不使用) 2. 标量替换 (对象内存中成员变量区只有最基本的,那么就舍弃markwork和类型指针只保留成员变量)
分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部 方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;甚至还有可能被外部线程访 问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;从不逃逸、方法逃逸到线 程逃逸,称为对象由低到高的不同逃逸程度。
方法区
  1. 方法区是线程共享的,主要存放一些虚拟机加载的类型信息、静态变量、常量、即使编译器编译后的代码缓存等数据。
  2. 如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError。
运行时常量池
  1. 运行时常量池属于方法区的一部分,也会抛出OutOfMemoryErroy异常
  2. 扩展 class内 包含 类的版本字段方法接口等描述外,还有常量池表(存放编译器产生的各种字面量、符号引用)。
直接内存
  1. 直接内存并不属于运行时数据区的一部分,他是因为NIO的引入,而产生的,它可以使用Native函数库直接分配堆外内存,然后通过DirectByteBuffer来直接操作。
  2. 直接内存不占用Java堆的大小限制,但是会受到本机总内存的限制。

HotSpot虚拟机对象探秘

对象的创建
  1. 对象的创建时通过一个 new 指令来实现的,当虚拟机接收到字节码指令new时,会先查看这个指令参数是否能在常量池中找到一个类的符号引用,并检查这个这个符号代表的类是否已经被加载、解析、是初始化过,如果没有就必须先执行相应的类加载过程。
  2. 在类加载检查通过后,就会分配内存,分配多少内存在类加载检查的时候就已经确定好了。内存分配一般有两种情况:第一种是被使用的内存都是放在一边的,空闲的内存被存放在另一边,用一个指针来做分界线,这样的情况下就先在未使用的空间中分配一块足够大小的内存然后移动指针来做新的分界(这种内存的分配叫做指针碰撞)。第二种是已使用的内存不是放在一起的而是零散分开的情况下,这种情况下虚拟机必须维护一个表来记录内存的使用情况,在分配内存的时候在表中找到一快足够使用的内存来进行分配,并进行记录这种分配方式被称空闲列表
  3. 采取指针碰撞空闲列表是由虚拟机采用的垃圾收集器是否支持空间压缩整理能力来决定。一般使用serial、parNew等带有压缩整理过程的垃圾回收器是使用的就是指针碰撞,使用CMS这种基于清楚算法的收集器时,理论上时使用的空闲列表理论上是因为CMS的实现里面,为了能够提高性能设计了linear Allocation Buffer的分配缓冲区。注意以为new对象是一件经常发生的问题,那么就会存在并发问题,即使仅仅修改一个指针所执行的位置,在并发情况下也是线程不安全的,会出现对A分配内存、指针还没有移动,对象B又使用了原来的内存,解决这种问题有两种方案可以选择:1 内存空间的动作进行同步处理,采用CAS配上失败重试的方式保证更i性能的操作的原子性。另外一种是TLAB(Thread Local Allcation Buffer) 本地线程分配缓冲,哪个线程要分配内存,就在哪个线程的本地缓冲区分配,只有当本地缓冲区不足时,分配新的内存时才会加同步锁。
对象的内存布局
  1. 在HotSpot虚拟机中对象在堆中的存储布局一般可以分为3个部分:对象头(Header)、实例数据(Instance Data)、对齐填充区域。具体 链接.
  2. 虚拟机对象头一般分为两部分空间:第一类时用来存储对象本身的运行时数据,如哈希码、GC年龄、锁状态标记、线程持有的锁、偏向锁id、偏向时间戳等。
    在这里插入图片描述
  3. 对象头的另外一个部分是类型指针,用来指向类型元数据的指针,java虚拟机通过这个指针来确定该对象是哪一个类的实列。
  4. 实例数据区是对象真正存储的有效信息,代码里所定义的各种类型的数据,包括父类继承的。这部分的存储顺序会受到虚拟机的参数(-XX:FieldsAlloncationStyle)和字段在java源码中的的定义顺序来决定。一般默认顺序是:Longs/doubles、ints、shorts/chars、bates/booleans、oops。
  5. 第三部分是对齐空间,他不是必须的而是用来对齐对象的,一般来说一个对象的大小要是8字节的整数倍如果上述两部分已经满足了 那么第三部分将不存在。
对象的访问定位
  1. 创建对象时为了后续使用对象,java程序会通过栈上的reference数据来操作堆上的具体对象。一般有两种主流的访问方式:一、句在这里插入图片描述
    柄。二时直接指针
  2. 句柄访问的化,java堆中将可能会划分出一部分内存来作为句柄池。reference中存储的就是对象的句柄地址,而句柄中包含了对象示例数据与类型数据各自具体的地址信息。
  3. 直接指针是之reference中存储的直接就是对象地址,可以直接访问对象不需要多一次间接访问的开销。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值