JVM学习之内存与垃圾回收篇1

news2024/11/17 12:28:18

文章目录

  • 1 JVM与Java体系结构
    • 1.0 Java发展重大事件
    • 1.1 虚拟机和Java虚拟机
    • 1.3 JVM整体结构
    • 1.4 Java代码执行流程
    • 1.5 JVM架构模型
    • 1.6 JVM的生命周期
    • 1.7 JVM发展历程
  • 2 类加载子系统
    • 2.1 ClassLoader
    • 2.2 用户自定义类加载器
      • 2.2.1 为什么需要自定义类加载器
      • 2.2.2 自定义类加载器的实现步骤
    • 2.3 双亲委派机制
    • 2.3 类的加载过程
    • 2.4 其他
  • 参考材料

1 JVM与Java体系结构

1.0 Java发展重大事件

2000年,JDK 1.3发布,Java Hot Spot Virtual Machine正式发布,成为Java的默认虚拟机。
2006年,JDK 6发布。同年,Java开源并建立了OpenJDK。顺理成章,Hotspot虚拟机也成为了OpenJDK中的默认虚拟机。
2008年,Oracle收购了BEA,得到了JRockit虚拟机。
2010年,Oracle收购了Sun,获得了Java的商标和HotSpot虚拟机。
2011年,JDK 7 中正式启用G1垃圾收集器。
2017年,JDK 9 中G1成为默认的GC,代替CMS。IBM也开源了J9 虚拟机。
2018年,JDK 11 LTS版本,发布革命性的ZGC,调整jdk授权许可。
2019年, JDK 12发布,增加了Shenandoah gc。

1.1 虚拟机和Java虚拟机

虚拟机,就是虚拟的计算机,是用来执行一系列虚拟计算机指令的软件。大体可以分为系统虚拟机和程序虚拟机。

Visual Box,VMWare就是系统虚拟机,它们完全是对物理计算机的仿真,提供了一个可运行的完整的操作系统软件平台。 Java虚拟机就是典型的程序虚拟机,它专门为执行单个计算机程序而设计。

无论是哪种虚拟机,其上所运行的软件都被限制于虚拟机提供的资源中。

Java虚拟机就是一台执行字节码(这个字节码可以是Java语言生成的,也可以是其他语言生成的)的虚拟计算机。

在这里插入图片描述

1.3 JVM整体结构

在这里插入图片描述

1.4 Java代码执行流程

java源码(xxx.java)
编译(前端编译):词法分析、语法分析、语法/抽象语法树、语义分析、注解抽象语法树和字节码生成器。
字节码(xxx.class)
Java虚拟机:类加载器,字节码校验器,字节码解释器和JIT编译器
二进制指令
操作系统

1.5 JVM架构模型

Java编译器输入的指令流基本上是一种基于栈的指令集架构,另外一种指令架构是基于寄存器的指令架构
栈式架构特点:

  1. 设计和实现更简单,适用于资源受限的系统。
  2. 避开了寄存器分配难题:使用零地址指令方式分配。
  3. 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小(单条指令更短),编译器容易实现。
  4. 不需要硬件支持,可移植性更好,更好实现跨平台。

寄存器式架构特点:
5. 典型应用是x86的二进制指令集:传统pc以及Android的Davlik虚拟机。
6. 指令集架构完全依赖硬件,可移植性差。
7. 性能优秀和执行高效。
8. 花费更少的指令(指令条数少)去完成一项操作。
9. 大部分情况下, 基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主。基于栈的指令架构往往以零地址指令为主。

举例:两种指令架构下实现2+3

iconst_2  // 常量2入栈
istore_1
iconst_3 // 常量3入栈
istore_2
iload_1
iload_2
iadd // 常量2,3出栈,执行相加
istore_0  // 结果5入栈
mov eax,2 // eax 初始值设置为2
add eax,3 // 将寄存器内的值+3

1.6 JVM的生命周期

【启动】
Java虚拟机的启动是通过引导类加载器(Bootstrap class loader)创建一个初始类(initial class)来完成的,这个类由虚拟机的具体实现指定的。
【执行】
一个运行中的Java虚拟机有一个清晰的任务:执行java程序。
程序开始执行时它才运行,程序结束时它就停止。
执行一个Java程序的时候,真正执行的是一个Java虚拟机的进程。
【退出】

  • 程序正常执行结束
  • 程序在执行过程中遇到了异常或者错误而异常终止
  • 由于操作系统出现错误而导致Java虚拟机进程终止。
  • 某线程调用Runtime类或者System类的exit方法,或者调用Runtime类的halt方法,并且Java安全管理器也允许这次exit或者halt操作。
  • JNI规范描述了用JNI Invocation API来加载或者卸载Java虚拟机时,Java虚拟机退出。

