1.java内存模型 / java运行时数据区模型?
元空间属于本地内存 而非JVM内存
内存模型
程序计数器
1.作为字节码的行号指示器,字节码解释器通过程序计数器来确定下一步要执行的字节码指令,比如:顺序执行,选择,循环,异常处理等。
2.程序计数器属于线程私有,当线程切换有切回来的时候程序计数器还记着它之前执行到哪一行指令了,可以接着执行。
程序计数器是唯一一个不会出现 OutOfMemoryError
的内存区域,程序计算器所维护的就是下一条待执行的命令的地址,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
java虚拟机栈
可能出现两种异常:
StackOverFlowError
: 若栈的内存大小不允许动态扩展(hotspot),那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError
错误。
OutOfMemoryError
: 如果栈的内存大小可以动态扩展(Classic), 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError
异常。
java虚拟机栈是线程私有的,与线程的生命周期同步,主要描述的是方法执行的内存模型。由一个个栈帧组成。
每个方法执行都会创建一个栈帧。也是先进后出的顺序。
每个栈帧包括:局部变量表、操作数栈、动态链接、方法返回地址。
https://blog.51cto.com/u_15334563/3473150
局部变量表:用来存储编译器可知的类型(基本数据类型、对象引用(reference))
操作数栈:是的方法调用的中转站,用来存放方法和执行过程产生的中间计算结果和计算过程的临时变量。
动态链接:在一个方法调用另外一个方法时,将符号引用转换为直接引用的过程。(java源文件被编译成字节码时,所有的变量、方法引用都作为符号引用保存在了Class文件的常量池里)
方法返回地址:用于存放调用该方法的pc寄存器的值,方法结束后出栈过程,恢复上层方法的栈帧信息(恢复上层方法的局部变量表、操作数栈、将返回值入调用者栈帧的操作数栈、设置PC寄存器值等,让调用者方法继续执行下去)
本地方法栈
和java虚拟栈功能类似。java虚拟机栈服务于java方法,本地方法栈服务于本地方法。也由栈帧组成,栈帧包括本地变量表、操作数栈、动态链接、出口信息。在hotspot里。本地方法栈和java虚拟栈合二为一了。
堆
堆是jvm里面最大的一块内存区域,也是垃圾回收器主要工作的场所。几乎所有的对象实例和数组都存放在堆内存里。但是有一些对象实例可以不放在堆里,而放在栈里面。因为这些对象是方法私有的,他们在方法里面被创建,但是没有逃逸出去(未返回,也未被外部引用)。
从 JDK 1.7 开始已经默认开启逃逸分析。
逃逸分析://TODO
堆内存模型:
jdk1.7及以前:堆内存主要由三部分组成:新生代、老年代、永久代组成
jdk1.8:堆内存主要由两部分组成:新生代、老年代。
而之前永久代存放的东西被元空间代替。存放到了直接内存。
“永久代与堆内存存在于连续的物理内存上,可以看作是从堆内存中拿出一部分内存作为永久代,但是两者又是互相隔离的,所以永久代称为非堆,此时永久代的大小受限于堆内存的大小,因为永久代是从堆内存拿的空间,逻辑上是与堆内存连在一起的”
为什么要用元空间取代永久代?
1.减少OOM
因为永久代配置的内存大小是有限的(jvm为永久代设置了一个大小),如果动态生成很多class的时候,很有可能出现java.lang.OutOfMemoryError:PermGen space错误。
2.方便合并 HotSpot 和 JRockit 的代码,Rockit没有永久代的概念。
堆内存容易发生的OutOfMemoryError
异常:
java.lang.OutOfMemoryError: GC Overhead Limit Exceeded
: 当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。java.lang.OutOfMemoryError: Java heap space 新建对象,内存空间不够的时候。
方法区(是逻辑区域)
永久代:
1.7之前 字符串常量池、静态变量、类信息、运行时常量池、JIT代码缓存
1.7 类信息、运行时常量池、JIT代码缓存 (字符串常量池、静态变量被移到了堆了)
1.8没了 被元空间取代了
JDK 1.7 为什么要将字符串常量池移动到堆中?
主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC。Java 程序中通常会有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。
方法区常用参数有哪些?
JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小。
-XX:PermSize=N //方法区 (永久代) 初始大小 -XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。下面是一些常用参数:
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小) -XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小 默认unlimited
与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
直接内存
不属于jvm运行时数据区,也不是jvm规范中定义的内存区域。但是会被频繁使用,也可能出现OOM. 不受jvm内存的限制,但是会收到本机总空间内存和处理器寻址空间的限制。
NIO的应用
NIO引入的channel和buffer的方式,可以直接使用本地函数分配堆外内存,然后通过堆中的DirectByteBuffer 对象作为这块内存的引用进行操作,避免了java堆和本地堆来回复制数据,提高了性能。
2.对象的创建过程
类加载检查->分配内存->设置默认初始值->设置对象头->执行init方法
3.对象的内存布局
对象头:第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据
对齐填充:Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍
4.对象的访问定位
句柄:局部变量表里面reference保存句柄的地址,根据堆里面的句柄(包含对象实例数据地址和对象类型数据地址),找堆里的实例数据和方法区的类信息->修改对象信息,不用修改栈
直接指针:局部变量表里面reference保存对象的地址,直接找到堆里面的对象实例数据,再从堆里面对象类型数据的指针找方法区的类信息->查找速度快
标签:java,对象,永久,面试,内存,JVM,OutOfMemoryError,方法 From: https://www.cnblogs.com/jyzyz/p/16985898.html