java内存区域详解
运行时的数据区域
程序计数器
私有的,有两个作用 第一个作用是字节码解释器是通过程序计数器来依次读取指令,从而实现了代码的流程控制,在多线程的情况下程序计数器用来记录当前线程执行的位置,从而当线程切换时有正确的位置
程序计数器是唯一不会出现outofmemory的内存区域,随线程创建而创建,随线程灭亡而灭亡
java虚拟机栈
私有,随线程创建而创建,随线程灭亡而灭亡,除了native调用的是本地方法栈,其他方法的调用都是通过栈来实现
方法调用的数据需要栈进行传递,每次方法的调用都会有一个对应栈帧压入栈中,方法调用结束再出栈
栈由一个个栈桢组成,栈桢的结构是局部变量表,操作数栈 动链接 方法返回地址 先进后出
局部变量表存放的是: 数据类型,对象引用
操作数栈:主要作为方法调用的中转站使用,用于存放方法执行过程中产生的计算结果,临时变量也会存放
动态链接:服务于一个方法需要调用其他方法的场景,当一个方法需要调用其他方法,需要将常量池中指向其他方法的引用转为其在内存地址中的直接引用,,动态链接就是将符号引用转为调用方法的直接引用
有两种返回,return正常返回 和抛出异常返回,都带遍方法结束
栈运行时可能出现的两种错误:
stackoverflowerror:如果栈的内存不允许动态扩展,当线程的栈需要深度超过JVM中的栈所需深度,会抛出该异常
outofmemory:可以动态扩展,但是虚拟机无法申请到足够的内存空间
本地方法栈
和虚拟机栈类似 不过调用的是native
堆
虚拟机中内存管理的最大一块,所有线程共享的一块区域,存放的是队形的实例,对象实例以及数组都在堆里面分配
jdk1.7默认开启逃逸分析: 某些方法中的对象引用没有被返回或者未被使用,可以在堆上直接分配对象
堆事垃圾收集管理器的主要区域GC堆
堆可以分为新生代和老年代
7之前分为新生代 老年代 永久代 8以后永久代被元空间取代,元空间使用的是本地内存
大部分情况对象都会在堆的eden区分配,在一次新生代的垃圾回收后如果对象还存活,会进入s0或者s1
,并且对象的年龄加一,eden--》survivor区默认对象的年龄是1,当年龄到十五的时候会进入老年代
堆里最容易出现的就是outofmemroyerror
:gc overhead limit exceeded 在垃圾回收用了大量时间很少的内存被回收 会出现这个报错
java heap sapce:堆的内存不足以存放对象时
方法区:
属于JVM运行时数据区域的一块逻辑区域,各个线程共享的区域
不同的虚拟机上 方法区的实现是不同的
当虚拟机需要一个类时 会读取并解析类,将类中的信息存入到方法区中,方法区存储的是已经被虚拟机加载的数据 存放类信息字段信息 变量等数据
方法区与永久代 元空间的关系,接口和类的关系
为什么将永久代替换为元空间
- 永久代有一个jvm本身设置的固定大小上限 无法进行调整而元空间使用的是本地内存 受本机可用内存的限制,溢出的概率比永久代小,溢出报错metaspace
- 元空间内存放的是类的元数据,加载多少类的元数据由系统的实际可用来控制,能加载的类变多了
- 8中 hotspothejrockkit合并 后者没有永久代
方法区的常用参数
- -xx: permsize =N 方法区永久代的初始大小
- -xx :maxpermsize=N 最大大小 超过会抛出outofmemroy的异常
- -xx: metasapcesize=N 设置元空间的初始大小
- -xx: Maxmetaspacesize=N 设置元空间的最大大小
运行时常量池(方法区)
方法区的一部分,类似于传统编程语言的符号表,class文件中除了描述信息外还有存放编译期生成的各种字面量(代码中固定值的表示法), 符号引用, 常量池表
字符串常量(方法区)
jvm为了提升性能和减少内存消耗针对string类 专门开辟的一块区域 主要目的是为了避免字符串的重复创建
7之后被移动到堆里,因为永久代的回收率低,只有full gc才会被执行gc,java程序中会有大量的字符串被创建,移入到堆中,可以更高效被回收
直接内存
特殊的内存缓冲区 不在堆或方法区中创建,而是通过JIN在本地内存中分配,不会受堆影响但是会受本地机器影响
HotSpot 虚拟机对象详解
hotspot虚拟机在java堆中对象分配 布局和访问的过程
对象的创建
- 类加载检查,虚拟机遇到一条new指令时,首先将先去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个类的符号引用带代表的类是否已经被加载过、解析过和初始化过,如果没有需要先执行相应的类加载过程
- 分配内存: 在类加载通过后,虚拟机需要为新生成的对象分配内存,对象所需要的内存大小,加载完类以后即可得分配方式有指针碰撞和空闲列表两种,选择哪种分配方式由java的堆是否规整决定,堆是否规整取决于采用的垃圾收集器是否具有压缩整理功能,指针碰撞: 适合内粗规整没有内存碎片,将用过的内存全部放在一边,没有用过的内存放到一边,中间的分界指针沿着没有用过的内存方向将该指针移动到需要的内训大小的位置即可。使用的分配方式的GC收集器: serial,parnew 空闲列表:堆内存不规整,虚拟机会维护一个列表,这个列表会标记哪些内存空闲,会分配给需要的对象,再更新列表 使用该分配的gc收集器cms。 内存分配并发问题:对象创建是频繁的,所以虚拟机需要保证线程的安全,采取两种方法,第一种方法:CAS+失败重试,乐观锁,假设不加锁没有冲突,失败就重试,直到成功为止 保证的是更新操作的原子性。第二种方法:TLAB 为每一个线程预先在eden 区分配一块内存,会优先使用这块内存分配给对象,当对象大于这个内存或者用完了就采用第一种方法
- 初始化零值:内存分配好之后,需要将分配到的内存空间初始化为零值,可以保证对象的实例字段在java代码中可以不赋值初始值使用,程序可以访问到这些这些字段数据类型对应的零值
- 设置对象头:初始化完零值以后需要对对象进行设置,关于对象的信息如元数据信息hash码 对象的gc年龄等需要存放到对象头中,不同的虚拟机有不同的设置方式
- 执行init方法:对象的初始化
对象的内存布局
在hotspot虚拟机中,对象的布局可以分为 :对象头、实例数据和对齐填充
对象头 :
- 存储自身运行时的数据,哈希码 gc年龄代 锁状态标志
- 类型指针,指向类元数据的指针,虚拟机通过该指针来确定时哪个类的实例
实例数据:存储真正有效的信息
对齐填充:非必须的,仅是占位作用,hotspot虚拟机要求对象是8字节的整数倍,对象头部分是8 的整数倍,那实例数据没有8的整数倍就要占位填充
对象的访问定位
句柄:
堆中会划分一块区域做句柄池,栈中的reference数据村的就是对象的句柄地址,句柄中包含了对象实例数据与对象类型数据各自的具体地址信息
直接指针:
栈中的局部变量表的reference --》堆中 --》方法区中的对象类型数据