引言
先上图
JVM结构.png
栈
功能:存放局部变量(线程栈)
1. 栈帧
(1)概念
一个方法对应一块栈帧内存区域,该栈帧中保存方法中声明的局部变量
栈帧工作概念图.png
(2)栈帧结构
栈帧由局部变量表,操作数栈,动态链接,方法出口等部分组成。其中:
操作数栈:方法运行时,用于变量操作的临时内存,如运算(1 + 1 = 2)时在操作数栈分配的内存中完成,后复制给栈帧中的局部变量;
动态链接:运行时,将符号变为其代码对应的直接内存地址(直接引用);
例如:func()运行时,将方法区中对应逻辑地址拷贝入动态链接之中;
方法入口:返回main()栈帧;
2.程序计数器
(1) 概念:每一个线程独有的内存空间,存放每个线程即将执行的代码行号
(2) 作用:多线程场景下,当前线程CPU被抢占,挂起后由程序计数器记录的行号恢复程序
程序执行器概念图.png
3. 栈,方法区与堆关系
栈中对象通过指针指向其堆中分配地址
方法区中常保存常量,静态变量及类信息
栈,方法区与堆关系.png
4. 对象的创建及结构
对象创建流程.png4.1 分配内存的方法:
(1)指针碰撞方法:
条件:当Java堆内存绝对整齐的排列时(较常见)
(2)空闲列表方式:
当java堆中已用内存不规整时,已使用的内存和空闲内存相互交错,无法使用指针碰撞方法,虚拟机中维持一个map,记录还有哪些内存块可用,有新对象要分配内存时,JVM分配map中的空闲内存块,并更新map中的信息。
5. 堆结构与垃圾收集
5.1 堆结构与垃圾收集结构图
堆结构与垃圾收集.png注1: 一般老年代最后留存的对象为静态对象,在7*24小时web服务中,静态对象将一直存活
注2: 一个OOM(内存溢出)小案例
public class HeapTest {
byte[] a = new byte[1024 * 100];
public static void main(String[] args) {
ArrayList<HeapTest> heapTests = new ArrayList<>();
while(True) {
heapTests.adds(new HeapTest());
# 通过可达性分析方式不能回收该部分内存
}
}
}
5.2 对象的并发放入方式
在高并发web服务中,对象会尝试通过高并发的方式放入堆中分配的空间,其中有两种方式:
(1)Cas+失败重试方案
(2)本地线程分配缓冲(TLAB:JDK1.8默认方式)
JVM预先为每个线程分配对应的内存块
5.3 对象结构
(1)对象由对象头,实例数据及对齐填充组成
对象结构.png
(2)对象结构的优化:指针压缩(JDK默认:8字节对象压缩至4字节)
- VM options: -XX: +UseCompressedOps
- 对象指针压缩优点:大大缩小对象占用内存,例如:
通过一定的压缩算法,使得:
35位的对象可以由32位的机器进行保存。
5.4 对象内存流程
对象内存流程.png(1)对象逃逸现象及分析
例1:
User u = CreateUser();
public User CreateUser() {
User user = new User();
user.setId(1);
return user;
}
// 局部对象user逃逸出了它的create方法,JVM视情况将这种对象的内存分配至堆中保存
例2:
public void CreateUser() {
User user = new User();
user.setId(1);
return ;
}
// 该局部变量user未逃逸,对于该种情况,JVM将判断对象所占内存大小,若内存较小,将对象保存入方法createUser对应的栈桢中;
// 优点:该方法结束时,内存(栈桢)被销毁,该对象内存被回收,以这种方式降低GC频率
(2)JVM对象逃逸处理
JVM虚拟机通过参数(-XX:+DoEscapeAnalysis) JDK默认开启
标量替换:若栈桢中无连续空间放置对象,仅将对象中成员变量放入栈桢中
标量替换.png
标量替换参数:XX: +EliminateAllocations
5.5 对象的分配:
(1)对象主要在Eden区分配,当Eden区空间不足时触发一次minor GC
(2)大对象直接进入老年代(建议直接进入老年代,不然增加年轻代回收频率)
大对象定义: -XX : PretenureSize Threshold = 10000000(大对象对应阈值)
巧妙设计分代年龄:根据业务需要设置分代年龄,使对象不必经过15次GC才进入老年代区(-XX:MaxTenuringThreshold)
6. 老年代空间分配担保机制
老年代空间分配担保机制.png注:在做gc之前,会判断老年代空间是否可存下eden区所有Obj,若存不下进行一次minor gc,通过老年代担保机制判断老年代剩余空间是否小于历史每次minor gc后进入OLD区对象平均大小,若不满足进行一次full gc。
优点:尽量减少做full gc的次数
7. 对象内存的回收
(1)引用计算方法:
main() {
obj A = new Obj(); // A计数值+1
obj B = new Obj();
A.instance = B; // A计数+2
B.instance = A;
}
A = null; // A计数值-1
(2) 可达性分析
将GC roots对象作为起点,从此向下搜索引用对象,可找到的标为非垃圾对象,其余为垃圾对象.
根结点一般为:
- 线程栈本地变量
- 静态变量
- 本地方法栈变量
8. 常见引用类型
- 强引用
- 软引用
- 弱引用
- 虚引用
9. 如何判断一个类已无用
回收元空间(方法区)内存:3个条件需均满足
- 类所有对象实例已被回收(该对象已无引用)
- 加载该类classLoader被回收(一般为自定义类加载器被回收)
- 该类对应java.lang.class 对象未被引用,无法在其他地方由反射访问该类
一般被回收对象:Tomcat, JSP对象
网友评论