一.从零开始JVM实战高手

这篇具有很好参考价值的文章主要介绍了一.从零开始JVM实战高手。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

从零开始👉JVM实战高手,建议收藏,加源妹儿微信 “ ymsdsss”领取整套JVM实战资料和精品视频,关注微信公众号 “疯狂Java程序猿” ,后续会推出JVM,Mybatis,SpringBoot,Redis等等一些列从入门到源码剖析的精品视频和文章。你的鼓励是我最大的动力。

作者:源码时代-Raymon老师

前言

目前市面上已有太多的JVM相关教程和书籍,但是大部分偏理论,比较枯燥难懂,少有结合实际业务开发,站在项目开发的视角下去分析和讲解相关经验的教程;而本套教程会从零开始带着大家一步一步深入了解JVM底层原理,以及结合一些开发中的典型生产环境问题来进行实战剖析,并且几乎采用一步一图的方式进行讲解。

通过核心理论和实战案例的结合,希望能对大家对JVM的理解和应用更上一层楼。

为什么要学习JVM?

  1. 面试需要(中大厂必考核的一项技能),说说你的项目是如何处理JVM GC、OOM等问题…

  2. 深入的理解Java这门语言。(万丈高楼地基最重要),Java的 boolean类型在JVM中是如何表现?类路径和类名是否唯一确定一个类?

  3. 更好的解决线上排查问题(更好的解决生产线问题), 线上系统跑着跑着突然卡死,FullGC非常频繁,各种GC日志一大堆,无从下手分析,何谈优化,遇到OOM异常直接躺平,求救或跑路

  4. 走向高级程序员和架构师的必经之路(底层逻辑),知其然更要知其所以然,透过现象看本质。只有掌握了底层逻辑,只有探寻到万变中的不变,才能动态地、持续地看清事物的本质;可以通过不变的底层逻辑,推演出顺应时势的方法论。

学习路线图

我们的学习路线图就通过从一个类的加载开始,来学习Java是如何将代码运行起来的,由点到面的方式,一步一步深入理解JVM的整体运行机制。

  • 理论相关的必备知识,通过大量绘图让小白也能看懂
  • 分享JVM实践经验,解决JVM生产环境的参数优化,GC问题,以及OOM问题

一.从零开始JVM实战高手

整体内容划分:

  1. Java代码运行机制+类加载机制+JVM内存结构深入讲解+案例实战
  2. 垃圾回收器与算法+分配策略+案例剖析
  3. JVM过程剖析+性能监控+故障处理+优化实战
  4. OOM内存溢出+场景分析&解决方案

你的Java代码是如何运行的?

我们平时写的Java代码,到底是如何运行起来的?

我们都知道,我们平时创建的一个一个类,在本地磁盘中的文件名后缀就是 .java,比如User.java 、Product.java ,这也叫做源代码文件。这些源代码文件必须经历我们的javac工具进行编译后生成 .class 的字节码文件才能被运行。

那接着我们就要继续思考了:那这些 .class 字节码文件又是如何运行起来的?(这里我们可以借助于DOS窗口执行 java 命令进行启动)

> javac User.java
> java User

输出: hello World…

此时一旦采用 java 命令,实际上就是启动了一个JVM进程,由JVM来负责加载这些字节码文件到内存进行执行。

一.从零开始JVM实战高手

而将class字节码文件加载到虚拟机的内存,这个过程称为类加载,其中涉及到 【类加载机制】和【类加载器】的概念。

一.从零开始JVM实战高手

当字节码文件被类加载器加载进入到JVM内存中后,会通过JVM的执行引擎来执行我们内存中对应的类,比如类中的main方法,就会先被执行,而main方法中如果还涉及到其他的对象引用,类加载又会开始加载对应的字节码文件到内存,再由JVM进行调用执行。(如下图)

一.从零开始JVM实战高手

