Java内存模型与内存溢出异常
《深入理解java虚拟机》读书笔记,记录一下基本的理论知识,有些地方可能总结不是很清楚,感兴趣的可以阅读原书
Java虚拟机在执行java程序时会把它所管理的内存划分为若干个不同的数据区域(如上图),除了红色里面的外面两部分区域堆和方法区是进程共有的,红色里面我画了2个线程,每个线程里面的三个区域是线程私有的,下面单独列出各个部分的特点
线程私有的三个区域,程序计数器,java虚拟机栈,本地方法栈
程序计数器
- 一块较小的内存空间,当前线程程序执行字节码的行号指示器
- 线程私有的,因为各个线程的执行顺序是互不影响的
- 如果执行java方法,记录的是虚拟机字节码指令的地址,如果正在执行native方法,计数器的值为空 (–>内存区域唯一没有规定OutOfMemoryError的区域<–)
字节码解释器工作时就是通过改变这个计数器的值来选择下一条需要执行的字节码指令
Java虚拟机栈
- 线程私有的,生命周期和线程相同
- java虚拟机栈是执行java方法的内存模型,每个方法执行时都会创建一个帧栈,从方法的调用到完成就是帧栈在java虚拟机栈中的入栈和出栈过程
- 这个区域会抛出StackOverflowError和OutOfMemoryError异常
本地方法栈
- 本地方法栈和虚拟机栈所发挥的作用是非常相似的,一个为执行java方法服务,一个为执行native方法服务
- 在有的虚拟机实现中两者合一
- 这个区域会抛出StackOverflowError和OutOfMemoryError异常
java虚拟机规范给出了一个规定,各个厂商可以在遵从虚拟机规范的前提下自己设计虚拟机,所以各个虚拟机有一些差别
所以线程共有的 堆和方法区
堆
- 虚拟机管理内存中最大的一块
- 所以线程共享
- 虚拟机启动时创建
- 唯一目的就是存放对象实例,几乎所以的对象实例都在这里存放
- 垃圾收集器管理的主要区域,所以堆被称为GC堆
- 逻辑上连续,物理上可以不连续
方法区
- 所以线程共享的一个区域
- 存在虚拟机加载的类信息,常量,静态变量
- 针对这个区域的内存回收目标主要是对常量池的回收和类型的卸载
运行时常量池,方法区的一部分
java对象的创建
1.类加载过程
2.分配内存
3.初始化为零
4.虚拟机对对象进行必要的设置
5.执行init方法
1.当虚拟机遇到new指令时,会首先判断这个类是否被加载过,如果没有加载就是执行类的加载过程
2.加载完成后,为新生对象分配内存,内存的大小在类加载完成后就可以确定(分配内存过程中会引出2个问题,第一个是分配内存采用指针碰撞还是空闲列表?第二个是当new比较频繁时,需要同步操作,使用同步或者TLAB)
3.内存分配完成后,需要将分配的内存空间全部初始化为零值,不包括对象头
4.虚拟机对类对象进行必要的设置,比如这个对象是哪个类的实例,对象的哈希码,对象的gc分代等
对象的访问定位
建立对象是为了访问对象,java程序需要通过栈上的reference数据来操作栈上的具体对象,但是由于reference对象在虚拟机规范中仅仅只规定了一个指向对象的引用,所以通过这个引用如何去定位,访问堆中对象的具体位置2中主流的访问方式,使用句柄和直接指针
OutofMemoryError异常测试
#测试java堆溢出
#堆用于存放对象实例,只要不断的创建对象就可以了
-Xms20m -Xmx20m #指定最小堆大小 , 最大堆大小
#虚拟机栈
# 使用递归测试
-Xss128k #设置栈容量大小
#方法区和运行时常量池
# 使用String.intern()不断加入到常量池
-XX:PermSize=10M -XX:MaxPermSize=10M #方法区最小最大值