浅析Jvm
浅析Jvm
基本概念
引言
Java 虚拟机(JVM,Java Virtual Machine)是 Java 生态系统的核心组成部分,它为 Java 应用程序提供了一个运行环境。JVM 的主要职责是将 Java 字节码(Bytecode)转换为机器码,并执行这些机器码,从而实现 Java 的“写一次,运行到处”的跨平台特性。
JVM 的基本架构
JVM 的架构可以分为五个主要部分:类加载子系统(Class Loader Subsystem)、运行时数据区(Runtime Data Area)、执行引擎(Execution Engine)、本地接口(Native Interface)和垃圾回收(Garbage Collection)。
1. 类加载子系统(Class Loader Subsystem)
类加载子系统负责将 Java 类从文件系统或网络中加载到 JVM 中。类加载过程包括以下三个步骤:
- 加载:找到并加载类的二进制数据。
- 链接:验证、准备和解析类。
- 验证:确保类文件的字节码符合 JVM 规范。
- 准备:为类的静态变量分配内存,并将其初始化为默认值。
- 解析:将类的符号引用转换为直接引用。
- 初始化:执行类的静态初始化块和静态变量的赋值操作。
2. 运行时数据区(Runtime Data Area)
运行时数据区是 JVM 在执行 Java 程序时使用的内存区域,它可以进一步细分为以下几个区域:
- 方法区(Method Area):存储已加载类的元数据、常量池、静态变量和JIT编译后的代码。从 Java 8 开始,方法区被移到本地内存中的元空间(Metaspace)中。
- 堆(Heap):所有对象实例和数组在这里分配内存。堆是垃圾回收(GC)的主要区域。
- 栈(Stack):每个线程都有自己的栈,存储局部变量、操作数栈和帧数据。栈中的数据是线程私有的。
- 程序计数器(Program Counter Register):一个小的内存区域,保存当前线程所执行的字节码指令的地址。
- 本地方法栈(Native Method Stack):为本地方法(由Native关键字修饰的方法)执行提供栈空间。
3. 执行引擎(Execution Engine)
执行引擎负责执行字节码。它包括以下几个部分:
- 解释器(Interpreter):将字节码逐条解释执行。解释执行速度较慢,但启动快。
- 即时编译器(Just-In-Time Compiler, JIT):将热点代码编译为机器码,以提高执行速度。编译后的代码直接在CPU上运行。
- 垃圾回收器(Garbage Collector):自动管理内存,通过标记和清除、复制和压缩等算法回收不再使用的对象。
4. 本地接口(Native Interface)
本地接口允许 JVM 调用本地库(如 C 或 C++ 编写的库)。通过 JNI(Java Native Interface),Java 程序可以与本地代码进行交互。
垃圾回收(Garbage Collection)
垃圾回收是 JVM 的一个重要特性,用于自动管理内存。JVM 的垃圾回收器会定期扫描堆中的对象,回收不再使用的对象内存。
所谓垃圾回收机制(Garbage Collection, 简称GC),指自动管理动态分配的内存空间的机制,自动回收不再使用的内存,不定时去堆内存中清理不可达对象,以避免内存泄漏和内存溢出的问题。最早是在1960年代提出的。
垃圾回收是 java相较于c、c++语言的优势之一。其他编程语言,如C#、Python和Ruby等,也都提供了垃圾回收机制。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,程序员唯一能做的就是通过调用System.gc 方法来建议执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。
这也是垃圾收集器的最主要的缺点。
常见的垃圾回收算法有:
- 标记-清除算法(Mark-Sweep):标记活动对象,然后清除未标记对象。
- 标记-复制算法(Mark-Compact):标记活动对象,然后将其复制到新空间,压缩内存。
- 分代收集算法(Generational GC):将堆分为年轻代(Young Generation)和老年代(Old Generation),分别使用不同的回收算法优化性能。
一次完整的垃圾回收过程是什么样的?
Jvm 垃圾回收的基本过程可以分为以下三个步骤:
- 垃圾分类:首先我们的 jvm 在进行垃圾回收的过程,需要确定哪些对象是垃圾对象,哪些对象是存活对象。这个类似于我们在做一件事之前的规划。具体的分类方法一般情况下,垃圾回收器会从堆的根节点(如程序计数器、虚拟机栈、本地方法栈和方法区中的类静态属性等),也就是 gc root。开始遍历对象图,标记所有可以到达的对象为存活对象,未被标记的对象则被认为是垃圾对象。进过标记后,分类成功。
- 垃圾查找:分类后,已经知道了对象所处的一个状态,jvm 会根据分类后对象,先找出所有垃圾对象,以便进行清理。不同的垃圾收集,其中的查找方式会产生相应的差异,随着现在 jdk 的 升级与发展,还会产生更加高效的算法,后面会有垃圾收集的算法详细介绍。
- 垃圾清除:标记完成后,进行最后的清理与删除。这里涉及不同的垃圾收集器,清理的方式也不同,常见的有:标记清除法、复制算法等;需要注意的是,垃圾清理可能会引起应用程序的暂停,不同的垃圾回收器通过不同的方式来减少这种暂停时间,从而提高应用程序的性能和可靠性。这也是垃圾收集器不断发展的一个重要命题。
如何判断对象是否可以被回收?
引用计数法
引用计数法(Reference Counting)是一种内存管理技术,用于跟踪对象的引用数量。每个对象都有一个引用计数器,记录着指向该对象的引用数量。
当一个对象被引用时,引用计数器加一;当一个引用被释放时,引用计数器减一。当引用计数器为零时,表示没有任何引用指向该对象,该对象可以被释放,回收其占用的内存。
可达性分析算法
可达性分析算法是JVM垃圾回收中的一种算法,它通过分析对象的引用关系,判断对象是否可达,从而决定对象是否可以被回收。
工作原理
- GC Roots:在Java中,GC Roots通常包括虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区(静态变量)中引用的对象、本地方法栈中JNI(Native方法)引用的对象等。
- 搜索过程:可达性分析算法从GC Roots开始,递归地访问所有可达的对象,并给它们打上标记。这个过程可以使用深度优先搜索(DFS)或广度优先搜索(BFS)等图遍历算法来实现。
- 回收判定:如果一个对象到GC Roots没有任何引用链相连(即该对象从GC Roots不可达),则证明该对象是不可用的,可以判定为可回收对象。
大概流程
我用下面的代码作为学习的例子
package cmk.study.jvm;
public class jvmStudy {
static int add(){
int a = 1;
int b = 2;
int c = (a + b)*10;
return c;
}
public static void main(String[] args) throws InterruptedException {
jvmStudy jvmStudy = new jvmStudy();
int add1 = jvmStudy.add();
System.out.println(add1);
}
}
我们反编译这个class类 javap -c jvmStudy.class > jvmStudy.txt
// 编译自 "jvmStudy.java"
public class cmk.study.jvm.jvmStudy {
// 默认构造方法
public cmk.study.jvm.jvmStudy();
Code:
0: aload_0 // 将this引用推送到操作数栈顶
1: invokespecial #1 // 调用父类java/lang/Object的构造方法
4: return // 从构造方法返回
// 静态方法 add,返回一个 int
static int add();
Code:
0: iconst_1 // 将常量1推送到操作数栈顶
1: istore_0 // 将栈顶的1存储到局部变量0
2: iconst_2 // 将常量2推送到操作数栈顶
3: istore_1 // 将栈顶的2存储到局部变量1
4: iload_0 // 将局部变量0的值(即1)加载到操作数栈顶
5: iload_1 // 将局部变量1的值(即2)加载到操作数栈顶
6: iadd // 将栈顶两值相加(1 + 2),结果3推送到操作数栈顶
7: bipush 10 // 将常量10推送到操作数栈顶
9: imul // 将栈顶两值相乘(3 * 10),结果30推送到操作数栈顶
10: istore_2 // 将栈顶的30存储到局部变量2
11: iload_2 // 将局部变量2的值(即30)加载到操作数栈顶
12: ireturn // 返回栈顶的int值,即30
// 主方法
public static void main(java.lang.String[]);
Code:
0: new #7 // class cmk/study/jvm/jvmStudy
// 创建一个新的jvmStudy对象
3: dup // 复制栈顶的对象引用
4: invokespecial #9 // Method "<init>":()V
// 调用构造方法初始化新创建的jvmStudy对象
7: astore_1 // 将对象引用存储到局部变量1
8: aload_1 // 加载局部变量1的对象引用到栈顶
9: pop // 弹出栈顶的对象引用,实际上这里是无用操作
10: invokestatic #10 // Method add:()I
// 调用静态方法add并将返回值推送到操作数栈顶
13: istore_2 // 将add方法的返回值(即30)存储到局部变量2
14: getstatic #14 // Field java/lang/System.out:Ljava/io/PrintStream;
// 获取System.out静态字段(标准输出流)
17: iload_2 // 将局部变量2的值(即30)加载到操作数栈顶
18: invokevirtual #20 // Method java/io/PrintStream.println:(I)V
// 调用PrintStream的println方法,打印int值
21: return // 从main方法返回
}
反编译结果可以清楚的看到程序的执行逻辑,可以与上图做对应。
浅析Jvm的更多相关文章
- 浅析JVM内存区域及垃圾回收
一.JVM简介 JVM,全称Java Virtual Machine,即Java虚拟机.以Java作为编程语言所编写的应用程序都是运行在JVM上的.JVM是一种用于计算设备的规范,它是一个虚构出来的计 ...
- 浅析JVM内存结构和6大区域(转)举例非常好
内存作为系统中重要的资源,对于系统稳定运行和高效运行起到了关键的作用,Java和C之类的语言不同,不需要开发人员来分配内存和回收内存,而是由JVM来管理对象内存的分配以及对象内存的回收(又称为垃圾回收 ...
- 浅析JVM中的GC日志
目录 一.GC日志的格式分析 二.运行时开启GC日志 一.GC日志的格式分析 在讲述GC日志之前,我们先来运行下面这段代码 package com.example; public class Test ...
- 浅析JVM内存结构和6大区域(转)
其实对于我们一般理解的计算机内存,它算是CPU与计算机打交道最频繁的区域,所有数据都是先经过硬盘至内存,然后由CPU再从内存中获取数据进行处理,又将数据保存到内存,通过分页或分片技术将内存中的数据再f ...
- 程序员从宏观、微观角度浅析JVM虚拟机!
1.问题 1.JAVA文本文件如何被翻译成CLASS二进制文件? 2.如何理解CLASS文件的组成结构? 3.虚拟机如何加载使用类文件的生命周期? 4.虚拟机系列诊断工具如何使用? 5.虚拟机内存淘汰 ...
- Java编程技术之浅析JVM内存
JVM JVM->Java Virtual Machine:Java虚拟机,是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的. 基本认知: ...
- JVM虚拟机—JVM内存
JVM在运行时将数据划分为了5个区域来存储,这5个区域图示如下: 其中方法区和堆对是所有线程共享的内存区域:而java栈.本地方法栈和程序员计数器是运行时线程私有的内存区域. 首先我们熟悉一下一个 J ...
- Android性能调优篇之探索JVM内存分配
开篇废话 今天我们一起来学习JVM的内存分配,主要目的是为我们Android内存优化打下基础. 一直在想以什么样的方式来呈现这个知识点才能让我们易于理解,最终决定使用方法为:图解+源代码分析. 欢迎访 ...
- Java 10大精华文章收集001
Java语言与JVM中的Lambda表达式全解 Lambda表达式是自Java SE 5引入泛型以来最重大的Java语言新特性,本文是2012年度最后一期Java Magazine中的一篇文章,它介绍 ...
- Android内存优化1 了解java内存分配 1
开篇废话 今天我们一起来学习JVM的内存分配,主要目的是为我们Android内存优化打下基础. 一直在想以什么样的方式来呈现这个知识点才能让我们易于理解,最终决定使用方法为:图解+源代码分析. 欢迎访 ...
随机推荐
- 绝对要收藏!!! JavaEE开发常用注解
目录 前言 1.Mybatis常用注解 2.SpringMVC常用注解 3.Spring常用注解 1. IoC注解 2. DI注解 3. 事务注解 4.SpringBoot常用注解 5.Lombok注 ...
- 虚拟硬盘系统 —— Windows系统 磁盘加速软件 —— 优缺点以及与真实物理磁盘访问文件的区别
在家里的局域网搞了一个NAS,但是由于磁盘读存速率问题导致远程copy的速度只有15MB/s,而如果NAS中的文件已在内存中有缓存则远程copy的速度为50MB/s. 于是考虑利用内存建立虚拟硬盘: ...
- 《Python数据可视化之matplotlib实践》 源码 第四篇 扩展 第十章
图 10.1 import matplotlib.pyplot as plt import numpy as np plt.axes([0.1, 0.7, 0.3, 0.3], frameon=Tru ...
- batch normalization的multi-GPU版本该怎么实现? 【Tensorflow 分布式PS/Worker模式下异步更新的情况】
最近由于实验室有了个AI计算平台,于是研究了些分布式和单机多GPU的深度学习代码,于是遇到了下面的讨论: https://www.zhihu.com/question/59321480/answer/ ...
- Ubuntu系统anaconda报错version `GLIBCXX_3.4.30' not found
参考文章: https://blog.csdn.net/zhu_charles/article/details/75914060 =================================== ...
- 多线程之深入理解park与unpark
1.背景 面试官问,如何暂停一个线程勒..... 说说你对park的理解....... 2.代码 package com.ldp.demo01; import com.common.MyThreadU ...
- B站基于Apache DolphinScheduler的一站式大数据集群管理平台(BMR)初窥
一.背景 大数据服务是数据平台建设的基座,随着B站业务的快速发展,其大数据的规模和复杂度也突飞猛进,技术的追求也同样不会有止境. B站一站式大数据集群管理平台(BMR),在千呼万唤中孕育而生.本文简单 ...
- Digest Auth 摘要认证
1.该代码展示了使用Apache HttpClient库进行HTTP请求,并处理基于MD5的HTTP Digest认证的过程. Digests类实现了MD5加密算法,HttpUtils类处理了GET. ...
- 面试题:写一个遍历ArrayList的时候,删除一个元素的例子?并说说原理。
代码实现 方法一:for循环 public static void main(String[] args) { ArrayList<String> list = new ArrayList ...
- 开关中断与cpsid/cpsie指令
在汇编代码中,CPSID CPSIE 用于快速的开关中断. I:IRQ中断; F:FIQ中断最常见的这两个命令的使用处是在关中断.开中断的实现中,我们经常用的local_irq_enabl ...