当我们通过Eclipse或IDEA工具开发完一个完整的项目后,一般都会将项目整体打成一个jar 包或者war包,然后部署到对应线上服务器进行运行;其实就是将我们写的所有java代码编译成对应的字节码文件后,加上一些项目的资源文件一起打包,部署进服务器比如Tomcat,当我们通过 java -jar之类的命令就可以运行和执行我们写好的代码。

一.从零开始JVM实战高手

ok,通过以上的分析,我们先整体对java代码的运行流程做了一个初步的介绍,接下来再深入分析类加载过程又是如何执行的,一步一步深入学习。

请说下JVM的类加载机制

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个部分统称为链接(Linking),这7个阶段的发生顺序如图:

一.从零开始JVM实战高手

加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始, 这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。

加载Loading阶段

加载Loading阶段就是JVM第一次去加载和读取对应位置上的文件,上述的整个类加载过程其实都是类加载器(后续讲)在完成。

相当于正式建立了IO通道,在这条通道上,我们需要做上述的一系列事情,比如验证、准备、解析等。一.从零开始JVM实战高手

思考:JVM在什么情况下会加载一个类呢?

通过以上的类加载流程,我们可以得知第一个环节就是加载一个类,因此当我们在IDEA中或直接运行某一个类的时候(比如First.java),其实是启动了JVM进程,然后JVM会通过类加载器将这个类的字节码(First.class)加载到内存,然后调用main方法开始执行。如果main方法中的代码是:

public class First {
    public static void main(String[] args) {
        //创建Second这个类的实例
        Second second = new Second();
    }
}

JVM这个时候会先检查内存中是否有该类的对象,如果没有会触发类加载器加载磁盘中的Second.class字节码到内存中,如下图:

一.从零开始JVM实战高手

如何验证类是否已经加载?可以通过如下方式演示:

  1. 在D盘创建cn/itsource/load/ 三个文件夹
  2. 将LoadTest.java文件拷贝到该文件夹
  3. 通过cmd编译
  4. 运行并查看:java -XX:+TraceClassLoading -cp . cn.itsource.load.LoadTest
验证阶段

验证是链接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

主要包括四种验证,文件格式验证(魔数CAFEBABE),元数据验证,字节码验证,符号引用验证。

简单说就是我们的【.class】文件是否符合JVM规范,是否有被篡改,否则JVM是没法执行该字节码文件的。

每个符合规范的Java文件二进制开头应该是对应的魔数CAFEBABE

一.从零开始JVM实战高手

每个类在被加载到JVM内存前都会还行验证这个过程:

一.从零开始JVM实战高手

准备阶段

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段
一.从零开始JVM实战高手

注意事项:static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾,(换句话理解就是:1.7之前存储于方法区,1.7之后存储于堆内存中)
一.从零开始JVM实战高手

static 变量分配空间和赋值是两个步骤,分配空间(内存分配)在准备阶段完成,赋值在初始化阶段完成

关于准备阶段,还有两个容易产生混淆的概念需要着重强调,首先是这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次是这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:

public static int value = 123;

那变量value在准备阶段过后的初始值为0而不是123,因为这时尚未开始执行任何Java方法,而把 value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作要到类的初始化阶段才会被执行。

如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成

上面提到在“通常情况”下初始值是零值,那言外之意是相对的会有某些“特殊情况”:如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量值就会被初始化为ConstantValue属性所指定 的初始值,假设上面类变量value的定义修改为:

public static final int value = 123;

编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置 将value赋值为123。

如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成

Java代码在进行Javac编译的时候,在虚拟机加载Class 文件的时候进行动态连接。在Class文件中不会保存各个方法、字段最终 在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用的。当虚拟机做类加载时,将会从常量池获得对应的符号 引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

  • 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引 用的目标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布局可以各不相同,

    但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式中。

  • 直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚 拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在。

这三个阶段中,大家最应该关心的核心是:准备阶段,这个阶段是给加载进来的类进行空间的分配,以及static静态变量的空间分配,并且给与初始化值。

一.从零开始JVM实战高手

类的初始化

