Java内存模型

JAVA内存模型

1.Java堆区,线程共享

java堆区是一块用于存储对象实例的存储区,同时也是GC(垃圾收集器)执行垃圾回收的重点区域。

1.1堆区内空间

堆区又可以分为新生代(YoungGen)和老年代(OldGen),新生代又可以划分为Eden空间,From Survivor空间和To Survivor空间。

1.2异常

堆大小在JVM启动时就已经设定好了,可以通过选项“-Xmx”和“-Xms”来进行设置。“-Xmx”用于表示堆区起始内存,“-Xms”用于表示堆区的最大内存,当堆区内存超出最大内存就会抛出OutOfMemoryError异常



2.方法区,线程共享

方法区也可以称为又叫静态区。
方法区存储了每一个Java类的结构信息,比如:运行时常量池,字段,方法数据,构造函数和普通方法的字节码内容以及类,实例,接口初始化时需要用到的特殊方法等数据。

方法区包含所有的class和static变量。方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。


2.1.运行时常量池

运行时常量池属于方法区中的一部分。运行时常量池中包含多种不同的常量,比如编译期就已经明确的数值字面量到运行期解析后才能够获得的方法或者字段引用。

运行时常量池所分配的内存来源于方法区,所需内存超过最大值也会抛出OutOfMemoryError异常。

2.2异常

方法区在JVM启动的时候被创建。当方法区中的内存大小超出选项“-XX:Max-PermSize”所指定的最大内存时,将会抛出OutOfMemoryError异常。


3.程序计数器,线程私有

程序计数器的生命周期与线程的生命周期保持一致,如果当前线程所执行的方法是一个Java方法,那么程序计数器就会存储正在执行的字节码指令地址,反之如果是native方法,这时程序计数器的值就是空。

3.1程序计数器为什么被设定为线程私有?

我们都知道多线程在一个特定的时间段内只会执行某一个线程的方法,CPU会不停地做任务切换,那么为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的方法自然是为每一个线程都分配一个程序计数器,这样一来各个线程之间就可以进行独立计算,互不干扰。

3.2存储字节码指令地址又有什么用?

JVM的字节码解析器需要通过改变程序计数器的值来明确下一条应该执行什么样的字节码指令。

3.3异常

程序计数器是内存中唯一一个没有明确规定需要抛出OutOfMemoryError异常的运行时内存区。



4.虚拟机栈(Java栈),线程私有

Java栈生命周期与线程的生命周期保持一致。Java栈用于存储栈帧,而栈帧中所存储的就是局部变量表,操作数栈,以及方法出口等信息。

4.1栈帧


在面向对象的世界中,类与对象是最基本的概念,字段和方法是一个类的主要构成元素。当实例化对象后,字段可以理解为一个对象的各种“器官”,方法可以理解成一个对象的一系列“行为”。

方法和栈帧之间又存在什么关系呢?

简单点说,栈帧是一中用于支持JVM调用/执行程序方法的数据结构,它是方法的执行环境,每一个方法被调用时都会创建一个独立的栈帧,便于维系所需的各种数据信息。栈帧随方法的调用创建,随方法的结束而销毁。
那么每一个方法从调用到执行结束的过程,就对应Java栈中一个栈帧从入栈到出栈的过程。

4.1.1局部变量表

局部变量表也可以称为本地变量表。局部变量表顾名思义,用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类原始数据类型,对象引用,returnAddress类型。局部变量表中最小的存储单元是Slot(变量槽),一个Slot可以存储一个类型为boolean,byte,char,short,float,reference以及returnAddress小于或等于32bit的数值。2个Slot可以存储一个类型为long或double的64bit数值。JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引来访问局部变量表中的值。

JVM使用局部变量表来完成方法调用时的参数传递,当一个实例方法被调用,它的方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表中的每一个Slot上。


注意:索引为0的Slot一定存储的是与被调用实例方法相对应的对象引用(通过Java语法层面的“this”关键字便可访问到这个参数,即:),后续的其他方法参数和变量会按照顺序从索引为1的Slot位置开始复制。

public class A{
 public void m() {
        this.m();
   }
 }
4.1.2操作数栈

操作数栈就是JVM执行引擎的一个工作区,当一个方法被调用的时候,新的栈帧被创建,但是这个时候栈帧中操作数栈却是空的,只有在方法执行过程中,才会有各种各样的字节码指令往操作数栈中执行入栈和出栈操作。举个例子:

public int add(int a,int b){
	int c;
	c=a+b;
	return c;
}

首先a会被压入操作数栈的栈顶,接着栈顶元素出栈并存储在局部变量表中索引为1的Slot上,b被压入操作数栈的栈顶,接着栈顶元素出栈并存储在局部变量表中索引为2的Slot上。接着把局部变量表索引为1和2的变量重新压入
操作数栈顶,然后执行加法指令,并把运算结果出栈存储在局部变量表索引为3的Slot上,最后return返回。


操作数栈的作用基本就是这样了。

4.1.3动态链接

常量池中有指向方法的符号引用,字节码文件中描述一个方法调用了另外的其他方法就是用符号引用来表示的。
动态链接指向运行时常量池中该栈帧所属方法的引用。


4.2异常

Java栈允许被实现成固定大小的内存或者是可动态扩展的内存大小,在此需要提醒大家,如果Java栈被设定为固定大小的内存,一旦线程请求分配的栈容量超过JVM允许的最大值时,JVM会抛出一个StackOverflowError异常,反之抛出一个OutOfMemoryError异常。

常见的出现StackOverflowError异常:使用方法进行递归的时候,无限进行递归调用,进而会引发栈溢出。

 public static void testStack() {
        if (true)
            testStack();
    }
    //错误日志
Exception in thread "main" java.lang.StackOverflowError

5.本地方法栈

本地方法栈用于支持本地方法(native方法,比如使用C/C++代码编写的方法)的执行,作用和Java栈的作用类似。

5.1异常

会抛出StackOverflowError异常或者OutOfMemoryError异常。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值