JVM内存区域


参考: 地址


运行时内存区域

内存区域图解

Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域,且 JDK 版本不同也有所区别。
JDK 1.8之前
1

JDK 1.8 之后
2
线程共享的:

  • 堆内存
  • 方法区
  • 直接内存

线程私有:

  • 虚拟机栈(Java栈)
  • 本地方法栈
  • 程序计数器

注:

  • 永久代是HotSpot的概念,方法区是Java虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现
  • 直接内存不是虚拟机规范中定义的内存区域

虚拟机栈

虚拟机栈的主要内容:
1

  • 虚拟机栈描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。
  • Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
  • 局部变量表存放了各种数据类型以及对象引用
  • 虚拟机栈会抛出两种异常:
    StackOverFlowError:虚拟机栈的内存大小不允许动态扩展,线程请求栈的深度超过最大深度。
    OutOfMemoryError:虚拟机栈的内存大小运行动态扩展,线程请求栈时内存已经用完。
  • 每一次函数调用都用一个对应的栈帧被压入,调用结束则会有一个栈帧弹出。

本地方法栈

本地方法栈的作用和虚拟机栈相似,区别是:
虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法(一个Java调用非Java代码的接口)服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

程序计数器

两个主要的作用:

  • 字节码解释器是通过改变程序计数器来依次读取指令的,从而实现代码的流程控制(选择、循环、异常处理等)。
  • 多线程情况下,由于程序计数器是线程私有的,所以互不影响;在线程切换时可以知道继续执行的位置。

堆内存是所有线程共享的一块最大的内存区域,用于存放几乎所有的对象实例
堆也是垃圾回收的主要区域,根据垃圾的收集算法,可以细分为如下:
2
新生代: eden,s0,s1
老年代: tentired
一般刚创建的对象都会先被分配在 eden 区域,在一次新生代垃圾回收后,如果对象还存活则进入 s0 或 s1(复制算法),并且对象的年龄加 1,当到达老年代的阈值时就会被放入老年代(如果一开始对象太大放入不了新生代则会直接放入老年代)。

方法区

方法区、永久代和元空间
方法区也是线程共享的内存区域,用于存储虚拟机加载的类信息、常量、静态变量等数据。
方法去也被称为永久代,通过下面两个参数调节大小:

XX:PermSize=N //方法区初始大小
-XX:MaxPermSize=N //方法区最大大小

在 JDK 1.8 的时候方法去被彻底移除,取而代之的是直接内存中的元空间,而其与方法区的区别:

  • 方法区的内存大小是由 JVM 本身设置的固定大小,无法进行调整,即受虚拟机的限制。
  • 元空间使用直接内存,其内存大小只受系统内存的限制。

运行时常量池
运行时常量池是方法区的一部分,主要存放两大类的常量,即存储 Java 类文件常量池中的符号信息:

  • 字面量:文本字符串,final型常量值等。
  • 符号引用量:类和接口的全限定名,字段名称和描述符、方法名称和描述符。

对象的创建过程

创建对象

有五步操作:
3

  1. 类加载检查: 检查类中引用的类是否已被加载过、解析和初始化过。
  2. 分配内存: 新生代分配内存,两种分配方式,即指针碰撞和空闲列表。
    4
    对应两种垃圾收集算法:标记-压缩标记-清除
    内存分配并发问题的两种解决方式:
    1.CAS+失败重试:乐观锁的一种实现,,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
    2.TLAB:为每个线程预留内存,对象优先放入此内存,若超过则采用第一种方法。
  3. 初始化零值: 将分配到的内存空间初始化为零值,保证 Java 不赋值可以直接使用。
  4. 设置对象头: 存放属于那个类的实例,对象哈希码,对象的分代年龄等信息。
  5. 执行 init 方法: 上面的是 new 的过程,而要被 Java 程序所使用,还需要按照程序员的意愿进行初始化,即执行 init 方法。

访问对象

两种方式:

  1. 句柄: Java堆中划分出一块句柄池,访问过程为 栈->ref->句柄池->对象具体地址。
  2. 直接指针: 栈 ref 中存储的就是对象的地址。

String 类和常量池

String 对象的三种创建方式:

		String s1 = "moke";
		String s2 = new String("moke");
		String s3 = s2.intern();

区别:

  • 直接使用引号声明的 String 对象会直接存储在常量池。
  • 使用 new 则是先将 moke 放入常量池后,再在堆内存创建一个新的对象,且指针指向堆对象。
  • 使用 String 的 intern 方法则会先判断常量池中是否等于此 String 对象内容的字符串,有则直接返回;没有则创建后返回。

字符串的拼接

		String s1 = "moke1";
		String s2 = "moke2";
		String s3 = "moke1" + "moke2";
		String s4 = s1 + s2;

s3 是存放在常量池,而 s4 是在堆上创建。

基本类型的包装类和常量池

包装类与常量池:

  • Byte,Short,Integer,Long,Character,Boolean 这五种基本类型的包装类实现了常量池技术。但超出范围(-128-127)还是在堆创建对象.
  • Float,Double 两个浮点数类型没有实现常量池技术。
    注: 当包装类进行符号运算时会自动拆箱,所以==号会比较数值是否相等,而不是地址。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值