JVM内存模型
1. 概述
1.1 内存区
-
程序计数器 :
-
如果线程执行的是个java方法,用于存储下一条指令的地址。详细的说PC寄存器是用来存储指向下一条指令的地址,也就是即将将要执行的指令代码。由执行引擎读取下一条指令。
即Java源代码不能直接去被执行,他得经过一次编译,编译成二进制字节码,里面的一行一行的东西都是JVM指令,Java虚拟机跨平台的基础就是这些JVM指令,这些指令在所有平台都是一致的。但这些指令也不能直接交给CPU执行,他必须要经过一个解释器,这个解释器也是Java虚拟机执行引擎的一个组件,他就专门负责把每一条JVM指令(比如getstatic)解释成为机器码,机器码就可以交给CPU执行。
-
如果为native【底层c++方法】,那么计数器为空。这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。
-
-
虚拟机栈: java方法的执行和结束对应着栈帧的入栈和出栈,
-
栈帧 : 用于存储局部变量表,操作方法行号,动态链接,方法出口等信息;
-
局部变量表所需要的内存空间在编译期完成分配,当进入一个方法时,这个方法在栈中需要分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表大小。
-
Java虚拟机栈可能出现两种类型的异常:
- 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
- 虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。
-
-
本地方法栈 : 本地方法栈是与虚拟机栈发挥的作用十分相似, 区别是虚拟机栈执行的是Java方法(也就是字节码.class)服务,而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++, 我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c / c++代码。
-
堆 : 对于大多数应用来说,堆是java虚拟机管理内存最大的一块内存区域,主要存放一些实例对象(简单理解就是new出来的对象)因为堆存放的对象是线程共享的,所以多线程的时候也需要同步机制。因此需要重点了解下, 其中堆又分为:
-
年轻代 YoungGen
- Eden区
- TLAB 区 ,详情可查看本文
- Survivor 0(survivor from)
- Survivor 1(survivor to)
- 新生代使用了复制算法
- 新生代为gc的重点对象,经官方测试70%对象都生命周期都会在新生代中完
- 新生代又分为了eden、survivor1、survivor2,对象创建先放在eden中,经过一定时间还幸存就会放在幸存者区
- 内存比例分默认为:8:1:1,分别对应eden,s0,s1
- 新生代收集器:Minor GC/Young GC
- 幸存区又分为from 和 to —谁为空谁为to ,始终都会有一个区域为空
- 幸存区不会主动进行垃圾回收,只会eden回收时才会附带进行gc
- 当在幸存区中的阈值达到了15后(默认15可修改)会自动进入老年代
- 当新生区(eden)出现了内存不足时,会进行YGC,那么会将没有指针的对象回收,还有指针引向的对象放入survivor1或者survivor2区域中,eden清空,数据放入一个survivor中。—当第二次进行gc那么会将eden数据放入另一个空的survivor中,并且将当前survivor中有效数据,放入空的survivor中,如此类推。
- Eden区
-
老年代 OldGen
- OldGen
Java7及以前将堆空间逻辑上分成三部分:新生区+老年区+永久代
Java8及以后将堆内存逻辑上分为:新生区+老年区+元空间 -
-
方法区: 保存在着被加载过的每一个类的信息;static变量信息也保存在方法区中;
可以看做是将类(Class)的元数据,保存在方法区里;方法区是线程共享的;当有多个线程都用到一个类的时候,而这个类还未被加载,则应该只有一个线程去加载类,让其他线程等待;
方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。
1.2 类加载子系统
类加载器子系统作用?
类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。
ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
1.3 字节码执行引擎
主要功能:
- 输入的是字节码文件,然后对字节码进行解析并处理,最后输出执行的结果。
- 修改程序计数器当前执行的位置;
2. 内存溢出和内存泄漏是什么
简述一下:
溢出: 所需要用的内存大于系统给的内存
泄漏: 某对象不用了但是没被回收