Arthas和常量池

news2024/11/25 5:02:45

一、Arthas

快速入门 | arthas

1、Arthas使用

运行arthas提供的应用程序

curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar

 运行arthas工具jar包

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

 启动界面如下图:

 输入1按enter键进入

 如果不清楚arthas的命令可以此时输入help查看

查看当前进程信息输入dashboard按回车

如果线程占用cpu比较多可以输入thread ID查看栈方法信息,如果需要通过线程匹配具体信息可以这样输入thread ID | grep 'main('

 thread -b查看死锁

反编译查看代码是否是最新代码输入jad demo.MathGame

 查看关键方法返回值,比如查看demo.Math类下面primeFactors方法的返回值可以输入watch demo.MathGame primeFactors returnObj

二、gc日志

对于java应用我们可用通过一些配置把程序运行过程中的gc日志全部打印出来,然后分析gc日志得到关键性指标,分析gc原因,调优参数,打印gc日志方法,在jvm参数里增加参数打印到当前目录下gc-%t.log文件中,%t代表时间,-XX:NumberOfGCLogFiles=10参数表示会生成10个gc日志文件,-XX:GCLogFileSize=100M参数表示gc日志文件大小超过100M会重新另起一个gc日志文件存储

-Xloggc:./gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps  -XX:+PrintGCTimeStamps -XX:+PrintGCCause  
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M

运行程序时加上gc打印参数

java -jar -Xloggc:./gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps  -XX:+PrintGCTimeStamps -XX:+PrintGCCause -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M microservice-eureka-server.jar

 我们可以看到图中第一行红框,是项目的配置参数。这里不仅配置了打印GC日志,还有相关的VM内存参数

第二行红框中的是在这个GC时间点发生GC之后相关GC情况。

  1. 对于2.668: 这是从jvm启动开始计算到这次GC经过的时间,前面还有具体的发生时间日期。
  2. Full GC(Metadata GC Threshold)指这是一次full gc,括号里是gc的原因, PSYoungGen是年轻代的GC,ParOldGen是老年代的GC,Metaspace是元空间的GC
  3. 3515K->0K(265216K),这三个数字分别对应GC之前占用年轻代的大小,GC之后年轻代占用,以及整个年轻代的大小。
  4. 3564K->6681K(101376K),这三个数字分别对应GC之前占用老年代的大小,GC之后老年代占用,以及整个老年代的大小。
  5. 7080K->6681K(366592K),这三个数字分别对应GC之前占用堆内存的大小,GC之后堆内存占用,以及整个堆内存的大小。
  6. 20889K->20889K(1069056K),这三个数字分别对应GC之前占用元空间内存的大小,GC之后元空间内存占用,以及整个元空间内存的大小。
  7. 0.0408131是该时间点GC总耗费时间。

 从日志可以发现几次fullgc都是由于元空间不够导致的,所以我们可以将元空间调大点

java -jar -Xloggc:./gc-adjust-%t.log -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps  
-XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M 
microservice-eureka-server.jar

 调整完我们再看下gc日志发现已经没有因为元空间不够导致的fullgc了

对于CMS和G1收集器的日志会有一点不一样,也可以试着打印下对应的gc日志分析下,可以发现gc日志里面的gc步骤跟我们之前讲过的步骤是类似的

/**
 * 把堆内存设置小点 让堆内存溢出
 */
public class HeapTest {

    byte[] a = new byte[1024 * 100];  //100KB

    public static void main(String[] args) throws InterruptedException {
        ArrayList<HeapTest> heapTests = new ArrayList<>();
        while (true) {
            heapTests.add(new HeapTest());
            Thread.sleep(10);
        }
    }
}

CMS,运行时添加如下参数,gc日志放到d盘

-Xloggc:d:/gc-cms-%t.log -Xms50M -Xmx50M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps  
 -XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M 
 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC

 G1

-Xloggc:d:/gc-g1-%t.log -Xms50M -Xmx50M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+PrintGCDetails -XX:+PrintGCDateStamps    -XX:+PrintGCTimeStamps -XX:+PrintGCCause  -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -XX:+UseG1GC

