读《深入Java虚拟机》的时候,第一章就是描述JVM在内存上的结构模型,特此记录整理。(图暂时借一下YSOcean的,稍后用自己画的替换)。
JVM运行时内存结构(内存结构模型)
java虚拟机规范定义的运行时数据区:
- 堆:最大的的一块是堆,属于线程公有的区域,用来存放对象实例。
- 栈:分为本地方法栈,以及虚拟机栈,是线程私有的,分别用于调用native方法和java方法。每次调用方法的时候,栈顶会创建一个栈帧,存放局部变量表、操作数栈、动态链接、方法返回地址。
- 程序计数器:用来存储当前执行的指令地址,是线程私有的。(唯一一个不会抛OOM的区域)
- 方法区:线程共享区域,存放类的结构信息、常量、静态变量、运行时常量池(存放编译期产生的字面量和符号引用)。
Hotspot JDK1.8的运行时数据区:
hotspot运行时数据区
与JVM对于运行时内存结构模型的规范相比,Hotspot的具体实现还是有一些差别的,主要是以下三点:
①、将Java虚拟机栈和本地方法栈合二为一;
②、元数据区取代了方法区,并且元数据区不在Java虚拟机中,而是在本地内存中。
③、运行时常量池由方法区中移到了堆中
垃圾回收与异常
栈
-
StackOverFlow:如果线程申请的栈深度大于虚拟机所允许的最大深度,会报StackOverFlow。说白了就是某个线程里的栈深度过深(递归深度过深,比如没有终止条件的无限递归)就会报StackOverFlow,这里的最大深度是由参数xss控制的。
-
OutOfMemory:如果虚拟机在拓展栈的时候无法申请到足够的内存空间,会报OutOfMemory。不止是栈,堆里也会发生OOM,说白了就是JVM的物理内存不够用了。刚才的StackOverFlow是针对任何一个线程而言的,因为最大栈深度是对每个线程的限制;OOM则是针对虚拟机整体而言的。一般来说,开启过多线程会引起OOM。
-
StackOverFlow和OOM的关系:为了解决StackOverFlow,可以把每个线程的空间设置的大一些;但如果这么做了,同样的JVM空间,可供开启的线程数就少了,更易造成OOM。
Java堆
- 线程共享,存放对象实例和数组(随着JIT的发展和逃逸分析技术的成熟,有的对象会分配在栈上);
- 垃圾收集:java堆可以进一步分为新生代和老年代,而新生代又可以分为Eden空间、From Survivor空间、To survivor空间,主要是因为新生代使用的是复制算法。
- 根据Java虚拟机规范,Java堆可以处于物理上不连续的内存空间中, 只要逻辑上连续即可,实现时既可以实现成固定大小,也可以是扩展的。如果堆中没有完成实例分配,并且无法扩展,将抛出OOM异常。
方法区
存储已经被JVM加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池用来存放编译期生成的各种字面量和符号引用。
方法区也称为永久代,因为垃圾回收期对方法区的垃圾回收较少,主要是针对常量池的回收、对类型的卸载,回售条件比较苛刻,经常会导致内存泄漏,当方法区无法满足内存分配时抛出OOM异常。