方法区:
1. 存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。唯一元素。
2. JVM用持久代(Permanet Generation)来存放方法区。
3. 是各个线程共享的内存区域。线程公有。
4. 可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。 默认16m 最大64m。
虚拟机栈:
1. 栈存放的是局部变量和方法调用。每执行一个方法都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放本次方法调用的临时变量,参数和中间结果。
2. 线程私有的。每个线程都有一个私有的栈。
3. 其大小可以设置为固定大小和动态增长。(编译期间内存空间分配)
4. 生命周期也可以确定,随着线程的创建而创建,方法返回,数据就会自动消失。
5.当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误。当那就是当申请不到空间时,会抛出 OutOfMemoryError。
本地方法栈:
1. 与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。支持native方法的执行,存储了每个native方法调用的状态.一个支持native方法调用的jvm实现的数据区
2. 线程私有
堆
1. new出来的对象和数组 只存放对象本身 不存放对象的引用和基本数据类型。
2. 被各个线程共享的内存区域.线程公有
3. 由垃圾回收器负责回收,生命周期不需要管理。因为其动态的分配内存空间,其存储速度也相对慢。
4. 堆被划分为新生代和老年代。(比例为1:3)新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代GC(Minor GC)任然存活的对象。新生代又分为两个survior区和一个eden区 比例为(1:1:8)
5. 当申请不到空间时会抛出 OutOfMemoryError
程序计数器(寄存器):
1. 是最小的一块内存区域 用来记录当前线程记录当前线程正在执行的指令。如果当前正在执行的方法是本地方法,那么此刻程序计数器的值为undefined。
2. 线程私有
3.这是唯一一个不会抛出运行时outofmemory的运行时数据区
运行时常量池:
0. jdk1.6字符串常量池存在于方法区。 jdk1.7存在于堆区 jdk1.8位于元空间。
1. jvm为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被称为字符串常量池或字符串字面量池。
2. 工作原理:当代码中出现字面量形式创建字符串对象时,jvm首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则创建新的字符串对象,并在字符串常量池中开辟新的内存存入,并返回该引用。
3. 字符串常量池存储的是字符串对象的一个引用。
4. 字符串常量时实现的前提条件就是string对象的不可变。这样可以安全保证多个变量共享同一个对象。
5. 优点:减少相同内容字符串的创建,节省内存空间。 缺点:以时间换空间 时间是指cpu需要在字符串常量池中查找是否有内容相同的引用,其内部实现为hashtable 计算成本相对较低。
6. intern函数
分析内存模型可以从存放类型角度比较 从生命周期和大小是否可控角度比较 从线程私有公有角度比较 从异常的角度比较。
其他⚠️:字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定(常量池和堆).有的是编译期就已经创建好,存放在字符串常 量池中,而有的是运行时才被创建.使用new关键字,存放在堆中。
关于永久代和元空间
1. 只有hotpot虚拟机 其他的虚拟机没有永久代。 永久代在之前是位于方法区中。
2. 移除永久代的工作从1.7之后就开始了,在jdk1.7中 存储在永久代的部分数据就开始转移到java heap或native heap中,比如字面量就在1.7时到了java heap中,类的静态变量(class statics)转移到了java heap,但永久代还存在于1.7中,没有完全移除。
3. 到了1.8,就不存在永久代这一说法了,取而代之的是元空间(Meta Space)。
4. 元空间,本质和永久代类似,都是jvm规范中方法区的实现,不过元空间和永久代最大的区别是:元空间不在虚拟机中,而是本地内存,因此,默认情况下,元空间的大小受本地内存的限制。
5. 取消永久代的原因:在方法区中容易出现性能问题和内存溢出,类信息的大小很难确定,永久代大小指定困难。永久代为gc带来复杂度,回收率偏低。