上面的这些参数,能够帮我们查看分析GC的垃圾收集情况。但是如果GC日志很多很多,成千上万行。就算你一目十行,看完了,脑子也是一片空白。所以我们可以借助一些功能来帮助我们分析,这里推荐一个gceasy(https://gceasy.io),可以上传gc文件,然后他会利用可视化的界面来展现GC情况。具体下图所示 

上图我们可以看到年轻代,老年代,以及永久代的内存分配,和最大使用情况。

 

上图我们可以看到堆内存在GC之前和之后的变化,以及其他信息。 

这个工具还提供基于机器学习的JVM智能优化建议,当然现在这个功能需要付费

jvm参数汇总查看命令

java -XX:+PrintFlagsInitial #表示打印出所有参数选项的默认值
java -XX:+PrintFlagsFinal #表示打印出所有参数选项在运行程序时生效的值

三、常量池

1、Class常量池和运行时常量池

Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic References)。

一个class文件的16进制大体结构如下图:

 对应的含义,细节可以查下oracle官方文档

当然我们一般不会去人工解析这种16进制的字节码文件,我们一般可以通过javap命令生成更可读的JVM字节码指令文件:

javap -v HeapTest.class

 红框标出的就是class常量池信息,常量池中主要存放两大类常量:字面量和符号引用。

  • 字面量

字面量就是指由字母、数字等构成的字符串或者数值常量

字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=1 这里的a为左值,1为右值。在这个例子中1就是字面量。

int a = 1;
int b = 2;
int c = "abcdefg";
int d = "abcdefg";
  • 符号引用

符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量:

  1. 类和接口的全限定名 
  2. 字段的名称和描述符 
  3. 方法的名称和描述符

上面的a,b就是字段名称,就是一种符号引用,还有Math类常量池里的 Lcom/tuling/jvm/Math 是类的全限定名,main和compute是方法名称,()是一种UTF8格式的描述符,这些都是符号引用。

这些常量池现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装入内存就变成运行时常量池,对应的符号引用在程序加载或运行时会被转变为被加载到内存区域的代码的直接引用,也就是我们说的动态链接了。例如,compute()这个符号引用在运行时就会被转变为compute()方法具体代码在内存中的地址,主要通过对象头里的类型指针去转换直接引用。

2、字符串常量池

字符串常量池的设计思想

  1. 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能
  2. JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
  • 为字符串开辟一个字符串常量池,类似于缓存区
  • 创建字符串常量时,首先查询字符串常量池是否存在该字符串
  • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中

三种字符串操作(Jdk1.7 及以上版本)

  • 直接赋值字符串

String s = "rufeng"; // s指向常量池中的引用

这种方式创建的字符串对象,只会在常量池中。

因为有"rufeng"这个字面量,创建对象s的时候,JVM会先去常量池中通过 equals(key) 方法,判断是否有相同的对象

如果有,则直接返回该对象在常量池中的引用;

如果没有,则会在常量池中创建一个新对象,再返回引用。

  • new String();
String s1 = new String("rufeng"); // s1指向内存中的对象引用

这种方式会保证字符串常量池和堆中都有这个对象,没有就创建,最后返回堆内存中的对象引用。

步骤大致如下:

因为有"rufeng"这个字面量,所以会先检查字符串常量池中是否存在字符串"rufeng"

不存在,先在字符串常量池里创建一个字符串对象;再去内存中创建一个字符串对象"rufeng";

存在的话,就直接去堆内存中创建一个字符串对象"rufeng";

最后,将内存中的引用返回。

  • intern方法
String s1 = new String("rufeng");   
String s2 = s1.intern();

System.out.println(s1 == s2);  //false

String中的intern方法是一个 native 的方法,当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将intern返回的引用指向当前字符串 s1(jdk1.6版本需要将 s1 复制到字符串常量池里)。

字符串常量池位置

Jdk1.6及之前: 有永久代, 运行时常量池在永久代,运行时常量池包含字符串常量池

Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到堆里

Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在堆里

用一个程序证明下字符串常量池在哪里:

/**
 * jdk6:-Xms6M -Xmx6M -XX:PermSize=6M -XX:MaxPermSize=6M  
 * jdk8:-Xms6M -Xmx6M -XX:MetaspaceSize=6M -XX:MaxMetaspaceSize=6M
 */
public class RuntimeConstantPoolOOM{
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000000; i++) {
            String str = String.valueOf(i).intern();
            list.add(str);
        }
    }
}

