JVM内存区域

线程私有:虚拟机栈、本地方法栈、程序计数器

线程共享:堆、方法区、直接内存

程序计数器

作用:

  • 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理

  • 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

程序计数器是唯一一个不会出现OOM的内存区域

Java虚拟机栈

每一次方法调用都会有一个对应的栈帧被压入栈中,每一方法的调用结束后,都会有一个栈帧被弹出。

栈由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法返回地址。

栈帧随着方法调用而创建,随着方法结束而销毁。无论方法正常完成还是异常完成都算作方法结束。

局部变量:数据类型、reference引用类型(类、接口、数组)

操作数栈 :(中转站)存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中。

动态链接:服务于一个方法调用其他方法,作用:将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。

栈中可能出现的问题:

  • StackOverFlowError 若栈的内存大小不允许动态扩展,如果当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度,就抛出 StackOverFlowError
  • OutOfMemoryError 如果栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError

Java虚拟机管理的内存中最大块,所有线程共享,作用:存放对象实例和数组(为对象分配内存)、GC的主要工作区域。

组成:1.新生代内存、老生代、元空间(使用的是本地内存)

堆这里最容易出现的就是 OutOfMemoryError 错误,并且出现这种错误之后的表现形式还会有几种,比如:

  1. java.lang.OutOfMemoryError: GC Overhead Limit Exceeded:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
  2. java.lang.OutOfMemoryError: Java heap space :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。(和配置的最大堆内存有关,且受制于物理内存大小。最大堆内存可通过-Xmx参数配置,若没有特别配置,将会使用默认值)

方法区

和堆一样,所有线程共享。

当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据

java 复制代码
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小

运行时常量池

Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的 常量池表(Constant Pool Table)

字面量是源代码中的固定值的表示法,即通过字面我们就能知道其值的含义。字面量包括整数、浮点数和字符串字面量。常见的符号引用包括类符号引用、字段符号引用、方法符号引用、接口方法符号。

常量池表会在类加载后存放到方法区的运行时常量池中。

字符串常量池

功能:避免字符串的重复创建

HotSpot虚拟机中字符串常量池中存储的是:字符串 和 字符串对象的引用 的映射关系,字符串对象的引用指向堆中的字符串对象。

直接内存

作用:内存缓冲区

HotSpot虚拟机对象

对象的创建
  1. 类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

  1. 分配内存

指针碰撞:

  • 适用场合:堆内存规整(即没有内存碎片)的情况下。
  • 原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。
  • 使用该分配方式的 GC 收集器:Serial, ParNew

空闲列表:

  • 适用场合:堆内存不规整的情况下。
  • 原理:虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。
  • 使用该分配方式的 GC 收集器:CMS

内存分配并发问题

CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。

TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配

  1. 初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

  1. 设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置 ,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

  1. 执行init方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

虚拟机访问对象的两种方式:句柄、直接指针。

这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

相关推荐
向阳12181 分钟前
使用Java Socket实现简单版本的Rpc服务
java·开发语言·rpc
红烧小肥杨6 分钟前
javaWeb项目-Springboot+vue-车辆管理系统功能介绍
java·前端·vue.js·spring boot·后端·mysql·毕业设计
调皮狗7 分钟前
Stream流
java
奥特曼狂扁小怪兽7 分钟前
关于QT cmake项目添加了.ui文件build未自动生成ui_xxx.h,错误提示找不到这个头文件问题处理
开发语言·qt·ui
是小Y啦15 分钟前
leetcode 739.每日温度
java·算法·leetcode
柠檬Leade18 分钟前
IDEA使用Alibaba Cloud Toolkit插件自动化部署jar包
java·自动化·intellij-idea
customer0826 分钟前
【开源免费】基于SpringBoot+Vue.JS音乐分享平台(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
customer0831 分钟前
【开源免费】基于SpringBoot+Vue.JS渔具租赁系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源
fmc12110435 分钟前
【前端】如何制作一个自己的网页(6)
开发语言·php
aa.173580341 分钟前
剖析淘宝猫粮前五十店铺:销售策略、产品特点与用户偏好
开发语言·python·算法·数据挖掘