深入理解JVM之JVM内存区域与内存分配

以下基本不是原创,都是转载。

 

JVM运行时,首先需要类加载器(ClassLoader) 加载所需类的字节码,加载完毕交由执行引擎执行,执行过程中需要一段空间来存储数据(类比CPU与主存)。这段内存空间的分配和释放过程正是我们所关心的,称为运行时数据区

运行时数据区

如上图所示,运行时数据区包括:程序计数器(即PC寄存器),Java 虚拟机栈(VM Stack),Java 堆(Heap),方法区(Method Area),本地方法栈(Native Method Stack)。下面带领大家深入理解各个数据区域

JVM实际上就是一台虚拟的计算机,目的是为了实现"一次编译,处处执行"。所以,在理解运行时数据区时,完全可以与操作系统系统 内存,寄存器类比学习。

2.2.1 程序计数器

程序计数器是一块较小的内存区域,作用可以看做是当前线程执行的字节码的位置指示器。分支、循环、跳转、异常处理和线程恢复等基础功能都需要依赖这个计算器来完成

注意,Java虚拟机中的程序计数器指向正在执行的字节码地址,而不是下一条。

每条线程都有独立的计数器,保证线程切换恢复正确位置,因此程序计数器这一块内存区域是线程隔离的。该区域是唯一一个没有规定任何OutOfMemoryError的区域

2.2.2 Java虚拟机栈(VM Strack

虚拟机栈也叫栈内存,是在线程创建时创建,是线程私有的,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就 Over,所以不存在垃圾回收也有一些资料翻译成JAVA方法栈,大概是因为它所描述的是java方法执行的内存模型。

每个方法执行的同时创建帧栈(Strack Frame)用于存储局部变量表(包含了对应的方法参数局部变量),操作栈(Operand Stack,记录出栈、入栈的操作),动态链接方法出口等信息。每个方法被调用直到执行完毕的过程,对应这帧栈在虚拟机栈的入栈和出栈的过程。

栈帧是一个内存区块,是一个数据集,一个有关方法(Method)和运行期数据的数据集,我们看一个图来理解一下 Java栈,遵循“先进后出”原则。如下图所示:

大多数人说的stack栈内存,就是指虚拟机栈中局部变量表部分

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象的引用(reference类型,不等同于对象本身,根据不同的虚拟机实现,可能是一个指向对象起始地址的引用指针,也可能是一个代表对象的句柄或者其他与对象相关的位置)和 returnAdress类型(指向下一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,在方法在运行之前,该局部变量表所需要的内存空间是固定的,运行期间也不会改变。

异常

  1. 当线程请求的栈深度大于所允许的深度,抛出StackOverflowError异常。
  2. 长度不够时,虚拟机栈可进行动态扩展,申请内存。若无法申请到足够的内存,抛出OutOfMemoryError异常。

2.2.3 本地方法栈(Native Method Stack

1.本地方法栈与虚拟机栈类似,区别是虚拟机栈记录执行的Java方法(也就是字节码),本地方法栈则记录Native方法

2.在HotSpot虚拟机将本地方法栈和虚拟机栈合二为一。

3.本地方法栈同样会抛出StackOverflowErrorOutOfMemoryError异常。

2.2.4 Java堆(Heap)

1.Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。

2.JVM里所管理内存最大的一块,几乎所有对象实例(包括类范围内的成员变量(其中包括成员变量的基本类型,理由),这就是为什么局部变量一定要初始化而成员变量不初始化也会自动初始化问题)以及数组都在堆上。(类的成员变量单指基本变量的引用??还没弄清晰等更正)

3/Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。

 内存模型

这块区域是垃圾收集器管理的主要区域("GC 堆 ")。现在收集器基本都是采用分代收集算法:新生代采用复制算法,老年代采用标记清理算法。从内存回收的角度,Java堆可以分为新生代(Young Generation)与老生代(Old Generation)。这种划分的方式,是为了更好的回收内存。

如图,新生代还可以分为Eden空间From Survivor空间To Survivor空间

永久代(Permanent Generation)用于存储静态类型数据,与垃圾收集器关系不大。

注意:本图展示的是JVM堆的内存模型,JVM堆内存包括Java堆区域 和 永久代区域。因此,永久代不属于Java堆

异常

Java堆同样可扩展(-Xmx与-Xms参数)。若堆中内存已无法为对象实例分配且无法再扩展,抛出OutOfMemoryError异常。

2.2.5 方法区(Method Area

方法区也叫永久代,在过去(自定义类加载器还不是很常见的时候),类大多是”static”的,很少被卸载或收集,因此被称为“永久的(Permanent)”。

也被称为Non-Heap(非堆),被所有的线程共享的一块内存区域。它用于存储已被虚拟机加载的类信息Object Class Data(加载类的类定义数据) )、常量静态变量即时编译器(JIT)编译后的代码等数据。

方法区也可以是内存不连续的区域组成的,并且可设置为固定大小,也可以设置为可扩展的,这点与堆一样。

垃圾回收在这个区域会比较少出现,这个区域内存回收的目的主要针对常量池的回收类的卸载(后面会提到)。

 

2.2.6 运行时常量池(Runtime Constant Pool)

运行时常量池是方法区的一部分。在字节码文件(Class文件)中,除了有类的版本、字段、方法、接口等先关信息描述外,还有常量池(Constant Pool Table)信息,用于存储编译器产生的字面量和符号引用(其实就是八大基本类型的包装类型和String类型数据(JDK7中String被移到Java堆和将符号Symbols移到native memory(不受GC管理的内存))。这部分内容在类被加载后,都会存储到方法区中的RCP。
 
值得注意的是,运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,所以运行时产生的新常量也可以被放入常量池中,比如 String 类中的 intern() 方法产生的常量。
package intern;

public class Main1 {
    public static void main(String[] args) {
        String s0= "I'm coding";   
        String s1=new String("I'm coding");   
        String s2=new String("I'm coding");   
        System.out.println( s0==s1 );  
        System.out.println( s0==s1.intern());   
        s2=s2.intern();  
        System.out.println( s0==s2 );   
          
    }
}

输出结果

false
true
true

本例中,s0直接保存在常量池,s1与s2的对象实例存储在Java堆中。==直接比较对象的hashCode,因此第一行输出false。s1.intern()方法返回s1在常量池中的引用,没有则创建。
s1存放的字符串已经在常量池中存在,直接返回s0的引用,第二行输出true
同理,s2接收了s2.intern()的返回值,字符串值与s0相同,第三行输出true

 
常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)对其他类型、方法、字段的符号引用.例如:
◆类和接口的全限定名;
◆字段的名称和描述符;
◆方法和名称和描述符。
  池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用.
  很有用且重要关于常量池的扩展:Java常量池详解 http://www.cnblogs.com/DreamSea/archive/2011/11/20/2256396.html
 
运行时常量池是方法区的一部分,因此受方法区内存的限制。当无法申请到内存时,抛出OutOfMemoryError异常。

2.2.7 直接内存

直接内存区并不是 JVM 管理的内存区域的一部分,而是其之外的。该区域也会在 Java 开发中使用到,并且存在导致内存溢出的隐患,如果你对 NIO 有所了解,可能会知道 NIO 是可以使用 Native Methods 来使用直接内存区的。

在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。

 
 
原文地址:https://www.cnblogs.com/furaywww/p/8573935.html