运行结果:
jdk7及以上:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
jdk6:Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

字符串常量池设计原理

  字符串常量池底层是hotspot的C++实现的,底层类似一个 HashTable, 保存的本质上是字符串对象的引用。

看一道比较常见的面试题,下面的代码创建了多少个 String 对象?

String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
 
System.out.println(s1 == s2);
// 在 JDK 1.6 下输出是 false,创建了 6 个对象
// 在 JDK 1.7 及以上的版本输出是 true,创建了 5 个对象
// 当然我们这里没有考虑GC,但这些对象确实存在或存在过
示例一:
String s0="rufeng";
String s1="rufeng";
String s2="ru" + "feng";
System.out.println( s0==s1 ); //true
System.out.println( s0==s2 ); //true

分析:字符串"ru" + "feng"在编译时就被优化成"rufeng",s2只是指向常量池的引用,一共在字符串常量池创建了三个对象"ru","feng","rufeng"

示例二
String s0="rufeng";
String s1=new String("rufeng");
String s2="ru" + new String("feng");
System.out.println( s0==s1 );  // false
System.out.println( s0==s2 );  // false
System.out.println( s1==s2 );  // false

分析:s0是指向字符串常量池的引用,s1则是在堆中创建了新的对象,s2最后在字符串常量池中找到并指向字符串常量池"rufeng"对应的值,jdk1.7下一共创建6对象(s0在字符串常量池中,s1在堆中,s2的"ru","feng"在字符串常量池中,s2的new String("feng")在堆中,s2是字新创建了一个对象)

示例三
String a = "a1";
String b = "a" + 1;
System.out.println(a == b); // true 
  
String a = "atrue";
String b = "a" + "true";
System.out.println(a == b); // true 
  
String a = "a3.4";
String b = "a" + 3.4;
System.out.println(a == b); // true

分析:加号在编译时就已经优化连接成字符串了

示例四
String a = "ab";
String bb = "b";
String b = "a" + bb;

System.out.println(a == b); // false

分析:"a"+变量或者"a"+new String("xx"),编译时都不能优化

示例五
String a = "ab";
final String bb = "b";
String b = "a" + bb;

System.out.println(a == b); // true

分析:final定义的字符串不可被修改,编译时可以解析成一个常量,都是指向字符串常量池的引用,所以是相等的

示例六
String a = "ab";
final String bb = getBB();
String b = "a" + bb;

System.out.println(a == b); // false

private static String getBB() 
{  
    return "b";  
 }

分析:getBB()方法在编译时无法确定返回何值,只有真的在运行时调用方法getBB()才会给"b"分配内存空间,重新创建一个新的String

示例七
//字符串常量池:"计算机"和"技术"     堆内存:str1引用的对象"计算机技术"  
//堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
String str2 = new StringBuilder("计算机").append("技术").toString();   //没有出现"计算机技术"字面量,所以不会在常量池里生成"计算机技术"对象
System.out.println(str2 == str2.intern());  //true
//"计算机技术" 在池中没有,但是在heap中存在,则intern时,会直接返回该heap中的引用

//字符串常量池:"ja"和"va"     堆内存:str1引用的对象"java"  
//堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
String str1 = new StringBuilder("ja").append("va").toString();    //没有出现"java"字面量,所以不会在常量池里生成"java"对象
System.out.println(str1 == str1.intern());  //false
//java是关键字,在JVM初始化的相关类里肯定早就放进字符串常量池了

String s1=new String("test");  
System.out.println(s1==s1.intern());   //false
//"test"作为字面量,放入了池中,而new时s1指向的是heap中新生成的string对象,s1.intern()指向的是"test"字面量之前在池中生成的字符串对象

String s2=new StringBuilder("abc").toString();
System.out.println(s2==s2.intern());  //false
//同上

3、八种基本类型的包装类和对象池

java中基本类型的包装类的大部分都实现了常量池技术(严格来说应该叫对象池,在堆上),这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象。因为一般这种比较小的数用到的概率相对较大。

public class Test {

    public static void main(String[] args) {
        //5种整形的包装类Byte,Short,Integer,Long,Character的对象,  
        //在值小于127时可以使用对象池  
        Integer i1 = 127;  //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
        Integer i2 = 127;
        System.out.println(i1 == i2);//输出true  

        //值大于127时,不会从对象池中取对象  
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);//输出false  
        