这里安利一款查看字节码的IDEA插件:IDEA中安装插件:<查看对应字节码文件二进制>

一.从零开始JVM实战高手

通过准备阶段类变量已经赋过一次系统要求的初始零值,而初始化阶段就是在给类变量进行赋值操作。

初始化阶段会执行类构造器方法<clinit>() ,该方法不同于类的构造器(是虚拟机视角下的<init>());该方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。

通过代码观察:我们second类中的静态变量b是在 <clinit>方法中才被真正初始化的–>对应了静态变量的赋值操作

一.从零开始JVM实战高手

如果我们在second类中再添加一个静态代码块,去修改b的值为11,可以验证静态代码块的操作也是在<clinit>中执行的:

一.从零开始JVM实战高手

注意1: 编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问

public class InitDemo {
    static {
        i = 20;// 给变量赋值可以正常编译通过
        //System.out.println(i);// 这句编译器会提示“非法向前引用”
    }
    public static int i = 10;
}

注意2:父类初始化先执行<clinit>()方法与类的构造函数(即在虚拟机视角中的实例构造器<init>()方法)不同,它不需要显式地调用父类构造器,Java虚拟机会保证在子类的 clinit方法执行前,父类的clinit方法已经执行完毕。因此在Java虚拟机中第一个被执行的 clinit 方法的类型肯定是java.lang.Object。

注意3:线程同步 : Java虚拟机必须保证一个类的()方法在多线程环境中被正确地加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行完毕()方法。如果在一个类的()方法中有耗时很长的操作,那就可能造成多个进程阻塞,在实际应用中这种阻塞往往是很隐蔽的。

public class ThreadInitTest {
    public static void main(String[] args) {
        Runnable script = new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread() + "start");
                DeadLoopClass dlc = new DeadLoopClass();
                System.out.println(Thread.currentThread() + " run over");
            }
        };
        Thread thread1 = new Thread(script);
        Thread thread2 = new Thread(script);
        thread1.start();
        thread2.start();
    }
}

class DeadLoopClass {
    static {
        if (true) {
            System.out.println(Thread.currentThread() + "init DeadLoopClass");
            while (true) {
            }
        }
    }
}

其他线程虽然会被阻塞,但如果执行<clinit>()方法的那条线程退出<clinit>()方法后,其他线程唤醒后则不会再次进入<clinit>()方法。同一个类加载器下,一个类型只会被初始化一次。

总结一句话:一个类如果已经被一个线程加载到内存中,也就代表加载过程中的这些阶段(加载-链接)都已经执行完毕了,那么后续如果有其他线程想要访问对应的字节码对象直接访问即可,不需要再次去加载这个类的字节码文件了。

对应了我们JavaSE中的语法问题:一个类中的静态修饰的内容会随着类的加载而加载,只会被加载一次《这个说话仅仅限于同一个类加载器》

总结
  • 类加载阶段:建立通道,把class文件加载到JVM内存中
  • 验证阶段:就是校验字节码文件是否符合JVM的规范
  • 准备阶段:就是分配内存空间,给类变量设置初始化值
  • 解析阶段:将符号引用(占位符)转化为直接引用(真实内存地址值)
  • 初始化阶段:给类变量进行赋值的操作

文章对应的配套视频请关注微信公众号:疯狂Java程序猿,文章来源地址https://www.toymoban.com/news/detail-415623.html

