一、内存区域(线程共享区-非线程共享区)
JVM初始运行的时候都会分配好Method Area(方法区)和Heap(堆),而JVM 每遇到一个线程,就为其分配一个Program Counter Register(程序计数器),VM Stack(虚拟机栈)和Native Method Stack (本地方法栈),当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。
二、程序计数器(Program Counter Register)
1.程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。
2.在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
3.由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。
4.在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。
5.由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。
三、Java虚拟机栈(Java Virtual Machine Stacks)
1.Java栈也称作虚拟机栈,也就是我们常常所说的栈
2.如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常
3.Java栈是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
4.局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)「String是引用类型」,对象引用(reference类型)和returnAddress类型(它指向了一条字节码指令的地址)
四、本地方法栈(Native Method Stack)
1.本地方法栈与虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,本地方法栈为虚拟机使用到的Native方法服务。
2.由于虚拟机规范中没有对本地方法栈中的语言、使用方式与数据结构进行强制规定,有的虚拟机(如Sun HotSpot虚拟机)直接把本地方法栈和虚拟机栈合二为一。
3.本地方法栈也会抛出StackOverflowError异常和OutOfMemoryError异常。
五、Java堆(Java Heap)
1. Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,是虚拟机所管理的内存中最大的一块。此内存区域的唯一目的就是【存放对象实例和数组】,几乎所有的对象实例和数组都在这里分配内存。
2. Java堆是垃圾收集器管理的主要区域,也称为GC 堆。现在收集器基本采用分代收集算法,所以Java堆还可细分为:新生代和老年代;再细致点分为Eden空间,From Survivor空间,To Survivor空间等。
根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。
3. 如果在堆中没有内存完成实例分配,并且堆上也无法再扩展时,将会抛出OutOfMemoryError异常。
4. 内存泄露和内存溢出
内存泄露: 指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用,可用内存越来越少。
内存溢出: 指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于老年代或永久代垃圾回收后,仍然无内存空间容纳新的Java对象的情况。
六、方法区(Method Area)
1.方法区又被称为静态区,是程序中永远唯一的元素存储区域。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
2. Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样 不需要连续的内存和可以选择固定大小或者可扩展之外,还可以选择不实现垃圾回收。
3. 很多开发者更愿意把方法区称为“永久代”(Perm Gen)(Permanent Generation)「总是存放不会轻易改变的内容」
4. 运行时常量池(Runtime Constant Pool)是方法区的一部分。
分析如下图,String str1 = "abc" str1指向常量池,而String str2 = new String("abc"); str2指向堆内存对象,二者地址不同所以str1 == str2结果为false,但是str2.intern()会把字符串值从堆内存移动到常量池中(如果常量池存在则返回该值的地址),这样一来str2和str1都是指向常量池的abc。
6.1运行时常量池
1. Class文件用于存放编译期生成的字面量和符号引用,这部分内容(也可以称为 .Class文件中的静态常量池)将在类加载后进入方法区的运行时常量池中存放。
字面量 : 比较接近Java语言层面的常量概念,如文本字符串、声明为final的常量值等。(final修饰的成员变量和类变量!「类变量即静态(成员)变量)」,也就是除final修饰的局部变量。
符号引用 : 属于编译原理方面的概念,包括
1.类和接口的全限定名(即路径,包名+类名)。
2.字段的名称和描述符。
3.方法的名称和描述符。
当虚拟机运行时,需要从常量池获得对应的符号引号,再在类创建或运行时解析、翻译到具体的内存地址之中(直接引用)。
2. 除了保存Class文件中描述的符号引用外,还会把编译出来的直接引用也存储在运行时常量池中。
3. Java语言并不要求常量一定只有编译期才能生成,也就是并非置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String 类的intern()方法。(后面会分析String类。)
4. 当常量池无法再申请到内存时也会抛出OutofMemoryError异常。
网友评论