一、JVM内存
1.线程共享内存
① Java堆区:用于存储对象实例
② 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
③ 运行时常量池:方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,
还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量
和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
2.线程私有内存
①程序计数器:当前线程所执行的字节码的行号指示器。为了线程切换后能恢复到正确的执行位置。
②Java栈:生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时
候都会同时创建一个栈帧(Stack Frame ①)用于存储局部变量表(各种基本数据类型、
对象引用和returnAddress 类型)、操作栈、动态链接、方法出口等信息。每一个方法被
调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
③本地方法栈:用于支持本地方法的执行。
二、内存分配
1. 开辟内存空间
①指针碰撞
如果内存空间以规整和有序的分布方式,即已用和未用的各占一边,彼此之间维系着一个记录下一次
分配起始点的指针,当为新对象分配内存时,只需要通过修改指针的偏移量将新对象分配在第一个空
闲内存位置上。
②空闲列表
有一个列表,其中记录中哪些内存块有用,在分配的时候从列表中找到一块足够大的空间划分给对
象实例,然后更新列表中的记录。
2.分配内存过程
①线程安全问题
堆区和方法区是线程共享区域,任何线程都可以访问到区域中的数据,由于对象实例创建在JVM中
非常频繁,因此在并发环境下从堆区中划分内存空间是非线程安全的,所以务必需要保证数据操作
的原子性。
②TLAB空间分配
基于线程安全的考虑,如果一个类在分配内存之前已经成功完成类装载步骤之后,JVM优先选择
在TLAB(Thread Local Allocation,本地线程分配缓冲区)中为对象实例分配内存空间。
TLAB在Java堆区中是一块线程私有区域,它包含在Eden空间内,除了可以避免一系列的非线程
安全问题外,同时还能够提升内存分配的吞吐量。将这种分配方式称之为快速分配策略
-XX:UseTLAB | 设置是否开启TLAB空间 |
-XX:TLABWasteTargetPercent | 设置TLAB空间所占用Eden空间的百分比(默认TLAB内存非常小,仅占1%) |
③Eden空间分配
一旦对象在TLAB空间分配失败时,JVM就尝试着通过使用加锁机制保证数据操作的原子性,从
而直接在Eden空间中分配内存,如果当Eden空间也无法分配内存时,JVM就会执行Minor GC,
直至最终可以在Eden空间中分配内存为止
④老年代中分配
如果是大对象直接在老年代中分配
3.初始化实例对象
①零值初始化
②初始化对象头和实例数据
③将对象引用入栈后再更新PC寄存器中的字节码指令地址
三、栈上分配
1.逃逸分析
目标:分析出对象的作用域。
当一个对象被定义在方法体内部之后,它的受访权限仅限于方法体内,一旦其引用被外部成员引用
后,这个对象就因此发生了逃逸。反之如果定义在方法体内的对象并没有被任何的外部成员引用时,
JVM就会为其在栈帧中分配内存空间
2.生命周期
由于对象直接在栈上分配内存,因此GC就无需执行垃圾回收。栈帧会伴随着方法的调用而创建,
伴随着方法的执行结束而销毁,由此可见,栈上分配的对象所占用的内存空间将会随着栈帧的出
栈而释放。
四、内存异常
1.内存溢出:指程序所需要的内存超出了系统所能分配的内存(包括动态扩展)的上限。
2.内存泄露:内存泄露是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,
因而造成了资源的浪费。