JVM——DAY1(程序计数器,栈,堆)
JVM的内存结构
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 堆
- 方法区
程序计数器
二进制字节码-->解释器-->机器码-->CPU
作用
记录下一条JVM指令的执行地址
特点
- 线程私有的(每个线程有自己的程序计数器):java支持多线程运行,CPU给每个线程分配一个时间片,如果在时间片内没运行完,去执行另一个线程在执行该程序。线程切换的过程中,将记录线程一应该执行的下一条代码
- 不会存在内存溢出
栈
虚拟机参数
-Xss+内存大小
特点
先进后出(例如:手枪弹夹,羽毛球桶)
定义
- 虚拟机栈:每个线程运行需要的内存空间
- 栈帧: 每个方法运行时需要的内存
- 每个线程由多个栈帧做成,对应着方法调用时占用的内存(方法内调用别的方法)
- 每个线程只能有一个活动栈帧,对应着当前正在执行的方法
执行完对应的方法后,栈帧出栈,将对应的内存释放掉
问题辨析
-
垃圾回收是否涉及栈内存?
不需要,方法(栈帧)在执行后会被自动弹出栈 -
栈内存分配越大越好吗?
不是,栈内存越大,线程越少(机械内存固定10M,一个栈2M则可以运行5个线程;一个栈1M,则可运行10个线程) -
方法内的局部变量是否安全?(多个线程对这个变量是共享的还是这个变量对每个线程是私有的)
不会,每个线程各有一个栈,各自的栈里各有一个栈帧存储x,互不干扰。
如果方法内局部变量没有逃离方法的作用访问,他是线程安全的
如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
内存溢出
- 栈帧过多导致栈内存溢出(递归死循环)
- 栈帧过大(不太容易出现)
线程诊断
- 案例一:CPU占用过多(需要Linux操作系统)
- 用top定位哪个进程对cpu的占用过高
- ps H -eo pid,tid,%cpu|grep进程id(用ps命令进一步定位哪个线程引起cpu占用过多)
- jstack进程(可以根据线程id找到有问题的线程,进一步定位问题代码的源码行号)
- 案例二: 程序运行很长时间没有结果(需要Linux操作系统)
本地方法栈
在调用本地方法是提供的内存空间(本地方法:不是由java代码编写的方法,Java代码的限制,底层的代码需要c/c++实现的本地方法。如:object类的clone()方法)
堆
虚拟机参数
-Xmx+内存大小
Heap堆
通过new关键字,常见对象都会使用堆内存
特点
- 它是线程共享的,堆中对象都需要考虑线程安全性问题
- 有垃圾回收机制
堆内存溢出
String a = "hello";
List<String> list = new ArrayList<>();
while(true){
list.add(a);
a = a + a;
}
list:hello / hellohello / hellohellohello...
堆内存诊断
- jps工具:查看当前系统中有哪些java进程
- jmap工具:查看堆内存占用情况
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("1....");
Thread.sleep(30000);
byte[] array = new byte[1024 * 1024 * 10];
System.out.println("2....");
Thread.sleep(30000);
array = null;
System.gc();
System.out.println("3....");
Thread.sleep(1000000L);
}
}
运行以上代码打开控制台选择Terminl(命令行界面)
在“1...”打印出后输入jps
在“2...”打印出后输入jhsdb(看jdk,有的不需要) jmap -heap 45984(你运行的程序前面的代号)
在“3...”打印出后输入jhsdb(看jdk,有的不需要) jmap -heap 45984(你运行的程序前面的代号)
观察比较堆内存的使用情况
- jconsole工具:图形界面的多功能监测工具,可以连续监测
运行以上代码打开控制台选择Terminl(命令行界面)输入jconsole,回车会跳出Java监控和管理控制台,选择你运行的程序连接-->不安全的;连接观察内存占用情况
案例
- 垃圾回收后,内存占用仍然很高
使用jvirsualvm(可视化的形式展示虚拟机,在控制台使用)