方法区(Non-Heap):所有线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 。当方法区无法满足内存分配需求时,抛出OutOfMemoryError.
运行时常量池:它是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池(Const Pool Table),用于存放编译期生成的各种字面量和符号引用 。并非预置入Class文件中常量池的内容才进入方法运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法 。当方法区无法满足内存分配需求时,抛出OutOfMemoryError 。
直接内存:并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域 。JDK1.4加入了NIO,引入一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作 。因为避免了在Java堆和Native堆中来回复制数据,提高了性能 。当各个内存区域总和大于物理内存限制,抛出OutOfMemoryError异常 。
2.3 Hotspot虚拟机
2.3.1 对象的创建
new 对象-------》常量池定位类引用---------》检查类引用和引用的类是否被加载、解析和初始化---------》没有,就先执行类加载 。----》创建对象分配内存
类加载通过后----》虚拟机从java堆中分配内存---》内存规整(已分配的内存和未分配的内存区域由一个指针划分开):分配内存时把该指针移动一个对象大小的距离,成为指针碰撞;内存不规整时:从一个大的空闲列表区域分配内存 。
分配内存时存在并发问题----》两种解决方法:1 对分配内存空间的动作进行同步处理(实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性);2 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,成为本地线程分配缓冲 。
new指令执行时,对象的所有字段仍为零,需要等init方法执行后,字段才会赋予新值 。
2.3.2 对象的内存区域
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding) 。
HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等 。另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例 。
2.3.3 对象的访问定位
对象的访问定位也取决于具体的虚拟机实现 。当我们在堆上创建一个对象实例后,就要通过虚拟机栈中的reference类型数据来操作堆上的对象 。现在主流的访问方式有两种(HotSpot虚拟机采用的是第二种):
使用句柄访问对象 。即reference中存储的是对象句柄的地址,而句柄中包含了对象实例数据与类型数据的具体地址信息,相当于二级指针 。
直接指针访问对象 。即reference中存储的就是对象地址,相当于一级指针 。
两种方式有各自的优缺点 。当垃圾回收移动对象时,对于方式一而言,reference中存储的地址是稳定的地址,不需要修改,仅需要修改对象句柄的地址;而对于方式二,则需要修改reference中存储的地址 。从访问效率上看,方式二优于方式一,因为方式二只进行了一次指针定位,节省了时间开销,而这也是HotSpot采用的实现方式
2.4 OutOfMemoryError异常
2.4.1 java堆溢出
2.4.2 虚拟机栈溢出和本地方法栈溢出
2.4.3 方法区和运行时常量池区
2.4.4 本机直接内存溢出
第三章 垃圾收集器与内存分配策略
3.1 概述
程序计数器、虚拟机栈、本地方法栈等3个区域随线程而生,随线程而灭,因为方法结束或线程结束时,内存自然就跟着回收了 。而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序出于运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存 。
3.2 判断对象是否已死
3.2.1 引用计数算法
算法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1,当失效时,计数器就减1,任何时刻计数器为0的对象就是不可能再被用的 。缺点:难以解决对象之间循环相互引用的问题 。
推荐阅读
- Java 线程池 ThreadPoolExecutor 八种拒绝策略浅析
- B-Tree 数据结构详解及Java代码实现
- Docker容器与虚拟机有什么区别
- Java序列化是什么?你知道什么时候需要序列化吗?
- Java Overload 与 Override 差别
- 详解Java多线程锁之Lock和ReadWriteLock
- Java面向对象——成员变量和局部变量
- java第一次调用 Hadoop Java API
- 2019年最流行的五大JavaScript 自动化测试框架
- Java虚拟机最多支持多少个线程?