到了这里,关于一.从零开始JVM实战高手的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Pytorch从零开始实战17

    本系列来源于365天深度学习训练营 原作者K同学 本文基于Jupyter notebook,使用Python3.8,Pytorch1.8+cpu,本次实验目的是了解生成对抗网络。 生成对抗网络(GAN)是一种深度学习模型。GAN由两个主要组成部分组成:生成器和判别器。这两个部分通过对抗的方式共同学习,使得生成器

    2024年01月20日
    浏览(25)
  • 从vue小白到高手,从一个内容管理网站开始实战开发第七天,登录功能后台功能设计--通用分页、枚举以及相关工具类

    上一篇实现了数据库访问层的相关功能,还没有了解的小伙伴可以去看前面文章实现的内容,因为每一篇内容都是连贯的,不学习的话可能下面的内容学习起来会有点摸不着头脑 从vue小白到高手,从一个内容管理网站开始实战开发第六天,登录功能后台功能设计--API项目中的

    2024年01月22日
    浏览(30)
  • 从vue小白到高手,从一个内容管理网站开始实战开发第八天,登录功能后台功能设计--业务逻辑层基础接口和基础服务实现

    上一篇我们介绍了项目后续要使用到的工具类,关于工具类的创建可以查看 从vue小白到高手,从一个内容管理网站开始实战开发第七天,登录功能后台功能设计--通用分页、枚举以及相关工具类-CSDN博客 文章浏览阅读2次。本次内容主要介绍了项目后续用到的部分工具类,这些

    2024年01月22日
    浏览(31)
  • 从零开始搭建家庭网络:软路由实战经验分享(一)

    最近入门了软路由,研究了半个月,一步一步从网络小白到最后自己搭建了家庭局域网络,现在给大家分享一下我搭建软路由的经验。 既然有软路由,那么相对的肯定有硬路由:目前我们网上买到的路由器,就是硬路由,这种从一开始就是 按照路由器设计规范设计出来的硬

    2024年02月02日
    浏览(35)
  • 联邦学习实战-1:用python从零开始实现横向联邦学习

    什么是联邦学习? 简单来说就是在一个多方的环境中,数据集是零散的(在各个不同的客户端中),那么怎样实现机器学习算法呢? 首先想到的就是将多个数据集合并合并起来,然后统一的使用传统的机器学习或者深度学习算法进行计算,但是如果有一方因为数据隐私问题

    2023年04月08日
    浏览(38)
  • 实战篇:从零开始构建一个完整的校园一卡通平台

    ✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 | SpringBoot/SSM Python实战项目 | Django 微信小

    2024年01月20日
    浏览(83)
  • 【从零开始学Skynet】实战篇《球球大作战》(十二):场景代码设计(上)

            场景服务会处理绝大部分的游戏逻辑。新建 service/scene/init.lua ,开始编写相关代码。    场景中包含小球和食物这两种对象,先看看小球的实现。代码如下所示: 首先定义 balls 表 和 ball 类 , balls 表 会以 玩家 id 为索引,保存战场中各个小球的信息; 小球与玩

    2023年04月21日
    浏览(30)
  • 【从零开始学Skynet】实战篇《球球大作战》(十三):场景代码设计(下)

            《球球大作战》是一款服务端运算的游戏,一般会使用主循环程序结构,让服务端处理战斗逻辑。如下图所示,图中的 balls 和 foods 代表服务端的状态,在循环中执行 “ 食物生成 ”“ 位置更新 ” 和 “ 碰撞检 测” 等功能,从而改变服务端的状态。 scene 启动后

    2023年04月18日
    浏览(46)
  • Android SDK安装教程(超详细),从零基础入门到实战,从看这篇开始

    前言 在使用appnium的时候,除了安装JDK之外,也需要安装Android SDK。那么,正确安装Android SDK是怎样的呢,跟着小编继续往下看。 安装Android SDK和环境配置 1.安装Android SDK 首先打开官网:https://www.androiddevtools.cn/ 选中导航中的Android SDK工具,再点击SDK Tools。 网页会自动往下翻,

    2024年02月14日
    浏览(38)
  • 【云原生 | 从零开始学Docker】三、Docker实战之安装Nginx和Tomcat

    该篇文章已经被专栏《从零开始学docker》收录 通过前面两章的学习,相信各位朋友们都在自己的服务器上安装了docker了,也熟悉了一些常用的指令,这里我带着大家去做一些简单的实战来让各位更熟悉这些指令!接下来的内容最好跟着敲一遍,记一下步骤,能让你更深刻理解

    2023年04月08日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包