定义:Java Virtual Machine --- java运行环境(Java二进制字节码的运行环境)
好处:
- 一次编写 到处运行
- 自动内存管理,垃圾回收功能
- 数组下标越界检查
- 多态
比较:
jvm jre jdk 的区别
jvm 只是运行环境 , 只是一个空壳 ,结合上基础类库(集合类,线程类,io类等)之后,才是真正意义上的java运行环境
在jre的基础上 结合上 编译工具(javac javap 等)就是java开发工具包
内存结构
1、 程序计数器
2、 虚拟机栈
3、 本地方法栈
4、 堆
5、 方法区
1、程序计数器
定义:Pogram Counter Register 程序计数寄存器(寄存器)
作用:记住下一条 jvm 指令的执行地址。(计数器的物理实现是 CPU 中的寄存器)
特点:程序计数器是线程私有的,每一个线程都有属于自己的程序计数器,记录本线程jvm指令的执行位置
不会存在内存溢出
2、虚拟机栈
2.1 定义 Java Virtual Machine Stacks(Java 虚拟机栈)
1 、每个线程运行时所需要的内存,称为虚拟机栈,多个线程同时运行可能会有多个虚拟机栈。
2、每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存。
3、每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。(在栈顶正在执行的方法,就是活动栈帧)
问题辨析:
1) 垃圾回收是否涉及栈内存?
不涉及,因为栈帧内存在每一次方法执行完之后都会自动被弹出释放掉,不需要垃圾回收机制来进行回收。
2) 栈内存的分配越大越好吗?
栈内存可以通过运行代码时通过虚拟机参数指定。
-Xss size
因为物理内存的大小是一定的,如果栈内存设置过大,会导致虚拟机栈的个数减数,即会导致线程数减少。
500 x 1 = 250 x 2
栈内存大只是可以增加递归调用的次数,并不会提高程序的运行效率,反而由于线程数受到限制,可能还会降低程序的运行效率。一般采用系统默认的栈内存大小。
3) 方法内的局部变量是否线程安全?
看一个变量是否线程安全,就看该变量是多线程共享的,还是单个线程私有的。
1 如果方法内局部变量的作用范围没有逃离该方法,则该局部变量是线程安全的。
2 如果该局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全。
3 如果该局部变量是基本类型的值,那么即是逃离了该方法的作用范围,该变量仍然是线程安全的。
/**
*局部变量的线程安全问题
*/
public class Test03 {
public static void main(String[] args) {
}
public static void m1(){
//线程方法内的局部变量,不会有线程安全问题
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb);
}
public static void m2(StringBuilder sb){
//不是线程安全的,StringBuilder 是作为方法参数传入的,
// 可能有别的线程也可能访问到该 StringBuilder 对象
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
public static StringBuilder m3(){
//不是线程安全的,虽然 StringBuilder 是方法内的局部变量,但是作为返回值返回了
//所以将来有可能别的线程会拿到该对象的引用,对该对象的值进行改变
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
} }
判断某个变量是否是线程安全的,除了该变量得是方法中的局部变量,还得判断该变量是否逃离了该方法的作用范围。
2.2 栈内存溢出
栈帧过多导致栈内存溢出
方法递归调用时,没有设置正确的递归结束条件,导致无限递归
package pys;
/*
演示栈桢
*/
public class Demo_1 {
private static int count;
public static void main(String[] args) {
try{
method1();
}catch (Throwable e){
e.printStackTrace(); //java.lang.StackOverflowError :栈内存溢出
System.out.println(count); //21025 //修改栈内存后:2080
}
}
private static void method1(){
count++;
method1();
}
}
栈帧过大导致栈内存溢出
第三方代码中出现两个类中循环引用的问题,导致无限递归。
2.3 线程运行诊断
案例 1:CPU 占用过多
定位
- 用 top 定位哪个进程对 cpu 的占用过高
- ps H -eo pid,tid,%cpu | grep 进程 id(用 ps 命令进一步定位是哪个线程引起的 cpu 占用过高)
- jstack 进程 id 可以根据线程 id 找到有问题的线程,进一步定位到问题代码的源码行数
案例 2:程序运行很长时间没有结果
线程间的死锁问题:deadlock
3、本地方法栈(Native Method Stacks)(线程私有的)
本地方法:指那些不是由 java 代码编写的方法。
java 代码不能直接与操作系统的底层打交道,而是需要通过一些 c++语言所写的 API 来与底层进行交互。
本地方法栈:给本地方法的运行提供空间
4、堆(Heap)
4.1 定义:通过 new 关键字,创建的对象都会使用堆内存。
4.2 特点:
它是线程共享的,堆中对象都需要考虑线程安全问题
堆内没有被引用的对象都会被垃圾回收机制回收
4.3 堆内存溢出
不断产生新的对象,且对象都被引用了,无法触发垃圾回收机制,长时间就会导致堆内存溢出。
-Xmx8m 设置堆内存大小
package pys;
import java.util.ArrayList;
import java.util.List;
public class Demo_1 {
public static void main(String[] args) {
int i = 0;
try{
List<String> list = new ArrayList<>();
String a = "hello";
while(true){
list.add(a); //hello,hellohello,hellohellohellohello...
a = a + a; //hellohellohellohello...
i++;
//在程序走到 catch 之前,list 对象都有被引用,不能被垃圾回收机制回收
//同理,hello 这些字符串对象都被加入到 list 中,所以也被引用了,无法被回收。
}
}catch (Throwable e){
e.printStackTrace(); //java.lang.OutOfMemoryError: Java heap space:堆内存溢出
System.out.println(i); //25 //更改堆最大内存后,为 17
}
}
}
4.4 堆内存诊断
1.jps 工具 查看当前系统中有哪些 java 进程
2.jmap 工具
查看堆内存占用情况:只能查看某一时刻的堆内存占用情况
使用方式:jamp -heap 进程 id
3.jconsole 工具
图形界面的,多功能的监测工具,可以连续监测
命令行讲解:
>jps 【jps 命令,查看当前系统中的 java 进程】
5920
7988 Test06
8212 Jps
3240 Launcher
9448 KotlinCompileDaemon
jmap -heap 7988 【jmap 命令:查看当前 7988 进程的堆内存占用情况,是该时刻的】
Attaching to process ID 7988, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration: 【堆内存配置】
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2107637760 (2010.0MB) 【最大堆内存空间】
NewSize = 44040192 (42.0MB)
MaxNewSize = 702545920 (670.0MB)
OldSize = 88080384 (84.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage: 【第一个时间节点的堆内存使用情况,此时 byte 数组还未被创建】
PS Young Generation
Eden Space:
capacity = 33554432 (32.0MB) 【总容量】
used = 4705344 (4.48736572265625MB) 【已使用容量】
free = 28849088 (27.51263427734375MB)
14.023017883300781% used
From Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
PS Old Generation
capacity = 88080384 (84.0MB)
used = 0 (0.0MB)
free = 88080384 (84.0MB)
0.0% used
3179 interned Strings occupying 260568 bytes.
jmap -heap 7988
Attaching to process ID 7988, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2107637760 (2010.0MB)
NewSize = 44040192 (42.0MB)
MaxNewSize = 702545920 (670.0MB)
OldSize = 88080384 (84.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage: 【第二个时间节点的堆内存使用情况,此时 byte 数组已被创建】
PS Young Generation
Eden Space:
capacity = 33554432 (32.0MB)
used = 15191120 (14.487380981445312MB) 【已使用容量增加了 10mb】
free = 18363312 (17.512619018554688MB)
45.2730655670166% used
From Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
PS Old Generation
capacity = 88080384 (84.0MB)
used = 0 (0.0MB)
free = 88080384 (84.0MB)
0.0% used
3180 interned Strings occupying 260616 bytes.
D:\JAVA\JVM\codeTest>jmap -heap 7988
Attaching to process ID 7988, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2107637760 (2010.0MB) NewSize = 44040192 (42.0MB)
MaxNewSize = 702545920 (670.0MB)
OldSize = 88080384 (84.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage: 【第三个时间节点的堆内存使用情况,此时 byte 数组已被垃圾回收机制回收】
PS Young Generation
Eden Space:
capacity = 33554432 (32.0MB)
used = 671112 (0.6400222778320312MB)
free = 32883320 (31.35997772216797MB)
2.0000696182250977% used
From Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
PS Old Generation
capacity = 88080384 (84.0MB)
used = 1009328 (0.9625701904296875MB)
free = 87071056 (83.03742980957031MB)
1.1459168933686756% used
3166 interned Strings occupying 259632 bytes.
jconsole:图形界面
案例: 垃圾回收后,内存占用仍然很高
排查步骤:
step1:先使用 jps 命令查看当前正在运行的进程
step2:使用 jmap -heap 进程 id 来查看当前进程的堆内存使用情况
关注新生代内存占用:Eden Space
关注老年代内存占用:PS Old Generation
step3:使用 jconsole 工具连接到当前的进程上,在内存选项卡中,点击执行(GC(G))按钮
step4:再次使用 jmap 进行查看
介绍一种新工具:jvisualvm 可视化的虚拟机