先来看一道题:
下面有关JVM内存,说法错误的是?
A 程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,是线程隔离的
B Java方法执行内存模型,用于存储局部变量,操作数栈,动态链接,方法出口等信息,是线程隔离的
C 方法区用于存储JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,是线程隔离的
D 原则上讲,所有的对象都在堆区上分配内存,是线程之间共享的
正确答案:
C
A:
程序计数器
是java虚拟机管理的
内存区域之一
,它是一块
较小的内存空间
,可以把它看做是当前线程所执行的字节码的
行号指示器
。
java虚拟机的多线程的实现原理是通过线程轮流切换并获得处理器的执行时间,在任何时刻,一个处理器都只会执行一条线程中的指令,因此线程切换之后,能够恢复到上次的执行位置,就是靠程序计数器。为了防止线程之间的紊乱,每个线程都需要一个程序计数器,所以程序计数器是线程私有的。
每个线程都有自己独立的程序计数器,用来指示下一条指令的地址
B:
JVM Java Virtual Machine的简称。意为Java虚拟机
又称为JMM,
描述java 并发程序的逻辑模型,
描述共享变量(类变量,静态变量)如何存储
Java内存模型规定所有的变量都是存在主存中,每个线程都有自己的工作内存
。
工作内存
:线程的内存,线程堆变量的操作(读写)都必须在工作内存进行,不能直接堆主存进行操作,并且每个线程不能访问其他线程的工作内存。工作内存中保存了所有变量的副本,
是线程隔离的
Java内存模型的Volatile关键字,原子性、可见性、有序性
主内存:
所有变量必须在主内存中存储
-
原子性:
一组操作要么同时发生,要么一个都不发生
基本数据类型的读写操作都属于原子性操作 int i=1;i=2;是;i=i+1; int y=i;不是
-
可见性
:某一线程对于变量的修改对于其他线程而言是立即可见的
只有三个关键字能保证可见性synchronized,volatile,final
-
有序性
:在单线程场景下,代码的执行顺序是书写顺序
在多线程场景下,所有代码都是乱序的
线程安全的代码指的是以上三个特性同时满足,任意一个不满足都不是线程安全的
同步问题的产生是因为以上三个特性有一个没有满足
关于三个特性详细请看我的这篇文章
java-volatile关键字
C:
方法区
:存放类的信息;此区包含常量池(常量池用来放基本类型常量和字符串类型常量),此部分可以回收;方法区可以放用static修饰的变量,但此部分不能回收,因为方法区也叫持久带,永久带基本不参与垃圾回收;
所有
线程共享
,所以不是线程隔离的,
存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据
。这个区域的内存回收目标主要是针对常量池的对象的回收和对类型的卸载
当方法区无法满足内存分配需求时,将抛出OOM异常
D:
JAVA 堆
,也称 GC 堆,
所有
线程共享
,存放对象的
实例和数组
,是JVM所管理的最大内存区域 是垃圾收集器管理的主要区域,如果在堆中没有足够的内存完成实例分配并且堆也无法再拓展时,将会抛出OOM
java堆内存区域存放的都是
对象实例
。JVM规范中说到:”
所有的对象实例以及数组都要在堆上分配
”
java虚拟机栈
:用来放局部变量、堆区对象的引用和常量池对象的引用;但对象本身不存放在栈中,而是存放在堆(new出来的对象)或者常量池中(对象可能在常量池里)(字符串常量对象存放在常量池中。);
本地方法栈:
本地方法栈与虚拟机栈的作用完全一样,他俩的区别无非是本地方法栈为虚拟机使用的
Native
方法服务,而虚拟机栈
为JVM执行的Java方法服务
运行时常量池(方法区的一部分):
存放字面量与符号引用
- 字面量 : 字符串(JDK1.7后移动到堆中) 、final常量、基本数据类型的值。
- 符号引用 : 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。
关于JVM内存模型的图解:
线程私有区域:程序计数器、Java虚拟机栈、本地方法栈
线程共享区域:Java堆、方法区、运行时常量池