一.java内存区域

jvm在执行java程序的过程中会把所管理的内存分为若干不同的区域,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁的。

以下是java运行时内存区域图:

Java 内存 流 java运行时内存区域_java



1.程序计数器

  程序计数器是一块较小的区域,它可以看做是当前所执行的字节码的行号指示器。在java的多线程交互执行的过程中,被中断的线程当前执行到那条指令的内存地址需要被保存下来,以便于该中断线程恢复执行的时候可以按照被中断时的指令继续执行下去。

线程私有”的内存。



2.java虚拟机栈

  每当创建一个线程,jvm就会为该线程创建一个java虚拟机栈,虚拟机栈的生命周期与线程相同,它随线程的创建与结束而启动和销毁的。该线程每调用一个方法,jvm就会在该虚拟机栈中创建一个栈帧,用于存放局部变量表,操作数栈,接口信息等。每一个方法的从调用到完成的过程就对应一个栈帧在虚拟机栈中的入栈以及出栈的过程。正在执行的方法的栈帧位于栈顶。

线程私有"的内存。

  该区域可抛出两种异常情况:

  • StackOverflowError异常:线程请求的栈的深度大于了虚拟机所允许的深度。
  • OutOfMemoryError异常:虚拟机栈东塔扩展时无法申请到足够长度。

 

3.本地方法栈

本地方法栈和Java栈所发挥的作用非常相似,区别不过是Java栈为JVM执行Java方法服务,而本地方法栈为JVM执行Native方法服务。 本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

   本地方法栈的这个内存区域为"线程私有"的内存。



4.java堆

堆是JVM所管理的内存中国最大的一块,是被所有Java线程锁共享的,不是线程安全的,在JVM启动时创建。堆是存储Java对象的地方,这一点Java虚拟机规范中描述是:所有的对象实例以及数组都要在堆上分配。Java堆是GC管理的主要区域,从内存回收的角度来看,由于现在GC基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代再细致一点有Eden空间、From Survivor空间、To Survivor空间等。



5.方法区

  方法区用于存储已被虚拟机加载的类信息,常量,静态变量即编译后的代码以及数据。

在方法区中存在一部分内存称为“运行时常量区”,用于存放编译器生成的各种字面量以及符号引用量。【其中,字面量就是指final变量等;引用变量包括类和接口的全限定名,字段名称,方法名和描述符】

  方法区为java线程共享

  当方法区无法满足内存需求的时候,将抛出OutOfMemoryError异常。

 



二.java内存模型

 1. java内存模型(java Memory Model , JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了java程序在各种平台下对内存访问都能保证一致的机制及规范

 2. Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

如:线程1 要与 线程2 进行数据交换,首先,线程1要把自己工作内存1中的数据更新保存到主内存中,然后线程2要去主内存中拷贝一份数据,刷新到自己的工作线程2之中。

3.java内存模型是围绕着并发编程中的原子性,可见性,有序性这三个特征来建立的。

  • 原子性:一个操作要么不执行,要么全部执行完毕,不能被打断。
  • 可见性:一个线程对共享变量做了修改之后,其他的线程立即能够看到该变量的变化。
  • 有序性:在本线程内观察,所有的操作都是无序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排”现象以及“工作内存与主内存同步延迟”现象。

4.总结:JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。

注:主内存和工作内存与JVM内存结构中的Java堆、栈、方法区等并不是同一个层次的内存划分,无法直接类比。