JVM内存结构
1. 程序计数器(Program Counter Register 程序计数器寄存器)
- 作用:是记住下一条jvm指令的执行地址
- 特点:
- 1.每个线程都有一个自己的程序计数器(线程私有)
- 2.不会存在内存溢出
2. 虚拟机栈(Java Virtual Machine Stacks java虚拟机栈)
2.1 定义
- 栈-线程运行时需要的内存空间,栈由多个栈帧组成
- 栈帧(Frame)-每个方法运行时需要的内存
- 每个线程只能有一格活动栈帧,对应当前正在执行的那个方法
2.2 问题辨析
- 1.垃圾回收是否设计栈内存?
答:不涉及,因为栈每次执行方法时会自动将相应的栈帧弹出栈,实现自动回收。 - 2.栈内存分配越大越好吗?
答:不是,假设物理内存有500m,一个线程占1m,理论上可以同时运行500个线程,而当增大栈内存为2m时,理论最多只能同时运行250个线程,结果是效率没有增加而线程的数目会减少。 - 3.方法内的局部变量是否线程安全?
答:如果方法内局部变量没有逃离方法的作用范围就是线程安全的,否则会存在线程安全问题。(例如:方法中return了该局部变量)
2.3 栈内存溢出(java.lang.StackOverflowError)
-
- 栈帧过多 (递归调用)
-
- 栈帧过大 超过了栈内存(不太容易出现)
-
- 第三方的库 (无限递归)例如:
class Emp{ // 员工类
private String name;
private Dept dept;
... get ... set
}
class Dept{ // 部门类
private String name;
private List<Emp> emps;
... get ... set
}
public static void main(String[] args){
Dept d = new Dept();
d.setName("Market");
Emp e1 = new Emp();
e1.setName("zhang");
e1.setDept(d);
Emp e2 = new Emp();
e2.setName("li");
e2.setDept(d);
d.setEmps(Arrays.asList(e1,e2));
// 部门类转换成Json类型。
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(d));
}
错误提示:无限递归 栈溢出
JsonMappingExcetion:Infinite recursion(StackOverflowError)
解决:加一个@JsonIgnore 阻止将Dept转成json类型
class Emp{ // 员工类
private String name;
@JsonIgnore
private Dept dept;
... get ... set
}
2.4 线程诊断
案例1:cpu占用过多
例如:while(true)
通过定位找到(Linux指令):
- 用top定位哪个进程对cpu的占用过高
- ps H -er pid,tid,%cpu | grep 进程id(用ps命令进一步定位是哪个线程)
- jstack 进程id (可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行数
案例2:程序运行时间很长
例如:死锁
同上定位
3. 本地方法栈(Native Method Stack)
Native方法: 不是由java代码编写的代码 (c ,c++)
本地方法栈:为本地方法提供内存空间
4. 堆(Heap)
4.1 定义
- 通过new 关键字,创建对象都会使用堆内存
特点
- 它是线程共享的,堆中对象都需要考虑线程安全的问题
- 有垃圾回收机制
4.2 堆内存移出(java.lang.OutOfMemoryError)
4.2.1 jps工具 (jdk自带)
查看当前系统中有哪些java进程
4.2.2 jmap工具 (jdk自带)
查看堆内存占用情况 jmap -heap 进程id (jdk高版本 jhsdb jmap --heap --pid 进程id)
4.2.3 jconsole工具 (jdk自带)
图形界面的,多功能的监测工具,可以连续监测
public static void main(String[] args) throws InterruptedException {
System.out.println("1..");
Thread.sleep(20000);
byte[] array = new byte[1024 *1024 * 20];
System.out.println("2....");
Thread.sleep(20000);
array = null;
System.gc();// 回收
System.out.println("3..");
Thread.sleep(1123123);
}
4.2.4 jvisualvm工具 (需要下载插件)
更加详细的界面更好的调优
有些垃圾回收后,内存占用依然很高
5. 方法区
它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。
5.1 方法区内存溢出
-
1.6 方法区在永久代里(java.lang.OutOfMemoryError:PermGen.space)
-
1.8 方法区在元空间里(java.lang.OutOfMemoryError:Metaspace)
5.2 常量池
- 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
- 运行时常量池,常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址