java自动内存管理
1.运行时的数据区域
java程序在运行的过程中把它所管理的内存划分为若干个区域分别是:
1. 方法区
2. 虚拟机栈
3. 本地方法栈
4,堆
5.程序计算器
2.程序计算器
程序计数器是一块小的内存空间,主要用来记录所执行字节码的行号。
3.java虚拟机栈
java虚拟机栈是线程私有的,生命周期和线程一致。虚拟机栈描述的是java方法执行的内存模型:每个java方法在执行的同时会创建一个java虚拟机栈,用于存储变量表,操作数,动态链表,方法出口等信息。
3.1 StackOverflowError 和 OutOfMemoryError的区别?
stackOverflowError: 当线程请求的栈深度大于虚拟机所允许的深度。
OutOfMenoryError: 如果拓展时无法申请到足够的内存,就会抛出OutofmemoryError异常
4.本地方法栈
本地方法栈基本与java虚拟机栈的形式是一样的,但是唯一的区别是虚拟机栈为虚拟机执行的java方法,但是本地方法栈为虚拟机虚拟机使用的Native方法服务。
Native方法介绍: 点击打开链接
5.java堆(GC堆)
1.java堆是被所有线程所共享的一块内存区域,在虚拟机启动的时候创建,主要时存放对象实例。
2.java堆因为时垃圾收集器所管理的主要区域,因此也叫作GC堆。java堆根据垃圾回收算法的不同又分为了,新生堆和老年代。
6.方法区(Non-Heap非堆)
方法区跟Java堆一样,是各个线程所共享的一块内存区域,用于存储虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
7.直接内存
直接内存并不是java虚拟机运行时的数据区的一部分,也不是虚拟机规范中定义的内存区域。
在jdk1.4中新加入的NIO,引入了一种给予通道与缓冲区的I / O方式,他可以直接使用Native函数库直接分配堆外内存,然后通过一个存储DirectbyteBuffrer对象作为这块内存的引用进行操作,在一些场景中发挥高的性能,避免了java堆和Native堆中来回复制数据
在jdk1.4中新加入的NIO,引入了一种给予通道与缓冲区的I / O方式,他可以直接使用Native函数库直接分配堆外内存,然后通过一个存储DirectbyteBuffrer对象作为这块内存的引用进行操作,在一些场景中发挥高的性能,避免了java堆和Native堆中来回复制数据
8.对象的创建
8.1对象创建的过程
1.虚拟机遇到new指令时,首先检查这个指令的参数是否在常量池中一个类的符号引用,并且去检查这个符号的引用是否已经被加载,解析或者是被初始化过。
2.如果没有,就要进行类加载的操作
3.类加载后就要为新对象分配内存
4.内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)
5.虚拟机要对对象进行必要的设置。例如这个对象是哪个类的实例,如何找到对象的头元素,对象的哈希码,对象的GC分代信息等等。(对于虚拟机来说已经结束对象的创建,但是对于java程序来说,还要执行init方法)
8.2对象的内存分配
对象的内存分配分为两种方法:
1.指针碰撞法:将内存分为了两种,空闲的内存和已经分配的内存,并且java堆绝对时规整的,并且用一个指针将空闲的内存和被分配的内存分割开,分配内存的过程就是将指针移动被分配对象大小的操作。
2.空闲列表法:如果java堆不是规整的,已使用的内存空间和空闲的内存空间交错相间,这时虚拟机需要创建一个空间列表去记录那些内存块时可用的,那些内存块是不可用的。
ps:Java堆是否规整取决于采用的垃圾收集器是否具有压缩规整的功能决定的。
8.2.1对象内存分配中出现的问题
在并发的过程中不是线程安全的(可能出现正在给A分配内存但是还没有结束又使用了旧的指针去给新的B分配内存)
解决方法:
1.一种是对分配内存空间的动作进行同步的处理--实际上是虚拟机采用的CAS配上失败重试的方法保证更新操作的原子性
2.把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲TLAB,那个线程需要分配内存就在那个线程的TLAB上进行分配,只有在TLAB使用完之后分配新的TLAB,才需要同步锁定
9.对象的内存布局
对象在内存中存储的布局可以分为3块区域:
1.对象头
2.实例数据
3.对齐填充
9.1对象头
1.一部分是用于存储对象自身的运行时的数据,如哈希码,GC分代年龄...
2.另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象时那个类的实例。
9.2对齐填充
对齐填充并不是必然的存在,没有特别的含义,它仅仅起着占位符的作用。
10.对象的访问定位
建立对象是为了使用对象,在java中设置了一个reference对象来操作堆上的具体对象,reference对象如何去访问具体的对象呢,具体有两种的方法:
1.使用句柄:reference栈 --》 句柄池(包含了到对象实例数据的指针) --》 对象型数据
2.reference栈 --》 直接指向对象类型数据
优势:
句柄:reference中存储的时稳定的句柄地址,在对象被移动的时候只要改变句柄中的实例数据的指针,reference本身不需要被修改
直接指针:访问的速度比较块,节省了一次的指针访问开销,在Sun HotSpot中使用的是直接指针