        //用new关键词新生成对象不会使用对象池
        Integer i5 = new Integer(127);  
        Integer i6 = new Integer(127);
        System.out.println(i5 == i6);//输出false 

        //Boolean类也实现了对象池技术  
        Boolean bool1 = true;
        Boolean bool2 = true;
        System.out.println(bool1 == bool2);//输出true  

        //浮点类型的包装类没有实现对象池技术  
        Double d1 = 1.0;
        Double d2 = 1.0;
        System.out.println(d1 == d2);//输出false  
    }
}

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

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

相关文章

Google Play针对恶意软件采取最新反制措施!

近日&#xff0c;谷歌开始针对Google Play上不断入侵的恶意软件采取反制措施&#xff0c;要求所有以机构名义注册的新开发者账户在提交应用程序之前提供一个有效的D-U-N-S号码。 这项新措施能有效提高平台的安全性和可信度&#xff0c;同时也能够有效遏制新账户提交恶意软件的…

kubernetes中特定域名使用自定义DNS服务器出现的解析异常

故障发生背景&#xff1a; 租户反馈生产业务服务连接到中间件的时候&#xff0c;偶尔会有连接失败的情况&#xff0c;然后我们查看对应组件服务正常&#xff0c;手动请求组件服务也显示正常&#xff0c;让租户查看业务服务日志发现报错无法解析对应的域名&#xff0c;我们手动是…

数据库的分片策略

数据库的分片策略 1、范围分片2、hash 取模分片3、一致性hash 分片 1.分片策略 数据库的分片策略是指将数据库中的数据按照一定的规则和方式进行分割&#xff08;分片&#xff09;存储在不同的物理节点或服务器上的策略。分片策略旨在实现水平扩展&#xff0c;提高数据库的性…

暑假第九天打卡

英语&#xff1a; 新东方六级一单元单词刷题复习 离散&#xff1a; 例12 使用消解算法判断下述公式是否是可满足的: 公式 S p∧(p∨q)∧(p∨q)∧(q∨r)∧(q∨r) 解&#xff1a; S p∧(p∨q)∧(p∨q)∧(q∨r)∧(q∨r) //化为主合取式 循环1&#xff1a; S0 S1 {p…

Windows 进程和作业

Windows 进程和作业 创建进程CreateProcess 函数的参数CreateProcess 的流程 创建Windows“现代化”进程创建其他类型的进程 进程的内部构造EPROCESSKPROCESSPEBCSR_PROCESSW32PROCESS 受保护进程最小进程和 Pico 进程最小进程Pico进程 Trustlet 安全进程进程的终止作业作业的限…

合并完之后,进行回退

我是将分支合并到了uat_v3上&#xff0c;现在又要求将uat_v3上的代码回退到合并以前&#xff1b; 我是将origin/uat_v3 checkout 除一份本地uat_v3,然后选中合并以前的commit记录&#xff0c;新建分支 temp/reverse 分支&#xff1b; 这样我本地的temp/reverse分支就已经是合…

[Java进阶] Swing两万字大总结一(超详细教程,这不得收藏一波)

&#x1f525;一个人走得远了&#xff0c;就会忘记自己为了什么而出发&#xff0c;希望你可以不忘初心&#xff0c;不要随波逐流&#xff0c;一直走下去&#x1f3b6; &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e; &#x1f984; …

将类模板做为参数(类模板中使用)

将类模板做为参数&#xff08;类模板中使用&#xff09; 这种方式只在类模板中使用&#xff0c;函数模板不能使用这种方法。 将模板名做为一种特殊的数据类型&#xff0c;实例化对象的时候&#xff0c;使用模板名做为参数&#xff0c;传递给模板。 下面例子中&#xff0c;数组…

Unity 上传文件到阿里云 对象存储OSS服务器

首先登录阿里云 免费试用–对象存储OSS --点击立即试用&#xff0c;可以有三个月的免费试用 创建Buket 新建AccessKey ,新建完成后&#xff0c;会有一个CSV文件&#xff0c;下载下来&#xff0c;里面有Key &#xff0c;代码中需要用到 下载SDK 双击打开 sln文件&#xff0…

Java初识,继承

