[jvm] -- 内存模型篇
-
内存模型
JDK1.6
JDK1.8
-
线程私有的:
- 程序计数器
- 虚拟机栈
- 本地方法栈
-
线程共享的:
- 堆
- 方法区
- 直接内存 (非运行时数据区的一部分)
-
程序计数器
- 线程私有
- 两个作用
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
- 程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
-
Java 虚拟机栈
- 线程私有
- 生命周期和线程相同
- Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有
- 局部变量表
- 操作数栈
- 动态链接
- 方法出口信息
- 出现的两种错误
- StackOverFlowError : 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
- OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 错误。
-
本地方法栈
- 线程私有
- 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。
- 也会出现 StackOverFlowError 和 OutOfMemoryError 两种错误。
-
堆
- 线程共享
- 唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存
- Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆
- Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存
- JDK 7 版本及JDK 7 版本之前(jdk 8 之后移除了方法区,取而代之是元空间)
- 新生代内存(Young Generation)
- 老生代(Old Generation)
- 永生代(Permanent Generation)
-
方法区
- 线程共享
- 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 被称为永久代
- jdk 1.8将其移除了,换成了元空间,若不指定大小,会耗尽所有可用的系统内存。
- 运行时的常量池也是方法区的一部分,JDK1.7及之后版本的JVM已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池
-
永久代 (PermGen) 替换为元空间原因?
- 整个永久代有一个 JVM本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小
- 元空间里面存放的是类的元数据,这样加载多少类的元数据就不由MaxPermSize控制了,而由系统的实际可用空间来控制,这样能加载的类就更多了
- 在 JDK8,合并HotSpot和JRockit的代码时,JRockit从来没有一个叫永久代的东西,合并之后就没有必要额外的设置这么一个永久代的地方了
明明可以靠才华吃饭,非要靠脸~