一、运行时数据区域
Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分为若干个不同的数据区域,简称运行时数据区域。其结划分构图如下图所示。
其中方法区和堆为线程共享的,而虚拟机栈、本地方法栈和程序计数器是线程私有的。
0、为何要了解虚拟机内存使用情况
对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出的问题,看起来由虚拟机管理内存一切都很美好。不过,正是因为Java程序员把控制内存的权利交给了Java虚拟机,一旦出现内存泄漏、溢出方面的问题,如果不了解虚拟机是怎么样使用内存的,那排查错误、修正问题将会成为一项异常艰难的工作。
1、程序计数器
程序计数器是一块较小的空间,它可以当作是当前线程所执行的字节码的行号指示器。
作用:
(1)字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
(2)在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
特性:
(1)线程私有,且每个线程都有一个独立的计数器,互不影响。
(2)唯一一个不发生OutOfMemoryError情况的区域。
2、Java虚拟机栈(VM Stack)
虚拟机栈为虚拟机执行Java方法(Java字节码)提供服务,描述的是Java方法执行的线程内存模型。每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表
,操作数栈
,动态链接
,方法出口
等信息。
局部变量表:
- 基本数据类型:四类八种
- 对象引用(reference类型):可能是一个指针或句柄,用于表面对象的位子
- returnAddress类型:指向了一条字节码指令的地址
异常:
(1)StackOverflowError:线程请求的栈深度大于允许的深度时。
(2)OutOfMemoryError:虚拟机栈动态扩容时。
3、本地方法栈
本地方法栈为虚拟机使用到的本地(Native)方法服务。本地方法可由C/C++实现,然后供Java调用。
异常:
(1)StackOverflowError:线程请求的栈深度大于允许的深度时。
(2)OutOfMemoryError:虚拟机栈动态扩容时。
4、堆
Java堆是被所有线程共享的一块区域,此区域的唯一目的就是存放对象实例,正因为对象实例在该区域存储,所以Java堆也是GC(Garbage Collection)主要发生的地方,也可以称其为”GC堆“。
存储:
(1)对象实例
(2)数组
堆参数:
① -Xmx:设置初始分配大小,默认为物理内存的1/64。
② -Xms:最大分配内存,默认为物理内存的1/4。
可通过在VM options中设置这两个参数的大小来调整堆的大小,例如:
VM options: -Xmx1024m -Xms1024m -XX:PrintGCDetails
异常:
(1)OutOfMemoryError:如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时。
5、方法区
方法区与 Java 堆一样,是各个线程共享的内存区域。该区域也有少许GC
,主要针对常量池的回收和对类型的卸载。
存储:
(1)已被虚拟机加载的类信息(类模板)
(2)常量
(3)静态变量
(4)即时编译器编译后的代码等数据
永久代:
方法区也被称为永久代,也有一定的区别,方法区是规范,永久代是具体实现。
Java8之后,永久代被替换为元空间,并移出方法区,存入直接内存中。
运行时常量池:
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
异常:
(1)OutOfMemoryError:既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出。