Java内存
java虚拟机内存
java虚拟机管理着内存,所以出现内存泄漏时,必须要对java虚拟机很了解才能有效的排查错误
运行时数据区域
运行时数据区的产生,如图1:
所包含的运行时数据区有:如图2
其中 方法区
和 堆
是所有线程共享的数据区。其他的都是线程隔离区
1. 程序计数器
是什么: 是一块内存空间。
有什么用: 用于指示当前线程所执行的字节码的行号。在虚拟机模型中,字节码解释器就是通过改变程序计数器 的值来选取下一条需要执行的字节码指令。
在哪些地方用到了:线程恢复、循环、分支、跳转、异常处理等。
在虚拟机的多线程中的作用: 首先要了解虚拟机的多线程的实现方式,是通过线程之间的轮流切换,和处理器执行时长的分配实现。又因为处理器在任何时刻,都只能有执行一个线程中的指令。所以在线程切换过后,要回到正确的执行位置,这时就系要给每条线程分配一个专用的程序计数器。所以这种内存区域是 线程私有 的内存。
2. Java虚拟机栈
也是线程私有的,和线程共存亡
java方法执行的内存模型,如下图3所示:
图中的 栈内存 就是指的虚拟机栈,而其中的每个·栈帧·用于储存局部变量表
、操作栈
、 动态链接
、 方法出口
等
局部变量表: 存放了编译期已知的基本数据类型、对象引用和returnAddress类型。它所需的内存空间大小在编译的时候就完成分配了,所以在方法运行的时候不会改变它的大小了。
常见异常:①报StackOverflowError异常:线程请求的栈的深度大于虚拟机允许的深度。
②报OutOfMemoryError:当虚拟机栈扩展时无法申请到足够的内存时。
3. 本地方法栈
与虚拟机栈的区别,前者是为执行Java方法(也就是字节码)服务。而后者则是为虚拟机使用到的Native方法服务。
4. Java 堆
是什么: 被所有线程共享的一块内存区域,在虚拟机启动时创建。可以处于物理上不连续的内存空间中,主要逻辑上是连续的即可。
主要用途: 用于存放对象实例和数组。是垃圾回收器管理的主要区域,也被叫做“GC堆”。
5. 方法区
是什么: 是各个线程共享的内存区域,不需要连续的内存,可以选择固定大小或者可扩展。也可选择不实现垃圾回收。
有什么用: 用于存储已被虚拟机加载的类的信息、常量、静态变量、即时编译后的代码等。
关于它的垃圾回收:虽然可以选择不实现回收垃圾,而且也很少出现在这块区域进行垃圾回收行为。但是在方法区的数据也并非永久存在,方法区的内存回收主要针对常量池的回收和类型的卸载,但是卸载效果差强人意,容易导致内存泄漏。
5.1 运行时常量池
是什么: 是方法区的一部分。相对于Class文件常量池,它具备动态性,也就是Java语言在运行期也可能将新的常量放入池中,如String的intern()方法便是利用了这个特性。
什么用:用于存放编译期生成的字面量和符号引用,在类加载后,将其存放到方法区的运行时常量池中。
6. 直接内存
也就是本机的自己本身的内存。不属于虚拟机中的内存区域。在配置虚拟机参数时,要考虑各个内存区的总大小不能超过物理内存,不然也会导致OutofMemoryError异常。
对象访问
对象访问过程:主流的访问方式有使用句柄 和 直接指针,如下两图所示,
示例:
Object obj = new Object();
“Object obj” 这部分,将会反映到Java栈本地变量表中, 作为一个reference类型数据出现。
"new Object()"这部分,会反映到Java堆中,产生一块结构化内存,用于存储Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)。
最后要能找到此对象类型的数据(如对象类型、父类、方法、实现接口等)的地址信息,这些数据要在方法区查找
使用句柄方式,在Java堆中会分出一块内存叫“句柄池”,reference中存储了对象的句柄地址。句柄中包含了对象实例数据和类型数据的地址信息
使用指针访问,reference中直接存储对象地址。
两大方式的区别: 句柄式:能在reference中存储稳定的句柄地址。在对象被移动时,只会改变句柄中的实例数据指针,reference本身不需要被修改。
指针访问:最大优点就是 快。节省了一次指针定位的时间
涉及的内存区: Java栈、Java堆、方法区