JVM 内存结构 -- 什么是JVM学习JVM的好处学习路线(JVM的组成)程序计数器虚拟机栈(栈内存溢出线程运行诊断)
Posted CodeJiao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM 内存结构 -- 什么是JVM学习JVM的好处学习路线(JVM的组成)程序计数器虚拟机栈(栈内存溢出线程运行诊断)相关的知识,希望对你有一定的参考价值。
文章目录
- 1. JVM JDK 和 JRE
- 2. 学习JVM的好处
- 3. 学习路线
- 4. 程序计数器(用寄存器实现register)
- 5. 虚拟机栈(线程运行需要的内存空间)
- 6. 本地方法栈(调用本地方法:C / C++ 写的方法)
1. JVM JDK 和 JRE
1.1 JVM
Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
java
虚拟机(Java Virtual Machine
)- java
程序的运行环境(java
二进制字节码的运行环境)
好处:
- 一次编写,到处运行。
- 自动内存管理,垃圾回收功能。
1.2 .class字节码文件
在 Java 中,JVM 可以理解的代码就叫做 字节码 (即扩展名为 .class 的⽂件),它不⾯向任何特定的处理器,只⾯向虚拟机。Java 语⾔通过字节码的⽅式,在⼀定程度上解决了传统解释型语⾔执⾏效率低的问题,同时⼜保留了解释型语⾔可移植的特点。所以 Java 程序运⾏时比较⾼效,⽽且,由于字节码并不针对⼀种特定的机器,因此,Java 程序⽆须重新编译便可在多种不同操作系统的计算机上运⾏。
1.3 Java 程序从源代码到运行的步骤
HotSpot 是 Java虚拟机的版本。
HotSpot采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系
统资源的只有那一小部分的代码(热点代码),而这也就是JIT所需要编译的部分。JVM
会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,
它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time
Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的
开销。JDK支持分层编译和AOT协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
14 JDK 和 JRE
JDK(Java语言开发工具包):
Java SDK (Development Kit)包含:JRE的超集,包含编译器和调试器等用于程序开发的文件
JRE(Java运行环境):
Java Runtime Environment (JRE) 包含:Java虚拟机、库函数、运行Java应用程序和Applet所必须文件
JRE三项主要功能:
- 加载代码:由class loader 完成;
- 校验代码:由bytecode verifier 完成;
- 执行代码:由 runtime interpreter完成。
JDK和JRE的区别和联系:
- jdk是jre的超集,是在jre的基础上增加了编译器及其他一些开发工具。
- jre就是java运行时环境,包括了jvm和其它一些java核心api,任何一台电脑,只有安装了jre才可以运行java程序。
- 如果只是要运行JAVA程序,只需要JRE就可以。 JRE通常非常小,也包含了JVM.
- 如果要开发JAVA程序,就需要安装JDK。
小结:
- JDK是Java Development Kit(Java开发工具包),它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。
- JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。
- 如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。
2. 学习JVM的好处
- 面试。
- 理解底层的实现原理。
- 中高级程序员的必备技能。
3. 学习路线
4. 程序计数器(用寄存器实现register)
从上⾯的介绍中我们知道程序计数器主要有两个作⽤:
- 字节码解释器通过改变程序计数器来依次读取指令,从⽽实现代码的流程控制,如:顺序执⾏、选择、循环、异常处理。
- 在多线程的情况下,程序计数器⽤于记录当前线程执⾏的位置,从⽽当线程被切换回来的时候能够知道该线程上次运⾏到哪⼉了。
注意:程序计数器是唯⼀⼀个不会出现 OutOfMemoryError 的内存区域,它的⽣命周期随着线程的创建⽽创建,随着线程的结束⽽死亡。
5. 虚拟机栈(线程运行需要的内存空间)
5.1 说明介绍
Java Virtual Machine Stacks (Java 虚拟机栈)
- 每个线程运行时所需要的内存,称为虚拟机栈。
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存。(1个栈帧就是方法的一次调用)。
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
说明:
这里演示的情况是方法1调用了方法2,然后方法2调用了方法3。
扩展:那么⽅法 / 函数如何调⽤?
5.2 代码示例
示例代码:
/**
* 演示栈帧
*/
public class Demo1_1
public static void main(String[] args) throws InterruptedException
method1();
private static void method1()
method2(1, 2);
private static int method2(int a, int b)
int c = a + b;
return c;
debug运行结果:
栈帧里面还包括返回地址,比如method3
栈帧执行完后会回到method2
栈帧。
5.3 问题辨析
5.3.1 垃圾回收是否涉及栈内存?
不涉及,栈内存在每个方法调用完成之后会自动释放内存(自动弹出栈帧),根本不需要垃圾回收来管理栈内存。
5.3.2 栈内存分配越大越好吗?(一般采用默认的大小就行了)
栈内存并不是分配越大越好,假设分配的物理内存是100MB,每个线程栈大小是1MB,那么可以分配100个线程,但是如果提升了线程栈大小,那可以分配的对应线程数就变少了。
5.3.3 方法内的局部变量是否线程安全?
- 如果方法内局部变量没有逃离方法的作业范围,它是线程安全的。
- 如果是局部变量引用了对象,并逃离方法的作用范围(例如把引用对象
return
到外界、把引用对象作为参数传递进来),需要考虑线程安全。
5.4 栈内存溢出
5.4.1 原因分析
栈帧过多导致栈内存溢出(一般情况就是这个原因):
栈帧过大导致栈内存溢出(这种情况比较少)
5.4.2 代码示例:栈帧过多导致栈内存溢出
/**
* 演示栈内存溢出 java.lang.StackOverflowError
* -Xss128k
*/
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();
运行结果:
现在我们改一下栈内存的大小(128KB),再次运行测试:
5.5 线程运行诊断
5.5.1 案例1:cpu 占用过多
用top
定位哪个进程对cpu
的占用过高
使用ps H -eo pid,tid,%cpu | grep 进程id
(用ps
命令进一步定位是哪个线程引起的cpu
占用过高)
jstack 进程id
,然后可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号。
然后我们把上一步找到的32665
线程转换为十六进制(jstack
工具里面显示的线程号都是十六进制)。
5.5.2 案例2:程序运行很长时间没有结果
6. 本地方法栈(调用本地方法:C / C++ 写的方法)
比如下面这些方法就是本地方法(native
):
查看Java虚拟机类型:
java -version
以上是关于JVM 内存结构 -- 什么是JVM学习JVM的好处学习路线(JVM的组成)程序计数器虚拟机栈(栈内存溢出线程运行诊断)的主要内容,如果未能解决你的问题,请参考以下文章