JVM---内存结构
程序计数器
Program Counter Register
- 是记住下一条JVM指令的执行地址
- 特点
是线程私有的
不会存在内存溢出
虚拟机栈
每个线程运行所需要的内存,成为虚拟机栈
每一个栈是由多个栈帧组成,栈帧是每个方法运行时需要的内存,一个栈帧对应着一个方法调用
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
垃圾回收是否涉及栈内存?
不会,栈内存是一次次的方法调用产生的栈帧内存,而栈帧内存在方法调用完成后,会自动出栈。
栈内存分配越大越好吗?
通过参数-Xss size参数来指定栈的大小
栈内存如果分配的大,则可以执行的线程数,越来越少,因为物理内存是一定的。
方法内的局部变量是否是线程安全?
局部变量是线程私有的,所以是线程安全的
如果变量是多个共享的,则需要考虑线程安全问题
总结:如果方法内的局部变量没有逃离方法的作用范围,它是线程安全的
如果局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
(引用作为参数,作为返回值)
/**
* 局部变量的线程安全问题
*/
public class Demo1_17 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append(4);
sb.append(5);
sb.append(6);
new Thread(()->{
m2(sb);
}).start();
}
public static void m1() { //线程安全的
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
//不是线程安全的,StringBuilder作为参数,main线程在修改它,新的线程也在修改,属于线程共享的
public static void m2(StringBuilder sb) {
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;
}
}
栈内存溢(java.lang.*Error)
- 栈帧过多导致栈内存溢出(方法的递归调用)
- 栈帧过大导致栈内存溢出(出现情况较少)
- 第三方库的使用也会导致栈内存溢出(json解析)
线程运行诊断
案例1:cpu占用过多
- linux中用top定位那个进程对cpu的占用过高
- ps H -eo pid,tid, %cpu | grep 进程id(ps命令进一步定位是哪个线程引起的cpu占用过高)
- 使用JDK自带的jstack工具,使用的格式:jstack 进程id,可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号
案例2:程序运行很长时间没有结果
本地方法栈
给本地方法运行提供内存空间,也是线程私有的
堆
- 通过new关键字,创建对象都会使用堆内存
- 它是线程共享的,堆中对象需要考虑线程安全问题
- 有垃圾回收机制
堆内存溢出问题(java.lang.OutOfMemoryError)
调整堆空间的最大值:-Xmx
排查堆内存问题时,将堆空间调小些
堆内存诊断
jps工具:查看当前系统中有哪些java进程,显示进程编号
jmap工具:查看堆内存占用情况,只能查询某一个时刻进程堆内存占用情况,jmap -head 进程id
jconsole工具:图形界面的,多功能的监测工具(线程,cpu),可以连续监测
jvisualvm工具:图形界面的
方法区
是线程共享的,存储了和类的结构相关的信息,方法区在虚拟机启动时创建,方法区是一个规范,可有不同的实现,JDK1.8之前方法区采用永久代实现,JDK1.8方法区采用元空间来实现
上一篇: *电视台电视译制片目录
下一篇: JVM内存结构详解