文章目录 环境变量配置的意义javapath与classpath伪随机数的生成RandmomMathThreadLocalRandomSecureRandom javamain函数Scanner 函数打印函数 注释Java中的注释主要分为以下三种 Stringboolean equals(Object anObject)int compareTo(String s) 数组数组的遍历数组名数组参数…

第八章:SegNet——一个用于强大的语义像素级标注的深度卷积编码-解码架构

0.摘要 我们提出了一种新颖的深度架构SegNet&#xff0c;用于语义像素级图像标注。SegNet具有一些吸引人的特性&#xff1a; (i)它只需要对完全学习的函数进行前向评估&#xff0c;就可以获得平滑的标签预测&#xff1b; (ii)随着深度增加&#xff0c;像素标注考虑了更大的上下…

网络安全中黑客的问题,黑客真的那么厉害吗?

前言 黑客这个名字一直是伴随着互联网发展而来&#xff0c;给大家的第一印象就是很酷&#xff0c;而且技术精湛&#xff0c;在网络世界里无所不能。目前几乎所有的公司企业甚至国家相关部门都会争相高薪聘请技术精湛的黑客作为互联网机构的安全卫士&#xff0c;所以黑客也是很…

超市商品信息管理系统设计与实现(论文+源码)

超市商品信息管理系统设计与实现(论文源码) 本篇 论文源码私我 以上内容只是精简版 还有很多原创类型论文 摘 要 本次主要先介绍研究背景、研究目标及相应价值的基础上&#xff0c;分析了国内外电子商务及相应超市管理系统的研究现状。随着计算机技术和网络技术的发展&#xf…

【力扣】543. 二叉树的直径

543. 二叉树的直径 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。两节点之间路径的 长度 由它们之间边数表示。 示例 1&#xff1a; 输入&#xff1a;root…

高等数学❤️第一章~第二节~极限❤️极限的概念与性质~函数极限(自变量趋于无穷大时的极限)详解

【精讲】高等数学中函数极限&#xff1a;自变量趋于无穷大时的极限 博主&#xff1a;命运之光的主页 专栏&#xff1a;高等数学 目录 【精讲】高等数学中函数极限&#xff1a;自变量趋于无穷大时的极限 导言 一、函数极限自变量趋于无穷大的概念 二、函数极限自变量趋于无穷…

【力扣刷题 | 第十九天】

目录 前言&#xff1a; 135. 分发糖果 - 力扣&#xff08;LeetCode&#xff09; 860. 柠檬水找零 - 力扣&#xff08;LeetCode&#xff09; 总结&#xff1a; 前言&#xff1a; 今天着重刷贪心算法的题目 135. 分发糖果 - 力扣&#xff08;LeetCode&#xff09; n 个孩子…

Deepin/UOS Linux 桌面自定义 IDEA/DataGrip 应用程序图标

在 $HOME/Desktop目录下编辑 vim jetbrains.intelij.idea.desktop [Desktop Entry] TypeApplication NameIntelij IDEA Icon/opt/module/idea-IU-203.8084.24/bin/idea.png Exec/opt/module/idea-IU-203.8084.24/bin/idea.sh Terminalfalse CategoriesDevelopment;IDE;vim je…

力扣“找出数组排序后的目标下标”:一种简洁高效的算法

本篇博客会讲解力扣“2089. 找出数组排序后的目标下标”的解题思路&#xff0c;这是题目链接。 本题的解题思路如下&#xff1a;首先&#xff0c;利用qsort函数对原数组进行升序排序&#xff0c;然后&#xff0c;根据目标值在排序后的数组中查找对应的下标&#xff0c;并将其存…

Pixi + Tone 实现简单midi音频可视化

依赖库 Pixi.js 是一个前端图形渲染库&#xff0c;使用精灵技术绘制高性能的图形。Tone.js是一个前端音频框架&#xff0c;对web audio api进行了封装&#xff0c;可以快速创建音频样本、音频效果、进行音频分析和音频播放。tonejs/midi是tonejs的一个插件&#xff0c;可以讲m…

Iterator-Generator详解

1 迭代器可迭代对象 2 原生的迭代器对象 3 自定义类的迭代器 4 生成器理解和作用 5 自定义生成器方案 6 异步处理方案解析 迭代器-JavaScript中迭代器&#xff08;了解&#xff09; 给某个数组专门添加一个迭代器的代码。 const names ["abc", "cba"…