JVM 是 Java 程序员中高级开发的必备技能之一,也是未来长久发展的核心!!!~

什么是JVM?

JVM 是Java虚拟机。他它是实现Java代码,一处编写,处处运行的基础。也是Java二进制代码的运行环境

JVM好处:

  • 提供了自动的内存管理机制:垃圾回收机制!
  • 一处编写,处处运行的基础
  • 数组下标越界的检查

说明:内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

JVM、JRE、JDK 关系

JVM 是一套规范,很多公司 都实现过JVM 我们常用的就是Open JDK、Oracle JDK,不同规范应用范围不同,同时受到的限制也不同!我们常用就是 OpenJDK

JVM 组成

图1
图2

按顺序学习 由浅入深的学习 顺序是: JVM 内存结构 、 垃圾回收 、 Java Class编译优化 、 类加载器

JVM的内存划分

顺序:

  • 程序计数器
  • 虚拟机栈

程序计数器 Program Counter Register

  • 作用:是记录下一条 JVM 指令的执行地址行号。(由物理的寄存器实现的)

特点:

  • 是线程私有的(属于自己的线程的)
  • 不会存在内存溢出
  • 每个线程,都有自己的程序计数器

内存溢:Out of Memory 是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。

A : B                   C       // D
0 : getstatic 		#20	// PrintStream out = System.out; 
3 : astore_1			// -- 
4 : aload_1 			// out.println(1); 
5 : iconst_1 		  	// -- 
6 : invokevirtual 	#26  	// -- 
9 : aload_1 			// out.println(2); 
10: iconst_2          		// -- 
11: invokevirtual 	#26 	// -- 
14: aload_1 		  	// out.println(3); 
15: iconst_3          		// -- 
16: invokevirtual 	#26 	// -- 
19: aload_1           		// out.println(4); 
20: iconst_4          		// -- 
21: invokevirtual 	#26 	// -- 
24: aload_1           		// out.println(5); 
25: iconst_5          		// -- 
26: invokevirtual 	#26 	// -- 
29: return

说明 每一列是一串二进制字节码 由 A B C D 四部分组成

A 列:就是 程序计数器 记录的数字

虚拟机栈 (栈帧:先进后出)

  • 每个线程运行需要的内存空间,称为虚拟机栈
  • 每个栈由多个栈帧(Frame)组成,对应着每次调用方法时所占用的内存
  • 每个线程只能有一个活动栈帧(顶部的栈帧就是活动栈帧 如下图 栈帧4 就是活动栈帧),对应着当前正在执行的方法

栈帧包含的内容:

栈问题辨析:

1)垃圾回收是否涉及栈内存?

  • 不会。栈内存是方法调用产生的,方法调用结束后会弹出栈。

2)栈内存分配越大越好吗?(就是启动命令加上参数 -Xss就是设置的栈大小)例如 -Xss1m、-Xss1024k 、-Xss1048576

  • 不一定。因为计算机物理内存是一定的,线程占用物理内存的 ,线程中栈内存越大,虽然可以支持更多的递归调用,但是受到物理限制,(可执行的)线程数就会越少。 比如物理内存200M 每个栈占用 5M 那么最多创建40个线程执行。栈小一点 就可以创建更多的线程!

3)方法呢的局部变量是否线程安全

  • 如果方法内部的变量没有逃离方法的作用访问,它是线程安全的
  • 如果是局部变量引用了对象,并逃离了方法的访问,那就要考虑线程安全问题。

总结一句:栈帧内部自己的内存不用别人的,也不让别人用。就是线程安全的

4)栈内存溢出 (推荐使用 -Xss256k 指定栈内存大小!)

递归调用导致栈帧过多,栈帧过大、或者第三方类库操作,都有可能造成栈内存溢出 java.lang.stackOverflowError ,推荐使用 -Xss256k 指定栈内存大小!

线程运行诊断

案例一:代码运行 CPU 占用过多
解决方法:Linux 环境下运行某些程序的时候,可能导致 CPU 的占用过高,这时需要定位占用 CPU 过高的线程

步骤:

用top命令得到进程(process ID)pid,再用ps命令得到进程pid中有问题的线程(thread ID)tid 最后用jstack 拿到tid 转为16进制 对比 nid

  1. top 命令,查看是哪个进程占用 CPU 过高
  2. ps H -eo pid, tid(线程id), %cpu | grep :刚才通过 top 查到的进程号 通过 ps 命令进一步查看是哪个线程占用 CPU 过高 得到tid
  3. jstack 线程 id :通过对比线程的 tid ,定位出错代码的行数,通过 ps 命令看到的 nid 来对比定位,注意 jstack 查找出的线程 id 是 16 进制的,需要我们拿到pid 转换为16禁止 对比 nid。

本地方法栈

一些带有 native 的关键字修饰方法就是。需要 JAVA 去调用本地的C或者C++方法,因为 JAVA 有时候没法直接和操作系统底层交互,所以需要用到本地方法栈,服务于带 native 关键字的方法。

此图就是Object 的 clone方法 他是native修饰的 说明实现是有底层调用本地C 或者C++来实现的

堆 Heap

  • 通过new关键字创建的对象都会被放在堆内存

特点

  • 它是线程共享,堆内存中的对象都需要考虑线程安全问题
  • 有垃圾回收机制

2)堆内存溢出

java.lang.OutofMemoryError :Java heap space. 堆内存溢出
可以使用 -Xmx8m 来指定堆内存大小

堆内存诊断分析

代码

public class Jvm {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("in 1");
        Thread.sleep(30000);


        byte[] bytes = new byte[1024 * 1024 * 10];
        System.out.println("in 2");
        Thread.sleep(30000);

        bytes = null;
        System.gc();
        System.out.println("in 3");
        Thread.sleep(1000000l);

    }
}

方式一:

1、查看当前系统中有哪些 java 进程

jps


2、查看堆内存占用情况 jmap – heap 进程id

jmap -heap 进程id

Eden区 就是我们每次new出来 在堆中使用的内存区域

方式二:JDK 提供的 jconsole.exe

常量池(constant pool)

指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

B站资源:https://www.bilibili.com/video/BV1yE411Z7AP