内存分配

21 篇文章 0 订阅

1. 运行时数据区

  • 包括:PC寄存器、Java虚拟机栈、Java堆、方法区、运行时常量池、本地方法栈等

1.1 PC寄存器

  • 每个线程拥有一个PC寄存器,是线程私有的,用来存储指向下一条指令的地址
  • 在创建线程的时候,创建对应的PC寄存器
  • 执行本地方法时,PC寄存器的值为undefined
  • 是一块较小的内存空间,是唯一一个在JVM规范中没有规定OutOfMemoryError的内存区域

1.2 Java栈

  • 栈由一系列帧(Frame) 组成(因此Java栈也叫做帧栈),是线程私有的
  • 帧用来保存一个方法的局部变量、操作数栈(Java没有寄存器、所有参数传递使用操作数栈)、常量池指针、动态链接、方法返回值等
  • 每一次方法调用创建一个帧,并压栈,退出方法的时候,修改栈顶指针就可以把栈帧中的内容销毁
  • 局部变量表存放了编译期可知的各种基本数据类型和引用类型,每个slot存放32位的数据,long、double占两个槽位
  • 栈的优点:存取速度比较快,仅次于寄存器
  • 栈的缺点:存在栈中的数据大小、生存期是在编译期决定的,缺乏灵活性

1.3 Java堆

  • 用来存放应用系统创建的对象和数组,所有线程共享Java堆
  • GC主要就管理堆空间,对分代GC来说,堆也是分代的
  • 堆的优点:运行期动态分配内存大小,自动进行垃圾回收
  • 堆的缺点:效率相对较慢

1.4 方法区

  • 方法区是线程共享的,通常用来保存装载的类的结构信息
  • 通常和元空间关联在一起,但具体的跟JVM实现和版本有关
  • JVM规范把方法区描述为堆的一个逻辑部分,但它有一个别名称为Non-heap(非堆),应是为了与Java堆区分开
  • 方法区实际上是一个规范,在不同的JVM上有不同的实现方法。如通过永久代(持久代)或元空间去实现。对于Java8, HotSpots在方法区的实现上取消了永久代,取而代之的是元空间。那么元空间和永久代有什么不同呢?
    • 存储位置不同,永久代物理上是堆的一部分,和新生代,老年代地址是连续的,而元空间属于本地内存
    • 存储内容不同,元空间存储类的元信息,静态变量和常量池等并入堆中。相当于永久代的数据被分到了堆和元空间中。

1.5 运行时常量池

  • 是Class文件中每个类或接口的常量池表,在运行期间的表示形式,通常包括:类的版本、字段、方法、接口等信息
  • 在方法区中分配
  • 通常在加载类和接口到JVM后,就创建相应的运行时常量池

1.6 本地方法栈

  • 在JVM中用来支持native方法执行的栈就是本地方法栈

1.7 栈、堆、方法区交互关系

    • 局部变量表
      • A
      • b
      • user(实际上是指向了堆里面的User对象)
    • User对象
      • User类的元数据信息(实际上是通过句柄引用指向方法区得到的)
      • 实例数据
  • 方法区
    • User类
    • 类定义
    • 字段
    • 方法

2. Java堆内存概述

  • Java堆用来存放应用系统创建的对象和数组,所有线程共享Java堆
  • Java堆是在运行期动态分配内存大小,自动进行垃圾回收
  • Java垃圾回收(GC) 主要就是回收堆内存,对分代GC来说,堆也是分代的

2.1 Java堆的结构

  • 新生代用来放新分配的对象;新生代中经过垃圾回收,没有回收掉的对象,被复制到老年代
  • 老年代存储对象比新生代存储对象的年龄大得多
  • 老年代存储一些大对象
  • 整个堆大小 = 新生代 + 老年代
  • 新生代 = Eden + 存活区(两个Survior,from和to)
  • 从前的持久代(也称永久代),用来存放Class、Method等原信息的区域,从JDK8开始去掉了,取而代之的是元空间(MetaSpace),元空间并不在虚拟机里面,而是直接使用本地内存