1.7 JVM发展历程

【Sun Classic VM】
1996年,JDK1.0发布了第一款商用的java虚拟机,Sun Classic VM。jdk 1.4的时候被淘汰掉了。
此VM只提供解释器
如果要使用jit编译器,需要进行外挂。一旦使用了JIT,JIT就会接管虚拟机的执行系统。解释器就不再工作。解释器和编译器不能配合工作。
现在HotSpot虚拟机中内置了此虚拟机。

【Exact VM】

  • jdk1.2时提供了此虚拟机。
  • Exact Memory Management:准确式内存管理。 虚拟机可以知道内存中某个位置的数据具体是什么类型。
  • 具备现代高性能虚拟机的雏形:(1)热点探测(2)编译器与解释器的混合工作模式。
  • 只在Solaris平台短暂使用过。 最终被Hotspot取代。

【HotSpot VM】

  • jdk1.3的时候成为了默认的虚拟机。
  • 占有绝对的市场地位,称霸武林。 Oracle jdk和OpenJDK中都是默认的虚拟机。
  • 使用热点探测技术。通过计数器找到最有编译价值的代码,触发即时编译或者栈上替换。通过编译器和解释器协同工作,在最优化响应时间和最佳执行性能中取得平衡。

【JRockit VM】

  • 专注于服务端应用。 不关注启动速度,内部不包含解释器,全部代码依靠即时编译器编译后执行。
  • 最快的JVM

【J9】

  • IBM公司所有
  • 号称是最快的虚拟机。 主要是在IBM的产品上使用效果比较好。

【Azul VM】

  • 与特定硬件平台绑定,软硬件配合的专有虚拟机。
  • 每个Azul VM实例都可以管理至少数十个CPU和数百个GB内存的硬件资源,并提供在巨大的内存范围内实现可控的GC时间的垃圾收集器、专有硬件优化的线程调度等优秀特征。

【Liquid VM】

  • 与特定硬件平台绑定,软硬件配合的专有虚拟机。
  • Liquid VM不需要操作系统的支持,或者说它本身实现了一个专用操作系统的必要功能,如线程调度、文件系统和网络支持等。

【Apache Harmony】

  • 它的Java类库代码被吸收进了Android SDK中。

【Micorsoft JVM】

  • 初衷是为了在IE浏览器中支持Java Applets,只能在windows平台下运行,是当时windows平台下性能最好的Java VM
  • 1997年,因为侵犯商标,不正当竞争等罪名,此VM下架了。

【TaobaoJVM】

  • 阿里基于OpenJDK开发了自己定制化的AlibabaJDK
  • 是深度定制且开源的高性能服务器版的Java虚拟机。
  • GCIH(GC Invisible heap)技术实现了off-heap,即将生命周期较长的Java对象从heap中搬移到heap之外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC回收频率和提升GC的回收效率的目的。
  • GCIH中的对象能够在多个虚拟机进程中实现共享。
  • 严重依赖intel的cpu,损失了兼容性,提高了性能。

【Dalvik VM】

  • Goolge开发的,应用于Android系统,并在Android2.2中提供了JIT
  • Dalvik VM只能称为虚拟机,不能称为“Java虚拟机”,它没有遵循Java虚拟机规范。
  • 执行的是dex(Dalvik Executable)文件,效率较高。dex文件可以通过classes文件转化而来。
  • 基于寄存器的指令架构
  • Android 5.0使用支持提前编译(Ahead of Time Compilation, AOT)的ART VM替换掉Dalvik VM。

【Graal VM】

  • 2018.04 Oracle Labs公开
  • Run Programmes Faster Anywhere
  • 跨语言全栈虚拟机,可以作为“任何语言”的运行平台。包括c和cpp。

2 类加载子系统

2.1 ClassLoader

在这里插入图片描述
ClassLoader是可以有多个的。

文件头特定的文件标识就是“咖啡baby”。
在这里插入图片描述
在这里插入图片描述
所有的类加载器不是继承关系,可以看做是等级关系,永远都是最高等级的bootstrap先加载对象,其加载不了的才轮到后面。

【Bootstrap类加载器】
bootstrap加载器加载jre/lib/rt.jarresources.jar或者sun.boot.class.path路径中的内容,Object类,String类,ArrayList等都是其加载的。
查看启动类加载器的加载路径

URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();

加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。

