1.程序计数器
- 定义:记住下一条JVM指令执行的地址
- 特点
- 线程是私有的,每个线程有一个单独的程序计数器
- 不会存在内存溢出
2.虚拟机栈
- 1 定义(Java Virtual Machine Stacks)
- 每个线程运行时所需要的内存,每个线程有一个单独的栈,称为虚拟机栈
- 每个栈里面包含多个栈帧,栈帧里装着调用单个方法时方法内的信息(变量等) 递归时每递归一层就会产生一个栈帧,存放相应信息
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
- 2 问题思考
- 垃圾回收不涉及栈内存,因为栈帧用完后会自动出栈销毁,不需要进行回收
- 栈的内存空间分配不是越大越好,内存空间固定,单个栈的内存空间越大,所能划分的栈的个数就越少
- 方法内的局部变量是线程安全的
- 什么是线程安全
在方法内定义一个局部变量,对该局部变量进行操作,且没有返回值,是线程安全的
public static void m1() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
在方法内部定义一个局部变量,对改局部变量进行操作,操作后进行返回,是线程不安全的
public static StringBuilder m3() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
对一个传入的参数进行操作,
public static void m2(StringBuilder sb) {
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
定义一个静态量,两个方法都对这个量进行操作,是线程不安全的
- 总结:如果方法内局部变量没有逃离方法的作用访问,他是线程安全的;如果是局部变量引用了对象,并且逃离了方法的作用范围,那么就是线程不安全的
- 3 栈的内存溢出 StackOverflowError
- 栈帧过多导致内存溢出:递归层数过多
- 栈帧过大导致栈内存溢出:一个方法内包含了过多的数据
- 4 线程的运行诊断
public static void main(String[] args) throws JsonProcessingException {
//新建一个部门对象
Dept d = new Dept();
//设置部门的名字
d.setName("Market");
//新建一个员工对象
Emp e1 = new Emp();
//设置员工1名字
e1.setName("zhang");
//设置员工部门为d
e1.setDept(d);
Emp e2 = new Emp();
//设置员工2名字
e2.setName("li");
//设置员工部门为d
e2.setDept(d);
//设置部门里有e1,e2两个员工
d.setEmps(Arrays.asList(e1, e2));
//转换java
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(d));
}
在转换对象过程中,{name:"Market",emps:[{name:"zhangsan",dept:{name:{} ,dept{name:{},…}部门添加员工,而员工中又有一个部门对象,部门对象中又有员工,无限循环,造成内存溢出。
3.本地方法栈
和操作系统交互的方法
4.堆 Heap
- 通过new创建关键字,创建的对象都会使用堆内存
- 是线程共享的,堆中的对象都需要考虑线程安全问题(堆是线程不安全的)
- 有垃圾回收机制
5.方法区
- 定义:存的是和类相关的信息,逻辑上是堆的一部分 实际不一定
- 方法区内存溢出(元空间内存溢出:原空间使用的是系统内存)MetaSpace
- 运行时常量池
- 常量池:一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数信息等等
- 运行时常量池:常量池是.class文件中的,当该类被加载,它的常量池信息就会被放入运行时常量池,并把里面的符号变成实际内存地址