尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JVM入门第一课:内存模型、编译器与调优的整体认知

JVM入门第一课:内存模型、编译器与调优的整体认知 JVMJava Virtual MachineJava虚拟机是所有Java程序员终究绕不开的一个话题。我见过不少写了三年代码的人平时写Spring、写业务逻辑头头是道但一提到jvm学习就头疼什么堆、栈、方法区、垃圾回收、jvm调优听到就头皮发麻。这篇博文是JVM学习系列的第一节也就是“引入课”我不会一上来就甩给你八股文式的内存模型图而是先帮你把整体框架搭起来。如果你是一名刚开始接触Java的初学者或者写过一两年业务代码、却从来没系统梳理过JVM相关概念的人这篇文章就是为你写的。jvm内存模型到底是什么、jre和jvm之间的关系怎么区分、jvm编译器有几种、jvm调优从哪里入手——这些热词背后的知识在这一课里我们会有一个初步但清晰的认识。目标只有一个让你在脑子里建立一张完整的JVM地图知道这台“虚拟机”大概由哪些部件组成、各自干什么后续再一点点深入拆解。这节内容你完全可以跟着操作一遍全程不需要IDE几个命令行就能让JVM“动”起来。1. 引入先别急着背八股文JVM第一课到底学什么1.1 为什么第一节不讲内存模型先讲“建立认知”很多JVM教程的开头就是一张复杂的运行时数据区图堆、栈、方法区、程序计数器全部铺开新手看了直接劝退。我早期学JVM就吃过这个亏翻开书第一章就是内存区域详解每个区域什么参数控制、会抛什么异常全得硬背结果一个月后全忘了。后来才明白学习这类底层知识顺序比内容更重要。第一课的核心不是“记住所有概念”而是“知道这门课要解决什么问题”。就像你去学做菜第一步不是背调料配比而是先弄明白厨房里有哪几个工作区域、灶台和烤箱各有什么用、整道菜从洗切到下锅的流程大致是什么。JVM学习也是一样先建立一张宏观地图知道自己以后要学堆、学垃圾回收、学类加载器、学调优工具然后再逐个深入到细节里。所以这一节我只会做三件事理清最基础的概念边界JVM/JRE/JDK、跑通一个最简单的程序并观察JVM的运作、对jvm内存模型做一次“远观”而不是“近视”。这三件事做完你会有一种“原来JVM就这么回事”的感觉后面再深入就不会迷失方向。1.2 学习JVM的整体路线先看地图再走迷宫既然这一系列是循序渐进地学习我必须告诉你后续的路是怎么规划的否则你很难判断现在自己站在哪里。我建议的JVM学习路线分五步第一步是概念层搞清楚JVM、JRE、JDK的关系明白Java程序从源码到运行到底经历了什么。第二步是运行时数据区也就是大家常说的jvm内存模型堆、虚拟机栈、方法区元空间等区域各自的职责、参数和溢出现象。第三步是类加载机制与字节码技术了解一个.class文件怎么被加载进内存、编译器和解释器如何协同工作。第四步是垃圾回收这是JVM学习里最硬核的部分之一从判断对象存活到各种回收算法再到分代回收。第五步才是jvm调优实战学会用命令行工具、分析日志、调整参数。这个顺序是有讲究的内存模型是静态视角类加载是动态视角垃圾回收是运行机制调优是应用层操作。前面的概念不牢后面调优就是空中楼阁。你不需要一下子记住这五条只要有个印象第一节课站在概念层后面我们一层一层往下钻。1.3 打消一个常见误解JVM调优不是玄学每次聊到jvm学习总会有人用“调优”这个词吓唬新手好像没调过几台服务器就不配说自己了解JVM。实际上jvm调优的前置条件只有两个一是你会用工具观测JVM运行状态二是你理解各内存区域和垃圾回收机制的基本原理。这一课我们先把第一条腿迈出去——从命令行工具开始后面学到参数的时候你就能自然对接。2. 三分钟理清JVM、JRE、JDK的关系2.1 JVM到底是什么它解决了什么麻烦JVM全称是Java Virtual Machine也就是Java虚拟机。你可以把它理解成一个“翻译官”Java编译器javac把.java源码编译成一种叫作字节码bytecode的中间格式这种格式不针对任何具体的操作系统JVM再把字节码逐条翻译成当前系统能理解的机器指令来执行。核心价值就一句话一次编写到处运行。为什么需要这样一层中间翻译直接编译成Windows上的exe和Linux上的elf不行吗理论上可以但那样同一个程序就要针对每个平台分别编译一套成本高且维护困难。JVM相当于在软件和操作系统之间垫了一层“通用底盘”应用只面向JVM编程JVM负责适配不同操作系统。这也是Java当年能火起来的重要原因。我记得第一次跟新人解释这个概念时用的比喻是字节码像普通话的发言稿JVM是各地的翻译员Windows翻译员把稿子翻成Windows能懂的方言Linux翻译员翻成Linux能懂的方言。写稿的人只需要写普通话翻译的事交给各地的翻译员。这个类比虽然不完全严谨但用来入门足够了。2.2 JRE和JVM之间的关系终于不用再混了这是学习JVM的第一个高频混淆点。很多教程里JRE、JDK、JVM三个词交替出现很容易把人绕晕。其实三者是包含关系JVM是最内核的虚拟机JREJava Runtime EnvironmentJava运行时环境包含JVM再加上Java运行时需要的核心类库比如我们用的java.lang、java.util这些包就是给JVM提供“原料”的JDKJava Development KitJava开发工具包又包含了JRE并在这个基础上额外提供了开发工具如javac编译器、javap反编译工具、jar打包工具等。可以这样记忆JVM是发动机JRE是发动机加满油的整车JDK则是在整车基础上又加了一个工具箱。普通用户运行Java程序只需要安装JRE但开发者要写代码、编译程序就必须装JDK。有些入门教材说“安装JDK后自带JRE”这在JDK 8及以前很明确从JDK 9开始官方调整了目录结构和部分组件划分但“JDK包含JRE包含JVM”这个逻辑关系的本质没有变。2.3 编译器究竟有几种把javac、解释器和JIT编译器说清楚热搜词里的“java jvm编译器有几种”也很有意思很多人在面试时被问到过。这里需要区分两个层面面向开发者的编译器和JVM内部的编译器。前者就是javac它把.java源码编译成.class字节码文件这一步不生成机器码而是生成JVM能识别的中间指令后者是JVM运行时的“翻译机构”负责把字节码翻译成机器码包括解释器和JIT编译器。解释器是一句一句翻译字节码优点是不需要等待编译启动快但逐句翻译执行效率偏低。JIT编译器Just-In-Time即时编译器则会把频繁执行的“热点代码”整段编译成机器码之后直接复用机器码执行速度更快。你看热点架构下的JVM通常会先靠解释器启动等到某段代码被多次调用后JIT就把这段代码优化编译成本地机器码相当于“常用句子直接背下来不必每次现翻”。如果你看相关技术文档还会看到C1编译器客户端编译器和C2编译器服务端编译器的说法它们一个偏向启动速度和编译开销一个偏向极致优化部分场景还会引入Graal编译器。这些细节这个阶段不用记只需要记住“javac负责生成字节码JVM内部通过解释器加JIT编译器把字节码转成机器码”这个主干就够了。3. 让JVM跑起来从安装JDK到手动编译一个Hello类3.1 环境准备JDK安装与环境变量配置理论说得再多都不如让JVM实际跑一次。这一节我从零开始带你搭建环境尽量不使用IDE因为IDE会帮你隐藏太多底层细节不利于观察JVM的工作过程。第一步是下载JDK。去Oracle官网或者OpenJDK镜像站下载适合你操作系统的JDK即可这里我用JDK 8或者JDK 11之后的版本都行命令是通用的。下载完成后按提示安装Windows直接双击exeLinux则可以解压到某个目录比如/usr/local/jdk。安装完成后最关键的一步是配置JAVA_HOME环境变量和PATH。以Linux/macOS为例在~/.bashrc或~/.zshrc中加入export JAVA_HOME/usr/local/jdk export PATH$JAVA_HOME/bin:$PATH配置好之后在终端执行以下命令验证java -version javac -version如果能看到版本信息说明JDK安装成功。这一步的坑很多新手都踩过但别急我后面专门用一节来写如何排查。现在假设你已经装好了我们继续往下走。3.2 手动编译运行一个最简单的Java程序在一个空目录下创建文件HelloJava.java内容如下public class HelloJava { public static void main(String[] args) { System.out.println(Hello, JVM! 我是第一课。); } }保存后在终端进入这个目录先执行编译命令javac HelloJava.java运行后你会发现这个目录多了一个HelloJava.class文件。这个.class文件就是字节码它和.java源码在同一目录但是机器并不直接执行它。紧接着执行java HelloJava注意这里不能写成java HelloJava.class只需要类名。运行后终端会输出那一行文字到此你就完成了“源码→字节码→JVM执行”的完整链路。整个过程虽然简单却是理解JVM的活教材javac生成了字节码java进程启动了JVMJVM负责加载这个类并执行main方法。3.3 用javap看一下字节码长什么样光知道有字节码还不够既然学JVM至少得瞅一眼字节码的“长相”。JDK自带了一个反编译工具javap我们可以用它查看HelloJava.class的内容javap -c HelloJava.class输出结果里面会出现类似这样的内容public static void main(java.lang.String[]); Code: 0: getstatic #2 3: ldc #3 5: invokevirtual #4 8: returngetstatic是获取某个静态字段ldc是把常量池里的字符串推入栈顶invokevirtual是调用实例方法。你不需要逐条理解这些指令只需要明白字节码是给JVM看的“汇编语言”它有自己的一套指令集。后面学习JVM字节码技术时这些指令会一个个被拆解现在先有个印象即可。3.4 调优工具初体验jps、jinfo、jstat入门观察我们前面提过以后要谈jvm调优就一定离不开观测工具。JDK里自带了一堆命令行工具这一课先认识三个最基础的。jps类似Linux下的ps只是它专门查看Java进程。运行当前示例程序时可以在另一个终端执行jps -l它会列出Java进程ID和主类信息。jinfo可以查看指定Java进程的运行参数配置比如jinfo -flags 进程IDjstat用来监控JVM内存和垃圾回收的动态信息一个常用参数组合是jstat -gc 进程ID 1000这条命令表示每隔1秒输出一次GC统计。第一次看到密密麻麻的数字不用慌说明JVM确实在背后管理着内存。你现在只需要知道“工具是在哪里打开”的等后面学到jvm内存模型和垃圾回收这些数值就都有了具体含义。4. jvm内存模型初探堆、栈、方法区一个都不能少4.1 运行时数据区的第一印象严格来说日常大家常说的“jvm内存模型”有两种所指一种是JVM运行时数据区也就是内存被划分为哪些物理区域另一种是Java内存模型JMM它更关注多线程下共享变量的可见性和有序性。第一课我先讲更直觉的“运行时数据区”JMM等到讲并发的时候再展开。JVM在运行Java程序时会把自己的内存划分为几个特定区域先记住其中最重要的四个堆、虚拟机栈、方法区、程序计数器。堆是最大的一块内存对象和数组的实例都在这里分配虚拟机栈是线程私有的每调用一个方法就会压入一个栈帧栈帧里保存局部变量、操作数栈、方法返回地址等方法区用来存储已被加载的类信息、常量、静态变量JDK 8以后改为元空间程序计数器则记录当前线程正在执行的字节码指令地址。4.2 用生活类比记住内存分区职责纯概念不好记我给你一个类比。把JVM想象成一个厨房堆是“仓库”所有菜品原料对象放在这里仓库空间最大但也最容易堆满堆满时就得清理对应垃圾回收虚拟机栈是“厨师的工作台”每个厨师线程都有自己的工作台工作台上摆着当前这道菜所需的配料一道菜做完就撤走对应方法执行完弹出栈帧方法区是“菜谱库”记录了各种菜的做法类信息全厨房共享程序计数器则是厨师手头那一页的“操作进度”做到哪一步了一翻就知道。这个类比能帮你快速建立画面感堆是共享的、栈是线程私有的、方法区存类元信息、程序计数器管执行位置。等后面学到OutOfMemoryError时你也会更容易判断是哪个区域出问题堆满了报java.lang.OutOfMemoryError: Java heap space栈溢出则报StackOverflowError。4.3 先留下几个疑问留给后续课程这一课我不展开讲每个区域的参数与异常细节但想提前给你埋几个钩子。第一个疑问对象一定在堆上分配吗后面学逃逸分析时你会发现不一定。第二个疑问元空间到底受不受JVM参数控制很多人会用-XX:MaxPermSize和-XX:MaxMetaspaceSize这两个参数的关系是什么。第三个疑问线程栈默认有多大栈里能放多少层方法调用这些疑问现在不必解决但请记下来。JVM学习最好的状态就是这样第一课留下问题后面的内容讲课开始逐一解答。你如果试着在搜索引擎搜一下这些关键词多半会看到各种互相矛盾的说法——这正是因为内存模型这块需要结合版本和具体配置来看所以一定不要满足于死记结论。5. 垃圾回收与调优启蒙第一课掌握到什么程度就够了5.1 垃圾回收到底回收谁初学者问得最多的一个问题是垃圾回收是回收内存那内存是哪里来的答案就是前面讲的堆。堆空间是有限的如果对象不停地创建、又一直被引用堆很快就会被占满。JVM里的垃圾回收器负责找出“已经没有任何引用指向”的对象然后把它们占用的内存回收再分配。打个比方堆是一个宿舍每个对象是住进来的学生垃圾回收器是宿管。宿管时不时检查哪些床位长期没人住就把床位腾出来给新学生。这个“检查没人住”的动作在JVM中就是可达性分析从一组叫GC Roots的根对象出发凡是能被引用链触摸到的对象都被标记为存活其余的都是可回收垃圾。这一课的接受程度就到“知道堆里的对象需要回收”即可。至于新生代、老年代、GC Roots具体包含哪些节点、不同回收器如何标记复制那是后面专门技术章节的内容。5.2 第一次接触JVM调优参数既然热搜词里有jvm调优我们就在第一课先接触几个最常见的启动参数让你知道调优不是改代码而是启动JVM时调整它的配置。我们最常遇到的两个参数是java -Xms256m -Xmx512m HelloJava-Xms设定堆的初始大小-Xmx设定堆的最大大小。为什么要调整它们因为JVM启动时默认的初始堆往往比较小如果服务启动后很快需要大量堆内存JVM会频繁扩容反过来如果最大堆设置过大又可能由于机器内存有限导致系统其他进程没有空间。典型的做法是按机器内存和服务预期设置一个合理区间比如物理内存8G时给服务设置-Xms2g -Xmx2g减少动态扩缩容开销。另外两个常见参数是-Xss控制每个线程栈的大小-XX:MetaspaceSize和-XX:MaxMetaspaceSize控制方法区/元空间的初始和最大容量。这一课你只需要记住它们的存在先不要背参数组合后面讲到内存模型时我会给出更完整的参数表。6. 新手实录JVM学习最容易踩的坑与排查方法6.1 环境变量配好了为什么还是提示“java不是内部或外部命令”这几乎是JVM学习第一课的最高频问题。我帮人排查过很多次九成是这样的原因JDK确实装了但打开的终端还是之前开的没有重新加载环境变量。Windows用户在改完系统环境变量后必须重新打开一个cmd窗口因为新配置不会自动同步到已打开的会话里。macOS/Linux用户则需要执行source ~/.bashrc或source ~/.zshrc让配置生效。第二个原因就是JAVA_HOME或PATH写错了比如路径里多了空格、写错了目录层级。排查时可以执行echo $JAVA_HOMEWindows是echo %JAVA_HOME%看看输出值对不对。还有一个常见坑是用which或where找不到java但java -version却正常这说明终端的PATH解析被某个缓存干扰多按一次rehash或重启终端就能解决。6.2 编译成功但运行时报“找不到或无法加载主类”这个问题同样常见而且特别容易打击新手信心。实际场景是javac HelloJava.java成功了执行java HelloJava却报错。最常见的原因是你把javac和java命令的运行目录搞混了或者类名大小写不对。注意Java类名是区分大小写的文件名和public类名必须一致。还有一个隐蔽的坑如果在包名路径下编译运行目录结构不匹配也会报这个错。比如源码声明了package com.demo;文件也在com/demo目录下运行时你就必须回到com目录的上一级用java com.demo.HelloJava来执行。其实这个问题本质上是JVM类加载机制的一部分以后学类加载器的时候会彻底搞懂现在先学会按“源码在哪包名是什么怎么对应目录”这个原则排查。6.3 学习心态上的三个典型误区技术坑好填心态坑更难绕开。我总结三个在JVM学习路上最常见的误区。第一个是“试图一遍全部记住”JVM的知识面实在太宽堆、栈、GC、类加载、字节码、调优工具一个月根本消化不完比较现实的做法是分阶段攻克第一课只要求“知道轮廓”。第二个是“只看不动手”JVM的很多特性必须通过命令行去观察比如打开jconsole或执行jstat只看博客永远没有体感。千万别怕敲命令那些命令不会把机器搞坏最多是进程报错重开一个就是了。第三个误区是“过早追求调优炫技”。很多新手刚学了-Xmx就想着把服务内存改来改去结果完全没看业务实际情况。调优的前提是监控数据告诉你确实有瓶颈而不是为了调而调。这个认知早点建立后面的学习会省很多事。7. 第一课后的练习与我的心得7.1 课后作业亲手把今天的流程走一遍光读不练等于没学。我建议你课后一定亲手完成三个任务。任务一不借助任何IDE纯命令行完成一个Java程序的编译和运行源码里加两行打印分别输出你的操作系统名称和JVM版本这需要用System.getProperty(os.name)和System.getProperty(java.version)运行时观察输出变化。任务二将一个Java类的main方法改为main大小写故意写错编译时看看javac报什么错再把编译成功的class文件用javap -c查看字节码尝试找到main方法里打印语句对应的字节码指令。任务三执行java -Xms64m -Xmx128m HelloJava这样的命令后再打开另一个终端运行jps -l查看进程并用jinfo -flags PID观察实际生效的参数你会看到设置的参数被列了出来。这三个任务分别训练你掌握“编译运行链路”、“字节码概念”、“JVM进程观测”正好对应本课的核心要点。7.2 一些个人体会关于JVM学习我自己最大的体会就是“急不得”。早期我试图靠一本大部头啃完整个虚拟机机制结果前三分之一就被绕晕了后面复习时反而走了弯路。后来改成“第一课只建立框架每个专题单独突破”的策略每一步都能动手验证进度反而快了很多。所以这门系列课也会沿用这个思路每一节只讲透一个主题搭配命令和日志让你在自己机器上能真实感受到JVM的存在。最后给你一个小建议学JVM期间笔记本上准备一张白纸每节课结束把今天学到的概念画成简图区域、流程、系数关系都画上去。画图的过程会逼你把模糊的理解梳理清楚等到一整套课程学完你手里的那几张纸就是属于你自己的JVM知识地图比任何一对一的教程都有价值。
返回列表