2.2 对象的内存布局

  • 对象在内存中存储的布局(这里以HotSpot虚拟机为例来说明),分为:对象头、实例数据和对齐填充
    • 对象头,包含两个部分:
      • Mark Word:存储对象自身的运行数据,如:HashCode、GC分代年龄、锁状态标志等
      • 类型指针:对象指向它的类元数据的指针
    • 实例数据
      • 真正存放对象实例数据的地方
    • 对齐补充
      • 这部分不一定存在,也没有什么特别含义,仅仅是占位符。因为HotSpot要求对象起始地址都是8字节的整数倍,如果不是,就对齐

2.3 对象的访问定位

  • 对象的访问定位
    • 在JVM规范中只规定了reference类型是一个指向对象的引用,但没有规定这个引用具体如何去定位、访问堆中对象的具体位置
  • 因此对象的访问方式取决于JVM的实现,目前主流的有:使用句柄 或 使用指针两种方式
    • 使用句柄:Java堆中会划分出一块内存来做为句柄池,reference中存储句柄的地址,句柄中存储对象的实例数据和类元数据的地址,如下图所示:

    • 使用指针:Java堆中会存放访问类元数据的地址,reference存储的就直接是对象的地址,如下图所示:

3. JVM相关参数

3.1 Trace跟踪参数

  • 可以打印GC的简要信息:-Xlog:gc
  • 打印GC详细信息:-Xlog:gc*
  • 指定GC log的位置,以文件输出:-Xlog:gc:garbage-collection.log
  • 每一次GC后,都打印堆信息:-Xlog:gc+heap=debug

3.1.1 GC日志格式

  • GC发生的时间,也就是JVM从启动以来经过的秒数
  • 日志级别信息,和日志类型标记
  • GC识别号
  • GC类型和说明GC的原因
  • 容量:GC前容量 -> GC后容量(该区域总容量)
  • GC持续时间,单位秒。有的收集器会有更详细的描述,比如:user表示应用程序消耗的时间,sys表示系统内核消耗的实现,real表示操作从开始到结束的时间

3.2 Java堆的参数

  • -XX:+UseConcMarkSweepGC:使用CMS收集器
  • -XX:+UseG1GC:使用G1收集器
  • -Xms10m(-XX:MinHeapSize=8m -XX:InitialHeapSize=9m,后者要大于等于前者):初始堆大小,默认物理内存的1/64(必须是1024的倍数并且大于1M
  • -Xmx10m(-XX:MaxHeapSize=10m):最大堆大小,默认物理内存的1/4(必须是1024的倍数并且大于2M)
  • -Xmn(-XX:NewSize= -XX:MaxNewSize=):新生代大小,默认整个堆的3/8。如果新生代太小会导致频繁的GC;如果新生代太大会导致仅仅执行FullGC,这可能需要很长时间才能完成,而且很容易造成虚拟机停顿。所以推荐新生代大小为总堆的25%~50%之间。对于G1收集器,建议不要去设置新生代的大小,让系统自动对内存进行分配和管理。
  • -XX:+HeapDumpOnOutOfMemoryError:OOM时导出堆到文件
  • -XX:+HeapDumpPath:导出OOM的路径
  • -XX:NewRatio=2:老年代与新生代的比值,默认为2。如果xms=xmx,且设置了xmn的情况下,该参数不用设置
  • -XX:SurvivorRatio:Eden区和Survivor区的大小比值吗,设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor占整个新生的1/10
  • -XX:OnOutMemoryError:在OOM时,执行一个脚本

3.3 Java栈的参数

  • -Xss:通常只有几百k,决定了函数调用的深度

3.4 元空间的参数

  • -XX:MetaspaceSize:初始空间大小
  • -XX:MaxMetaspaceSize:最大空间,默认是没有限制的
  • -XX:MinMetaspaceFreeRatio:在GC之后,最小Metaspace剩余空间容量的百分比
  • -XX:MaxMetaspaceFreeRatio:在GC之后,最大Metaspace剩余空间容量的百分比
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JackieLeeee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值