【Ext类加载器】
扩展类加载器加载的是 jre/lib/ext/*.jar中的内容。
如果用户创建jar包也放在了ext目录中, 也会自动由ext类加载器进行加载。

查看扩展类加载器的加载路径

String extDirs = System.getProperty("java.ext.dirs");
String dirArr = extDirs.split(";");

【系统类加载器】
应用程序类加载器加载Java编码中自定义的对象。
负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库。
系统类加载器是程序中默认的类加载器,一般来说,Java应用的类都有它来完成。
可以通过如下方式获取到此类加载器:ClassLoader.getSystemClassLoader()

验证用例程序

package org.example.classloader;

import com.sun.nio.zipfs.JarFileSystemProvider;

public class ClassLoaderTest {
    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(o.getClass().getClassLoader()); // bootstrap class loader 获得不到的,返回值为null

        System.out.println("==========================================");

        MyObject myObject = new MyObject();
        System.out.println(myObject.getClass().getClassLoader());
        System.out.println(myObject.getClass().getClassLoader().getParent());
        System.out.println(myObject.getClass().getClassLoader().getParent().getParent());

        System.out.println("==========================================");

        JarFileSystemProvider provider = new JarFileSystemProvider();
        System.out.println(provider.getClass().getClassLoader());
        System.out.println(provider.getClass().getClassLoader().getParent());
    }
}

class MyObject {

}


null
==========================================
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@14ae5a5
null
==========================================
sun.misc.Launcher$ExtClassLoader@14ae5a5
null

从JVM规范的角度上讲,类加载器分为两种:引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-defined ClassLoader)。Java虚拟机规范中将所有的派生于ClassLoader的类加载器全部划归为自定义类加载器。

获取classLoader的方式:

  1. 获取一个类的ClassLoader
clazz.getClassLoader();
  1. 获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader();
  1. 获取系统的ClassLoader
ClassLoader.getSystemClassLoader();
  1. 获取调用者的ClassLoader
DriverManger.getCallerClassLoader();

2.2 用户自定义类加载器

2.2.1 为什么需要自定义类加载器

  • 隔离类加载器
  • 修改类加载方式
  • 扩展加载源
  • 防止源码泄漏

2.2.2 自定义类加载器的实现步骤

  1. 继承抽象类java.lang.ClassLoader, 建议把自定义的类加载逻辑放在findClass()方法中。
  2. 如果没有特别复杂的需求,可以直接继承URLClassLoader,这样可以避免自己去写findClass方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。

2.3 双亲委派机制

在这里插入图片描述
助记:“往上捅”

用一个案例证明双亲委派机制保证了Java代码的安全性。

package java.lang;

public class String {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}


错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

双亲委派机制的好处:

  • 避免类的重复加载
  • 保护程序安全,防止核心api被随意篡改。

2.3 类的加载过程

加载(Loading)-> 【验证(Verification)-> 准备(preparation)-> 解析(Resolution)】-> 初始化(Initilization)
--------------------------【这个就是链接过程】

【加载】

  1. 通过一个类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流所定义的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

.class文件的来源:

  • 本地系统中
  • 网络获取,典型场景:Web Applet。
  • 从zip包中读取,是从jar、war中读取的基础。
  • 运行时计算生成,使用最多的是动态代理技术。
  • 由其他文件生成,典型场景:jsp应用
  • 从专有的数据库中提取.class文件,比较少见
  • 从加密文件中获取,典型的防止Class文件被反编译的保护措施。

【验证】

  1. 目的在于确保class文件中包含的信息符合当前虚拟机的要求,保证被加载的类的正确性,不会危害虚拟机自身的安全。
  2. 主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。

【准备】

  1. 为类变量分配内存并且设置类变量的默认初始值,即零值
  2. 如果是被final修饰的static变量,就已经是一个常量了,常量在编译阶段已经分配值了,准备阶段会显示初始化。
  3. 这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量会随着对象一起分配到Java堆中。

【解析】

  1. 将常量池内的符号应用转换为直接引用的过程。
  2. 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。

【初始化】
4. 初始化阶段就是执行类构造器方法 <clinit>()的过程. <clinit>()不同于类的构造器(关联:构造器是虚拟机视角下的<init>()
5. <clinit>()不需要定义,是由javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。 类构造器方法中的指令按照语句在源文件中出现的顺序执行。
6. 若一个类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()方法已经执行完毕。
7. 虚拟机必须保证一个类的<clinit>()方法在多线程环境下被同步加锁。
8.

public class ClassInitTest {
	static {
		number = 20;
		// 报错:非法的前向引用
		// System.out.println(number);
	}

	// prepare : number = 0;
	// initial : 20 ---> 10
	private static int number = 10; 

	public static void main(String[] args) {
		System.out.println(ClassInitTest.number); // 10
	}
}

2.4 其他

JVM中表示两个class对象是否为同一个类的两个必要条件:

  • 类的完整类名必须一致,包括包名
  • 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同

JVM必须知道一个类型是由启动类加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。 当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。

Java程序对类的使用分为:主动使用和被动使用。
主动使用情况:
1 创建类的实例
2 访问某个类或者接口的静态变量,或者对该静态变量赋值
3 调用类的静态方法
4 反射(例如Class.forName(“xxxx”))
5 初始化一个类的子类
6 Java虚拟机启动时被标明为启动类的类
7 JDK7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatciREF-putStaticREF-invokeStatic句柄对应的类没有初始化,那么就需要初始化
除了主动使用的情况,都是被动使用,被动使用的时候不会导致类的初始化。

参考材料

[1] https://www.bilibili.com/video/BV1jJ411t71s?p=5&spm_id_from=pageDriver&vd_source=f4dcb991bbc4da0932ef216329aefb60

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/756997.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【框架篇】对象注入的三种实现方式

对象注入的实现 一&#xff0c;实现方式的使用 对象注入也可被称为对象装配&#xff0c;是把Bean对象获取出来放到某个类中。 对象注入的实现方式有3种&#xff0c;分别为属性注入&#xff0c;Setter注入和构造方法注入。 为了更好地理解对象注入的实现方式&#xff0c;搞个…

24 MFC文档串行化和单文档应用程序

文章目录 文档串行化全部代码 单文档应用程序搭建原理搭建框架Win32 过度到MFC 三部曲设置ID资源全部代码 单文档应用程序设置标题绘图 简单的管理系统部分代码 文档串行化 ui 设计 保存 void CfileDemoDlg::OnBnClickedBtnSave() {UpdateData();//CFile file(L"Demo.dat…

python+pytest接口自动化(9)-cookie绕过登录(保持登录状态)

目录 cookie工作原理 cookie绕过登录 总结 在编写接口自动化测试用例或其他脚本的过程中&#xff0c;经常会遇到需要绕过用户名/密码或验证码登录&#xff0c;去请求接口的情况&#xff0c;一是因为有时验证码会比较复杂&#xff0c;比如有些图形验证码&#xff0c;难以通过…

旅游信息推荐系统带文档springboot+vue

功能 用户注册和登录&#xff1a;用户可以注册一个账户并登录到系统中。旅游项目展示&#xff1a;系统展示各种旅游项目的信息&#xff0c;包括目的地、行程、费用等。旅游项目搜索和筛选&#xff1a;用户可以搜索和筛选旅游项目&#xff0c;根据目的地、日期、费用等条件。预…

Linux系统终端窗口ctrl+c,ctrl+z,ctrl+d的区别

时常在Linux系统上&#xff0c;执行某命令停不下来&#xff0c;就这几个ctrl组合键按来按去&#xff0c;今天稍微总结下具体差别&#xff0c;便于以后linux系统运维操作 1、ctrlc强制中断程序&#xff0c;相应进程会被杀死&#xff0c;中断进程任务无法恢复执行 2、ctrlz暂停正…

mongodb集群搭建

下载地址&#xff1a; https://www.mongodb.com/try/download/community下载mongodb-linux-x86_64-rhel70-5.0.18 搭建集群 tar -zxvf mongodb-linux-x86_64-rhel70-5.0.18.tgz mkdir -p data/dp cd mongodb-linux-x86_64-rhel70-5.0.18 mkdir -p data/db mkdir log mkdir c…

Ubuntu 23.04安装最新版本Halcon 23.05

Ubuntu 23.04安装最新版本Halcon 23.05 官网下载安装环境变量设置创建快捷方式给个最新ubuntu的镜像源地址 官网下载 去Halcon官网&#xff1a;https://www.mvtec.com/products/halcon/&#xff0c;注册或登录&#xff0c;点击Download&#xff1a; 或者进入大恒网站&#xf…

Ubuntu最新版本23.05配置Flameshot(途中解决疑难杂症)

Ubuntu最新版本23.05配置Flameshot截图软件 安装方法&#xff1a;添加Ubuntu的快捷键遇到的问题解决 安装方法&#xff1a; sudo apt install flameshot出现该页面表示成功&#xff1a; 可以直接在终端输入&#xff1a;flameshot gui flameshot gui进行截图。 添加Ubuntu的…

云计算与大数据——MPI集群配置

什么是MPI集群&#xff1f; MPI&#xff08;消息传递接口&#xff09;是一种用于编写并行程序的标准&#xff0c;它允许在多个计算节点上进行通信和协作。MPI集群配置是指在一个或多个计算节点上设置MPI环境以实现并行计算。 MPI集群配置的步骤&#xff1a; 硬件选型&#x…

三菱PLC上位机测试

利用三菱的MX Component与三菱PLC进行以太网通信&#xff0c;我们可以用官方的dll编写C#代码&#xff0c;特别简单&#xff0c;最后附上整个源码下载。 1. 安装MX Component&#xff08;必须&#xff09;和GX WORKS3&#xff08;主要是仿真用&#xff0c;实际可以不装&#xf…

空间光通信-调制解调滤波与同步

图文并茂&#xff0c;讲解电磁波传播原理_哔哩哔哩_bilibili 深入浅出空间光通信-3.调制解调滤波与同步_哔哩哔哩_bilibili 傅里叶变换这样学&#xff0c;何愁不会呢&#xff1f;直观理解傅里叶变换_哔哩哔哩_bilibili 第二十三课&#xff1a;声音编辑必看&#xff01;&…

【六袆 - windows】windows计划任务,命令行执行,开启计划任务,关闭计划任务,查询计划任务

windows计划任务 查看 Windows 自动执行的指令取消 Windows 中的计划任务启动执行计划任务 查看 Windows 自动执行的指令 您可以使用以下方法&#xff1a; 使用任务计划程序&#xff1a;任务计划程序是 Windows 内置的工具&#xff0c;可以用于创建、编辑和管理计划任务。您可…

pytest+allure运行出现乱码的解决方法

pytestallure运行出现乱码的解决方法 报错截图&#xff1a; 这是因为没有安装allure运行环境或者没有配置allure的环境变量导致&#xff0c;解决方案&#xff1a; 1.安装allure运行环境 官方下载地址&#xff1a;https://github.com/allure-framework/allure2/releases 百度…

JavaSE - 内部类

目录 final定义常量 1. 内部类 1.1 实例内部类 1.1.1 如何获取实例内部类的对象 1.1.2 实例内部类中不能有静态的成员变量 1.1.3 实例内部类方法中可以直接访问外部类中的任何成员 1&#xff09;在实例内部类方法中访问同名的成员时&#xff0c;优先访问自己的&#xff0…

nacos注册中心+Ribbon负载均衡+完成openfeign的调用

目录 1.注册中心 1.1.nacos注册中心 1.2. 微服务注册和拉取注册中心的内容 2.3.修改订单微服务的代码 3.负载均衡组件 3.1.什么是负载均衡 3.2.什么是Ribbon 3.3.Ribbon 的主要作用 3.4.Ribbon提供的负载均衡策略 4.openfeign完成服务调用 4.1.什么是OpenFeign 4.2…

5.2 Python高阶特性之---切片迭代

一、 切片 一般用于提取指定区间内的内容&#xff0c;常用于&#xff1a;str、list、tuple等类型的的局部变量&#xff0c;如下为具体案例1、 【列表切片】 res_list [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]1&#xff09; 无步长: …

C++——类的六大默认成员构造函数

文章目录 1.默认成员函数思维导图2.构造函数定义特性 2.析构函数定义特性 3.拷贝构造函数定义特性 4.赋值构造函数定义特性 5.重载取地址运算符定义特性 6.重载const取地址运算符定义特性 1.默认成员函数思维导图 2.构造函数 定义 在面向对象编程中&#xff0c;构造函数是一种…

RHCSA——Linux网络、磁盘及软件包管理

ZY目录 Linux操作系统讲解&#xff1a;一、网络管理1、NetworkManager1.1、nmtui界面&#xff1a;1.2、nmcli使用方法&#xff1a; 2、配置网络2.1、网络接口以及网络连接2.2、配置方法&#xff1a;2.3、ping命令&#xff1a;2.4、wget命令 二、磁盘管理2.1、分区得两种格式2.1…

HCIP第五次作业

配置IP地址 r1 [r1]int g0/0/0 [r1-GigabitEthernet0/0/0]ip add 12.0.0.1 24 [r1-GigabitEthernet0/0/0]int lo0 [r1-LoopBack0]ip add 192.168.1.1 24 [r1-LoopBack0]int lo1 [r1-LoopBack1]ip add 10.0.0.1 24 r2 [r2]int g0/0/0 [r2-GigabitEthernet0/0/0]ip add 12.0.0.2 …

在vs里配置c运行环境

勾选环境包 勾选完安装就好 创建c项目 选择相关配置 设置名称和位置 创建c后缀文件 一新建项 二设置名字 输出hello world 代码段 运行结果