Java虚拟机在执行Java程序的过程中都会把所管理的内存划分为若干个不同的区域,下面就是几个运行时的数据区域:
我们逐个来看看各自的领域:
1.程序计数器
程序计数器就是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器,说白就就是通过这个计数器的值来选取下一条需要执行的字节码指令,就是记录程序行走的步骤,什么循环、跳转、异常处理都是依赖这个计数器完成的。就拿个例子来说说,我们的Java的多线程是通过线程之前的轮流切换来实现的,一个线程停止后,另外一个线程启动,那么回来的时候怎么知道自己执行到哪里呢,就用这个程序计数器来记录了。
2.Java虚拟机栈
虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法被调用直至执行完成的过程,就对应着一个栈帧入栈到出栈的过程。
其实很多人把Java内存区分为堆和栈,这种分法不准确,内部的分法远比这复杂,这里需要指出的是,我们说的Java栈其实就是这里的虚拟机栈了。
3.本地方法栈
本地方法栈与虚拟机栈的作用非常的相似,区别是虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用到的Native方法服务。
4.Java堆
堆是Java虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,这个内存区域的唯一目的就是存放对象实例,几乎所有对象的实例都是在这里分配内存的。
堆其实也是垃圾收集器管理的主要区域,很多时候也被称为“GC堆”。
5.方法区:
方法区跟堆是一样的,是所有线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。
6.运行时常量池
这个运行时常量池也是方法区的一部分,Class文件中除了有类的版本、ziduan 、方法、接口等描述信息外,还有一项信息是常量池,用于放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放在方法去的运行时常量池中。
我们来看看对象访问是如何进行的?
很简单的一句:Object object = new Object();执行了这句话后内存这样执行的:“Object object”这部分产生的结果就是在Java栈的本地变量表上,有一个reference类型数据的出现,而“new Object()”这部分的结果就是在堆上形成一块存储了Object类型的内存,另外,在堆上还有能查到此对象类型数据(父类、实现的接口、方法)的地址信息,这个东西真正存储在方法区中。
不同的虚拟机实现对象的访问有所不同,下面介绍这两种。
1.句柄访问方式
Java堆中将会划分出一块内存来做句柄池,存储的就是对象的句柄地址,包含对象实例数据和类型数据的具体地址信息。
2.直接指针访问方式
就在堆中生成一块内存区,里面既有类型数据的指针也有对象实例数据。
我们来看看JVM中溢出的各种情况吧,了解了解:
1.Java堆溢出
java堆是用于存储对象的,如果我们不断的创建对象的话,在对象数大于对的最大容量的时候,就会发生溢出。
public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while(true) { list.add(new OOMObject()); } } }
这样的话我们就可以在命令行下看到这样的情况:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
这个就是堆溢出的标志了。
2.虚拟机栈和本地方法栈溢出
栈说白就是跟方法的调用有关,如果我们不断的调用方法,局部变量多,栈的容量就很快会满。
public class StackTest { public void method() { method(); } public static void main(String[] args) { StackTest stack = new StackTest(); try { stack.method(); } catch (Exception e) { e.printStackTrace(); } } }
你运行下看看就会出现下面的错误了:
Exception in thread "main" java.lang.StackOverflowError
哈哈,这个就是栈的溢出,请记住它。
3.运行时常量池溢出
要想看到这个溢出就要不停的往运行时常量池添加东西,这里我们要介绍一个方法:String.intern(),这个是Native方法,也就是本地的方法,用法:如果池中包含一个等于此String对象的字符串,则返回此String对象,否则就将这个字符串添加到运行时常量池中,返回此String对象,下面我们就用这个方法来让运行时常量池溢出。
public class RuntimeConstantPoolTest { public static void main(String[] args) { List<String> list = new ArrayList<String>(); int i = 0; while(true) { list.add(String.valueOf(i++).intern()); } } }
然后就会出现下面的错误:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,也就是说这个运行时常量池是放在方法区的,看看方法区的定义就知道了。