1. 什么是JVM
java 二进制字节码的运行环境,简称:java 虚拟机(Java Virtual Machine)
2. 好处是什么
- 一次编写,到处运行
- 自动内存管理,GC垃圾回收功能
- 数组下标越界检查
- 多态
- …
3. jdk、jre、jvm
4. 学习JVM有什么用?
- 面试
- 理解底层实现原理
- 中高级程序员的必备技能
5. 常见的JVM
JVM是一个规范,只要你遵守规范,那么也可以自己写一个JVM出来
现在最出名的就是Oracle的JVM,以前就是 Eclipse J9
6. PC Register(程序计数器)
右侧为 JAVA 底层源码。是最基础的打印效果
左侧为 编译后的 二进制字节码,
-
左侧绿色数字代表在内存中的内存地址,
-
右侧黑色数字,代表
一行源码 在字节码中所占的标符
6.1 流程:
解释器
通过PC Register
获得字节码 一行源码 的内存地址(通过寄存器)- 解释器将对应内存地址中的字节码进行转换为机器码
- 转为 机器码 后交给CPU处理
6.2 作用:
PC Register 的作用:记住下一条JVM指令的执行地址(内存地址)
6.3 特点
-
线程是私有的
其每一次转换线程完成执行地址交换,都会记录在独自的线程中,若是换线程执行结束后,执行地址记录将会混乱
-
不会存在内存溢出
7. JVM Stacks(虚拟机栈)
栈:线程运行的内存空间
栈帧:内存空间中的数据(每个方法所运行时需要的内存)
7.1 定义
-
每个线程运行时所需要的内存,称为
虚拟机栈
-
每个栈由多个栈帧(Frame)构成,对应着每次方法调用时所占用的内存
-
每个线程只能由一个
活动栈帧
,对应着当前正在执行的那个方法
当最后一个方法(栈帧)用完后,变为一步步变为最初的方法(栈帧)
7.2 问题解析
-
垃圾回收是否涉及栈内存?
不会,栈内存就是会在每次栈帧使用完后就会被弹出栈,不用GC的管理,GC管理堆内存
-
栈内存分配越大越好吗?
并不会,内存分配越大,递归调用次数越多罢了,并不会加快程序的运行效率
-Xss size(线程栈内存参数)
linux、macOS、Orade Solaris(64-bit):1024KB
windows:根据虚拟内存自动分配
-
方法内的局部变量是否线程安全?
常态来说,不会。
若方法交由两个线程执行,那么 一个动态变量 就会存在于这两个线程中执行,结果不影响
但若是静态变量(static),它会将运行交由两个线程,其线程完成计算结果后再交给静态变量更新数值,故而有可能会影响结果静态变量的线程安全
如果方法内的局部变量没有逃离方法的作用范围,它是线程安全的
如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全
/*
线程安全问题
*/
StringBuilder sb = new StringBuilder();
sb.append(3);
sb.append(4);
sb.append(5);
new Thread(()->{
m2(sb);
}).start();
/**
* 线程安全
*/
public static void m1(){
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
System.out.println(sb.toString());
}
/**
* 线程不安全
* @param sb
*/
public static void m2(StringBuilder sb){
sb.append(1);
sb.append(2);
System.out.println(sb.toString());
}
/**
* 线程不安全
* @return
*/
public static StringBuilder m3(){
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
return sb;
}
7.3 栈内存溢出
7.3.1 导致栈内存溢出的原因
-
栈帧过多导致栈内存溢出 (递归)
-
栈帧过大导致栈内存溢出
7.4 线程运行诊断
7.4.1 cpu占用过多(linux环境)
-
用 top 指令 定位哪个进程对 CPU 占用过高
-
ps H -eo pid,tid,%cpu | grep 进程id
(通过ps命令进一步定位是哪个线程引起的cpu占用过高)
-
jstack 进程id
(可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号)
7.4.2 程序运行时间很长时间没有结果
- 死锁问题,同样通过 jstack 进程id,查看源码错误
7.5 代码实现
public class tStack {
public static void main(String[] args) {
// Integer integer = demo1(1);
// System.out.println(integer);
/*
线程安全问题
*/
StringBuilder sb = new StringBuilder();
sb.append(3);
sb.append(4);
sb.append(5);
new Thread(()->{
m2(sb);
}).start();
}
public static Integer demo1(int a){
return demo2(a+2);
}
public static Integer demo2(int b){
return b;
}
/**
* 线程安全
*/
public static void m1(){
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
System.out.println(sb.toString());
}
/**
* 线程不安全
* @param sb
*/
public static void m2(StringBuilder sb){
sb.append(1);
sb.append(2);
System.out.println(sb.toString());
}
/**
* 线程不安全
* @return
*/
public static StringBuilder m3(){
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
return sb;
}
}
8. Native Method Stacks(本地方法栈)
线程私有
它本身的作用就是为本地方法接口提供一个内存空间
8.1 本地方法接口(C/C++ 实现)
对底层CPU进行指令输送,JAVA本身是做不到的,所以得由C/C++实现
它中间会有一个缓存区,也就是直接内存,JAVA通过这个内存区才能够获得到 java对象
9. Heap 堆
线程共享区域
- 通过new关键字,创建对象都会使用堆内存
特点:
- 它是线程共享的,所有堆中的对象都需要考虑线程安全问题
- 垃圾回收机制会干涉,在堆中长时间不使用的数据会被 GC 释放
9.1 堆内存溢出
/**
* 堆内存溢出:java.lang.OutOfMemory: Java heap space
*/
public class OutOfHeap {
public static void main(String[] args) {
int i =0;
try{
ArrayList<String> integers = new ArrayList<>();
String a = "hello";
while (true){
integers.add(a);
a = a+a;
i++;
}
}catch (Exception e){
e.getStackTrace();
System.out.println(i);
}
}
}
9.2 堆内存诊断
-
- jps 工具(命令行)
jps(Java Virtual Machine Process Status Tool)是JDK提供的一个可以列出正在运行的Java虚拟机的进程信息的命令行工具,它可以显示Java虚拟机进程的执行主类(Main Class,main()函数所在的类)名称、本地虚拟机唯一ID(LVMID,Local Virtual Machine Identifier)等信息。另外,jps命令只能显示它有访问权限的Java进程的信息。
虽然jps命令的功能比较单一,但它使用的频率却很高。对于本地虚拟机来说,本地虚拟机唯一ID和操作系统的进程ID(PID,Process Identifier)是一致的,如果同时启动多个Java虚拟机进程,无法根据进程名称确定某个进程,我们就是使用jps命令显示主类名称的功能区分出来。
jps 虚拟机ID 进程ID
-
- jmap 工具
jmap是jdk中提供的一个用来监视进程运行中的jvm物理内存的占用情况的工具。该进程内存内,所有对象的情况,例如产生了哪些对象,对象数量。当系统崩溃时,jmap 可以从core文件或进程中获得内存的具体匹配情况,包括Heap size, Perm size等。
jmap -heap pid
使用jmap会影响线上运行的应用,所以尽量不要在线上执行此命令。
在java9及以上的版本,无法通过jmap -heap pid命令查看堆内存使用情况了。
在java9以后,引入了全新的命令行工具:jhsdb
jhsdb 包含了很多的模块,对应着各个区块的功能
- 在java8以后版本的使用
jhsdb jmap --pid 33504 --heap
由于版本更新,JVM指令都整合在了 jhsdb 中
-
- jconsole工具
JConsole 是一种 Java 监控和管理控制台工具,可以用于监视 Java 虚拟机(JVM)的性能和资源利用情况。它提供了一种图形化界面,可以实时查看 JVM 的运行状态、内存使用情况、线程活动、垃圾回收等信息,以及执行一些管理操作。
使用 JConsole 可以帮助开发人员和系统管理员进行性能调优、内存泄漏排查和故障诊断,从而提高应用程序的可靠性和性能。
控制台直接输入命令:jconsole
-
- JVisualVM工具
JVisualVM是JDK自带的性能检测工具,路径在%JAVA_HOME%/bin下面
可以对堆内存进行dump、快照以及性能可视化分析,也可以安装插件对堆外内存进行分析(我用Buffer Pool),如果项目要求不是特别高,这一款工具已经足够强大加易用。但是也有缺点,据我所知,这些数据是通过RMI协议进行传输,而且监视那一栏采样时间是2S,可能采样精度不够高。