JVM(1):JVM的介绍
一、 什么是JVM
定义
Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境)
好处
- 一次编写,到处运行
- 自动内存管理,垃圾回收机制
- 数组下标越界检查
比较
JVM JRE JDK的区别
学习 JVM 有什么用?
面试必备
中高级程序员必备
想走的长远,就需要懂原理,比如:自动装箱、自动拆箱是怎么实现的,反射是怎么实现的,垃圾回收机制是怎么回事等待,JVM 是必须掌握的。
常见的 JVM
我们主要学习的是 HotSpot 版本的虚拟机。
JVM整体架构
二、内存结构
1、程序计数器
Program Counter Register 程序计数器(寄存器)
1.1 作用
- 解释器会解释指令为机器码交给 cpu 执行,程序计数器会记录下一条指令的地址行号,这样下一次解释器会从程序计数器拿到指令然后进行解释执行。
- 多线程的环境下,如果两个线程发生了上下文切换,那么程序计数器会记录线程下一行指令的地址行号,以便于接着往下执行
1.2 特点
- 线程私有
1、CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码。
2、程序计数器是每个线程所私有的,当另一个线程的时间片用完,又返回来执行当前线程的代码时,通过程序计数器可以知道应该执行哪一句指令。 - 不会存在内存溢出
2、虚拟机栈
2.1 定义
- 每个线程运行需要的内存空间,称为虚拟机栈
- 每个栈由多个栈帧组成,对应着每次调用方法时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的方法
代码演示
package com.ali.jvm;
public class Test {
public static void main(String[] args) {
method1();
}
private static void method1() {
method2(1, 2);
}
private static int method2(int a, int b) {
int c = a + b;
return c;
}
}
在控制台中可以看到,主类中的方法在进入虚拟机栈的时候,符合栈的特点,每一个方法都是一个栈帧,最先进入的方法是main方法,所以main方法对应的栈帧压在最下面,最后进的方法是method2,所以method2对应的栈帧在最上面
2.2 问题辨析
-
垃圾回收是否涉及栈内存?
不需要。因为虚拟机栈中是由一个个栈帧组成的,在方法执行完毕后,对应的栈帧就会被弹出栈。所以无需通过垃圾回收机制去回收内存。
-
栈内存的分配越大越好吗?
不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。
-
方法内的局部变量是否是线程安全的?
如果方法内局部变量没有逃离方法的作用范围,则是线程安全的
如果如果局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全问题
2.3 栈内存溢出
Java.lang.stackOverflowError 栈内存溢出
发生原因
- 虚拟机栈中,
栈帧过多(无限递归) - 每个
栈帧所占用过大
代码演示1:
package cn.itcast.jvm.t1.stack;
/**
* 演示栈内存溢出 java.lang.StackOverflowError
* -Xss256k
*/
public class Demo1_2 {
private static int count;
public static void main(String[] args) {
try {
method1();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(count);
}
}
private static void method1() {
count++;
method1();
}
}
图上所示就是无限递归,当调用了一万八千多次之后,出现栈内存溢出
代码演示二:
package cn.itcast.jvm.t1.stack;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.List;
/**
* json 数据转换
*/
public class Demo1_19 {
public static void main(String[] args) throws JsonProcessingException {
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));
// { name: 'Market', emps: [{ name:'zhang', dept:{ name:'', emps: [ {}]} },] }
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(d));
}
}
class Emp {
private String name;
// @JsonIgnore
private Dept dept;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
class Dept {
private String name;
private List emps;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getEmps() {
return emps;
}
public void setEmps(List emps) {
this.emps = emps;
}
}
2.4 线程运行诊断
案例一:CPU占用过高
解决方法:Linux 环境下运行某些程序的时候,可能导致 CPU 的占用过高,这时需要定位占用 CPU 过高的线程
1、top 命令,查看是哪个进程占用 CPU 过高
2、ps H -eo pid, tid(tid是线程id的意思), %cpu | grep 刚才通过 top 查到的进程号 通过 ps 命令进一步查看是哪个线程占用 CPU 过高
3、jstack 进程 id 通过查看进程中的线程的 nid ,刚才通过 ps 命令看到的 tid 来对比定位,注意 jstack 查找出的线程 id 是 16 进制的,需要转换。
代码演示:
package cn.itcast.jvm.t1.stack;
/**
* 演示 cpu 占用过高
*/
public class Demo1_16 {
public static void main(String[] args) {
new Thread(null, () -> {
System.out.println("1...");
while(true) {
}
}, "thread1").start();
new Thread(null, () -> {
System.out.println("2...");
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread2").start();
new Thread(null, () -> {
System.out.println("3...");
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread3").start();
}
}
先用javac命令编译Demo1_16.java文件,然后在项目根目录下执行nohup java cn.itcast.jvm.t1.stack.Demo1_16 &命令,再用top命令查看
再用ps H -eo pid,tid,%cpu | grep 24634命令,得到是线程24662的cpu占用率为98.9%
再用命令jstack 24634 得到如下图:
3、本地方法栈
一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法
例如:Object类中由很多这样的方法
4、堆
定义:通过new关键字创建的对象都会被放在堆内存
特点:
- 所有线程共享,堆内存中的对象都需要考虑线程安全问题
- 有垃圾回收机制
堆内存溢出
代码演示:
package cn.itcast.jvm.t1.heap;
import java.util.ArrayList;
import java.util.List;
/**
* 演示堆内存溢出 java.lang.OutOfMemoryError: Java heap space
* -Xmx8m
*/
public class Demo1_5 {
public static void main(String[] args) {
int i = 0;
try {
List list = new ArrayList<>();
String a = "hello";
while (true) {
list.add(a); // hello, hellohello, hellohellohellohello ...
a = a + a; // hellohellohellohello
i++;
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println(i);
}
}
}
堆内存诊断的几种工具
- jps工具:查看当前系统中由哪些java进程
- jmap工具:查看堆内存占用情况
- jconsole工具:图形界面的,多功能的检测工具,可以连续监测