Java GC机制 —— 个人笔记

文章目录

    • JVM内存区
    • 对象是否需要回收?
      • 1. 引用计数法
      • 2. 可达性分析法(根搜索算法)
        • Java的引用
    • 对象何时被回收?
    • 回收策略
      • 回收策略1:引用计数算法
      • 回收策略2:标记清除算法(Mark-Sweep)
      • 回收策略3:标记整理算法(Mark-Mark-Compact)
      • 回收策略4:复制算法(Copying)
      • 回收策略5:分待回收机制
        • 内存区分配
        • 为何还要对新生代进行划分?
        • 为啥Survivor区需要两个?
    • 垃圾收集器
      • 新生代:Serial
      • 新生代:ParNew
      • 新生代:Parallel Scavenge
      • 老年代:Serial Old
      • 老年代:Parallel Old
      • CMS收集器
      • 混合:G1
    • 复习

文章源于学习若干个博客过程中整合,图源于图文并茂,万字详解,带你掌握 JVM 垃圾回收!

7种垃圾回收器特点,优劣及使用场景

GC garbage collection 垃圾回收

C/C++中,对象的申请和释放都需要程序员自己进行操作,然而程序员如果忘记对内存空间进行正确的释放,可能导致内存泄漏。

为避免此类情况发生,GC机制可以让程序员开发过程中更专注程序应用本身,而无需考虑内存泄露问题。

  • GC自动检测对象是否超过作用域,自动回收内存
  • 垃圾收集器自动管理

JVM内存区

JVM一共有五个区域: Method Area, VM stack, Native Mathod Stack, Heap, Program Counter Register

其中GC回收的区域为:

  1. 堆区
  2. 方法区

GC回收区域的共同点为:线程共享

对象是否需要回收?

对象无引用,或不可达

判断方法

1. 引用计数法

static class Test{public Test instance;
}public void run() {Test t1 = new Test();Test t2 = new Test();t1.instance = t2; t2.instance = t1;// t1和t2相互引用
}

如果我们执行run()方法后,虽然t1和t2不会再被访问,但由于t1, t2相互引用对方,引用计数器不为0,无法对他们进行回收。所以,市面上主流的Java虚拟机都没有使用这个算法,而是使用可达性分析法

2. 可达性分析法(根搜索算法)

通过 GC Roots根对象来作为起始节点,从这些节点开始,根据引用关系向下搜索,搜索过程的就是一条引用链(Reference Chain)。不在这个链里面的对象,就认为是 可回收的

在这里插入图片描述
哪些对象可以作为GC Roots?

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象,参数,局部变量,临时变量
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈JNI引用对象

虚拟机栈引用的对象

当运行某个函数的时候,JVM就会为这个函数在栈区开辟内存,如果运行main函数,那么JVM为main函数的局部变量在栈区开辟内存

public class Test {public static void main(String[] args) {Test a = new Test();//a是栈中局部变量a = null; //a为null的时候,他和原本的new Test()断开连接//对象回收}
}

方法区中类静态属性引用的对象

方法区本身用于存放类的信息:名称,父类,接口,变量等

public class Test {public static Test s; //s为类静态属性引用的对象public static void main(String[] args) {Test a = new Test();a.s = new Test();a = null;}
}

a = null 时,由于 a 原来指向的对象与 GC Root (变量 a) 断开了连接,所以 a 原来指向的对象会被回收。
然而s是类静态属性,且被赋值引用,被认为是GC Root,s依然 可达

方法区中常量引用的对象


public class Test {public static final Test s = new Test();public static void main(String[] args) {Test a = new Test();a = null;}

同上,a对象被回收不会影响到常量s指向的对象

本地方法栈中 JNI(Java Native Interface引用) 的对象

  • 一个 java 调用非 java 代码的接口
  • 可能由 C 或 Python等其他语言实现的
  • 通过 JNI 来调用本地方法, 而本地方法是以库文件的形式存放的(Windows为dll, unix上为so文件)

区别?

Java方法本地方法
虚拟机会创建一个栈桢并压入 Java 栈虚拟机会保持 Java 栈不变,不会在 Java 栈祯中压入新的祯,虚拟机只是简单地动态连接并直接调用指定的本地方法。
Java的引用

强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)

//强引用
String s = new String("hello");
System.gc();
System.out.println(s);

和前文一样,强引用存在,垃圾收集器永远不会回收被引用的对象,只有当引用被设为null的时候,对象才会被回收。如果赋值给static变量,对象很长一段时间都不会被回收

//软引用
String s = new String("hello");
//强引用添加到软引用
SoftReference<String> softReference = new SoftReference<>(s);
s=null;
//执行垃圾回收
System.gc();
//再次获取
if(softReference !=null ){System.out.println(softReference.get());
}

GC过程中,如果内存充足,软引用对象不会被释放

//弱引用
WeakReference<String> weakReference = new WeakReference<>(new String("hello"));//执行垃圾回收System.out.println("执行垃圾回收之前");System.out.println(weakReference.get());System.gc();System.out.println("执行垃圾回收之后:");System.out.println(weakReference.get());

只要系统执行完垃圾回收,无论内存是否足够,弱引用变量指向的对象都会被回收

ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
PhantomReference<String> phantomReference = new PhantomReference<>(new String("hello"), referenceQueue);
System.gc();
System.out.println(referenceQueue.remove().get());

无论内存是否足够,弱引用变量指向的对象都会被回收,和虚引用的区别在于,无法通过虚引用来取得一个对象实例,虚引用也不会对生成他的对象生存时间产生影响。

作用:对象被收集器回收的时候得到系统通知

对象何时被回收?

  • 自动调用的情况:

    • 程序创建新对象/基本类型数据
    • 内存空间不足
  • 手动调用的情况

    • System.gc()

回收策略

回收策略1:引用计数算法

  • 发现垃圾时,立即回收。

  • 最大限度减少程序暂停,因为发现后立即回收,减少了程序因内存爆满而被迫停止的现象

  • 时间开销大,因为引用计数算法需要时刻监控引用计数器的变化。

  • 无法回收循环引用的对象

回收策略2:标记清除算法(Mark-Sweep)

未引用对象并不会被立即回收,垃圾对象将一直累计到内存耗尽为止,当内存耗尽时,程序将会被挂起,垃圾回收开始执行

  1. 遍历所有对象,标记所有的可达对象
  2. 会遍历所有对象,清除没有标记的对象

优点:简单,快速
缺点:标记和清除过程效率不高 —— 之后会产生大量不连续的内存碎片,提高触发另一次垃圾收集动作的概率
在这里插入图片描述

回收策略3:标记整理算法(Mark-Mark-Compact)

  1. 标记过程仍然与“标记-清除”算法一样

  2. 第二阶段不对可回收对象进行清理,而让所有存活的对象都向一端移动

在这里插入图片描述

回收策略4:复制算法(Copying)

改进了标记-清除算法的效率

内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
在这里插入图片描述
优点

  1. 不用考虑内存碎片
  2. 简单高效

缺点

  1. 内存缩小一半

回收策略5:分待回收机制

于是我们结合前面的2,3,4 三种策略,得到了当前最流行的分带回收机制算法

  • 新生代:复制算法
  • 老年代:标记-清除算法、标记-整理算法
弱分代假说:绝大多数对象都是朝生夕死的。
强分代假说:熬过越多次的垃圾回收的对象,就越难消亡

绝大多数对象在前几次GC过程中回收,经过多次GC过程未被回收的对象很难消亡。

于是把对象分为 新生代老年代。然后分配到不同的区域后,执行不同的策略。比例一般为1:2

新生代老年代
大部分对象会被回收难被回收
频繁使用可达性分析法较少频率回收
存活对象被复制到幸存者区域后被释放

详细说明

新生代

  1. 所有new的对象先出现在新生代中
  2. 新生代内存满了触发一次GC事件(Minor garbage collection)
  3. 无法回收的对象移动到老年代
  4. Minor garbage collection为全局暂停事件,垃圾回收过程结束其他线程才可运行

老年代

  1. 老年代被占满时也会触发GC时间(Major garbage collection)
  2. 也需要全局暂停
  3. 涉及所有存活对象,更慢。受老年代垃圾回收器影响

除此之外,还有一个永久代(在非heap内存)

  1. 存储了描述应用程序类和方法的源数据
  2. JVM 在运行时基于应用程序所使用的类产生
  3. 类不在被其他类所需要,同时其他类需要更多的空间的时候才会被回收(此回收过程为Full garbage collection)
    在这里插入图片描述
内存区分配

前面说过,新生代(Young generation)、老年代(Old generation)所占空间比例为 1 : 2,其中新生代还会被分为三个空间

  • 1个伊甸园空间(Eden)
  • 2个幸存者空间(Fron Survivor、To Survivor)

默认情况下,新生代空间的分配:Eden : Fron : To = 8 : 1 : 1
在这里插入图片描述

为何还要对新生代进行划分?

假设我们没有survivor

  1. Eden区每进行一次Minor GC,存活的对象就会被送到老年代。
  2. 老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)
  3. 老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多

如果不划分新生代区,有无其他方法避免上述情况?

方案优点缺点
增加老年代空间更多存活对象才能填满老年代。降低Full GC频率随着老年代空间加大,一旦发生Full GC,执行所需要的时间更长
减少老年代空间Full GC所需时间减少老年代很快被存活对象填满,Full GC频率增加

上述两种解决方案都不能从根本上解决问题

Survivor:就是减少被送到老年代的对象,进而减少Full GC的发生。Survivor的预筛选保证:经历16次Minor GC还能存活的对象,才会被送到老年代。

为啥Survivor区需要两个?

若只有一个Survivor区,会出现以下情况

  1. Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区,此时Eden和Survivor各有一些存活对象
  2. Minor GC到底一定次数后,Survivor区会把部分数据存到Old区,eden也会把数据存放到Survivor区
  3. 但是Eden存到Survivor的数据不一定能刚好存入Survivor空缺部分的数据,导致内存碎片化

在这里插入图片描述
应该建立两块Survivor区

  1. 刚刚新建的对象在Eden中
  2. 经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;
  3. 等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制到survivor space S1

在这里插入图片描述

垃圾收集器

在这里插入图片描述
新生代收集器

  • Serial
  • ParNew
  • parallel
    老年代收集器
  • Serial Old
  • CMS
  • Parallel Old
    新生代和老年代收集器
    G1

常用组合
在这里插入图片描述

新生代:Serial

  • 单线程收集器
  • 使用**“复制”**算法
  • GC过程中,所有线程必须暂停
    在这里插入图片描述
    特点
  1. 简单,无上下文切换,效率高
  2. 用户体验较差,其不知情情况下停止所有线程
  3. 适用场景:Client 模式(桌面应用);单核服务器。
  4. 使用命令如下开启Serial作为新生代收集器
-XX:+UserSerialGC #选择Serial作为新生代垃圾收集器

新生代:ParNew

serial的一个多线程版本
多核机器上,其默认开启的收集线程数与cpu数量相等。可以通过如下命令进行修改

-XX:ParallelGCThreads #设置JVM垃圾收集的线程数  

在这里插入图片描述

当用户线程都执行到安全点时,所有线程暂停执行,采用复制算法进行垃圾收集工作

特点

  • 有效利用CPU
  • 用户体验较差,其不知情情况下停止所有线程
  • Server模式常用,因为CMS收集器只能和serial或者parNew联合使用。可以使用如下命令进行强制指定
-XX:UseParNewGC #新生代采用ParNew收集器  

新生代:Parallel Scavenge

  • 复制算法
  • 对比parNew,PS收集器关注缩短GC停顿时间,从而达到可控的吞吐量

吞吐量 = 运行用户代码时间 运行用户代码时间 + 垃圾收集时间 吞吐量=\frac{运行用户代码时间}{运行用户代码时间+垃圾收集时间} 吞吐量=运行用户代码时间+垃圾收集时间运行用户代码时间

例如虚拟机一共运行了 100 分钟,其中垃圾收集花费了 1 分钟,那吞吐量就是 99%

垃圾收集器每 100 秒收集一次,每次停顿 10 秒,和垃圾收集器每 50 秒收集一次,每次停顿时间 7 秒,虽然后者每次停顿时间变短了,但是总体吞吐量变低了,CPU 总体利用率变低了。
在这里插入图片描述

特点

  • 高吞吐量,高CPU利用率,可控
  • 使用高吞吐量高CPU利用率场景,如高速运算,少量交互的情况

老年代:Serial Old

Serial Old是Serial收集器的老年代版本
标记-整理算法

适用场景

  • Client模式;
  • 单核服务器;
  • 与Parallel Scavenge收集器搭配;
  • 作为CMS收集器的后备方案,在并发收集发生Concurrent Mode Failure时使用

老年代:Parallel Old

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法

在这里插入图片描述
一般搭配Parallel Scavenge 收集器

参数指定

-XX:+UserParallelOldGC

CMS收集器

获取最短回收停顿时间为目标的收集器。采用的算法是“标记-清除

  1. 标记GC Roots 能够直接关联到达对象
  2. 并发标记,进行GC Roots Tracing 的过程
  3. 重新标记,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
  4. 标记清除算法清除对象
    在这里插入图片描述
    特点
  • 并发收集,低停顿
  • CPU资源非常敏感,默认回收线程数(CPU数量+3)/4,CPU数量不足4个对用户影响较大
  • 无法处理浮动垃圾
    • 可能会出现“Concurrent Mode Failure”失败而导致一次FullGC的产生
    • 此时用SerialOld来重新进行老年代GC
    • CMS并发清理阶段用户线程还在运行,伴随程序运行自然还会有新的垃圾产生,所以只能等待下一次GC清楚,无法填满后GC
    • -XX:CMSInitiatingOccupancyFraction修改CMS触发的百分比
  • 空间碎片

混合:G1

面向服务端应用的垃圾收集器,目前是JDK9的默认垃圾收集器

  • 并行并发
  • 分代收集。能够采用不同的方式去处理新创建的对象和已经存活了一段时间的对象
  • 整体上来看基于“标记-整理”,部上看是基于复制算法,不产生空间碎片
  • 可预测的停顿

Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但两者之间不是物理隔离的。他们都是一部分Region的集合

在这里插入图片描述
一个方块对应一个区域,他的身份不定,每种身份的数量也不一定

JVM启动设置每个区为2的次幂大小,最多为2048个区域( 2048 × 32 M = 64 G 2048\times 32M=64G 2048×32M=64G)假如设置 -Xmx8g -Xms8g,则每个区域大小为 8g/2048=4M。

作用

  • 避免在整个Java堆全区域GC
    • 跟踪各个区的价值大小(回收空间大小,所需时间)得到优先队列
    • 每个区域都有Remembered Set 来实时记录 引用类型数据和其他区域数据的关系

在这里插入图片描述

  1. 初始阶段:标记处GC直接关联对象(需要停止线程,单线程执行)
  2. 并发阶段:GC roots开始对heap对象进行可达分析,和用户线程并行
  3. 最终标记:修正并行过程中变动的记录
  4. 筛选回收:对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划 —— 最少的时间回收垃圾最多的区域,这里需要停止用户线程

复习

  1. JVM的五大内存区是?
  2. 判定是否需要回收某个对象的方法有哪些?优缺点是什么?
  3. 为什么市面上多采用可达性分析方法
  4. 可达性分析方法如何判断GC Root
  5. 四种引用的区别是什么
  6. 对象什么时候会被回收?主动回收和被动回收的区别?
  7. 回收策略有哪些?
  8. 标记清除算法和标记整理算法的区别?
  9. 分带回收机制和Copying机制主要区别在哪?
  10. 这些回收策略的优缺点是什么?
  11. 新生代,老年代如何划分?
  12. 新生代如何变成老年代?
  13. Minor, Major, Full GC的区别
  14. 为何Survivor区设置为两个?少一个会如何?
  15. 为啥主流的回收策略是分带回收机制?
  16. 常见的垃圾收集器有哪些?一般他们如何搭配使用?

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深眸科技聚焦3D机器视觉技术,从技术形态到应用前景实现详细分析

机器视觉技术的不断升级&#xff0c;使得对二维图像的处理逐渐扩展到了更复杂的三维领域&#xff0c;形成了3D机器视觉。3D机器视觉是机器视觉的重要应用领域之一&#xff0c;通过计算机能够在短时间内处理视觉传感器采集的图像信号&#xff0c;从而获得目标对象的三维信息。 …

鸿蒙开发工具的汉化

1、下载汉化包 汉化插件下载地址&#xff1a;Chinese (Simplified) Language Pack / 中文语言包 - IntelliJ IDEs Plugin | Marketplace 百度网盘下载地址&#xff1a;链接&#xff1a;百度网盘 请输入提取码 DevEco Studio是基于IDEA223版本&#xff0c;下载汉化包时请注意…

ubuntu| sudo apt-get update 更新失败, 没有 Release 文件 无法安全地用该源进行更新,所以默认禁用该源

xiaoleubt:~$ sudo apt-get update -y 命中:1 https://dl.google.com/linux/chrome/deb stable InRelease 忽略:2 http://ppa.launchpad.net/ubuntu-desktop/ubuntu-make/ubuntu focal InRelease 命中:3 https://packages.microsoft.com/repos/code stable InRelease 命中:4 ht…

GPT出现了Something went wrong.

网络上的一种说法如下

四种常见分布式限流算法实现!

转载&#xff1a;四种常见分布式限流算法实现&#xff01; - 知乎 大家好&#xff0c;我是老三&#xff0c;最近公司在搞年终大促&#xff0c;随着各种营销活动“组合拳”打出&#xff0c;进站流量时不时会有一个小波峰&#xff0c;一般情况下&#xff0c;当然是流量越多越好&…

Verilog使用vscode

使用vscode打开.v文件 Tools setting texteditor vscode文件路径 [line number]:[file name] &#xff08;可能会出错&#xff0c;可以去vscode确认打开的文件路径&#xff0c;后经调整后改为 vscode文件路径 [file name]&#xff09; 安装插件 搜索Verilog 添加使用最多的 …

chatglm3-6b部署及微调

chatglm3-6b部署及微调 modelscope: https://modelscope.cn/models/ZhipuAI/chatglm3-6b/filesgithub: https://github.com/THUDM/ChatGLM3镜像: ubuntu20.04-cuda11.7.1-py38-torch2.0.1-tf1.15.5-1.8.1v100 16G现存 单卡 安装 软件依赖 # 非必要无需执行 # pip install -…

C语言 每日一题 11.9 day15

数组元素循环右移问题 一个数组A中存有N&#xff08; > 0&#xff09;个整数&#xff0c;在不允许使用另外数组的前提下&#xff0c;将每个整数循环向右移M&#xff08;≥0&#xff09;个位置&#xff0c;即将A中的数据由&#xff08;A0​A1⋯AN−1&#xff09;变换为&…

网工内推 | 上市公司,云平台运维,IP认证优先,13薪

01 上海新炬网络信息技术股份有限公司 招聘岗位&#xff1a;云平台运维工程师 职责描述&#xff1a; 1、负责云平台运维&#xff0c;包括例行巡检、版本发布、问题及故障处理、平台重保等&#xff0c;保障平台全年稳定运行&#xff1b; 2、参与制定运维标准规范与流程&#x…

RabbitMQ 系列教程

一、RabbitMQ 部署及配置详解(集群部署) 二、RabbitMQ 部署及配置详解 (单机) 三、RabbitMQ 详解及实例&#xff08;含错误信息处理&#xff09; 四、RabbitMq死信队列及其处理方案 五、RabbitMQ Java开发教程—官方原版 六、RabbitMQ Java开发教程&#xff08;二&#x…

用POST请求在Linux之间传输文件(Python在Linux间传输文件)

背景 实际需求&#xff1a; 已通过iperf和dd命令测试过两台不同区域之间的Linux服务器带宽&#xff0c;均为1000Mb网络。但发送post请求传输文件至对象存储时&#xff0c;总是卡在14Mb/s。除了排查区域之间的防火墙&#xff0c;也应该尝试检查Linux&#xff08;KylinV10&…

linux的美化工具 oh-my-zsh的安装与使用 神器工具

目录 1 安装zsh的环境2 安装 Oh My Zsh3 主题设置重新启动终端:关闭连接 在重新链接一下附加 -插件管理案例讲解看效果 Oh My Zsh 是一款基于 Zsh 的开源命令行工具&#xff0c;它提供了丰富的主题和插件&#xff0c;可以帮助用户更加高效地使用终端。本文将详细介绍 Oh My Zsh…

Java 设计模式——访问者模式

目录 1.概述2.结构3.案例实现3.1.抽象访问者类3.2.抽象元素类3.3.具体元素类3.4.具体访问者类3.5.对象结构类3.6.测试 4.优缺点5.使用场景6.扩展6.1.分派6.2.动态分配6.3.静态分配6.4.双分派 1.概述 访问者模式 (Visitor Pattern) 是一种行为型设计模式&#xff0c;它用于将数…

springboot本地启动多个模块报错:Address already in use: JVM_Bind

目录 背景解决方法 背景 环境&#xff1a; jdk1.8 idea 2019.2.4idea本地启动多个模块联调时&#xff0c;提示报错&#xff1a; 错误: 代理抛出异常错误: java.rmi.server.ExportException: Port already in use: 9090; nested exception is: java.net.BindException: Addre…

vue+java实现语音转文字思路

思路&#xff1a; 前端录音生成wav文件后端去解析 技术&#xff1a; 后端&#xff1a; Vosk是一个离线开源语音识别工具。它可以识别16种语言&#xff0c;包括中文。 API接口&#xff0c;让您可以只用几行代码&#xff0c;即可迅速免费调用、体验功能。 目前支持 WAV声音文件…

基于8086家具门安全控制系统设计

**单片机设计介绍&#xff0c;基于8086家具门安全控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 # 8086家具门安全控制系统设计介绍 8086家具门安全控制系统是一种用于保护家具和保证室内安全的系统。该系统基于808…

小程序游戏对接广告收益微信小游戏抖音游戏软件

小程序游戏对接广告是一种常见的游戏开发模式&#xff0c;开发者可以通过在游戏中嵌入广告来获取收益。以下是一些与小程序游戏对接广告收益相关的关键信息&#xff1a; 小程序游戏广告平台选择&#xff1a; 选择适合你的小程序游戏的广告平台非常重要。不同的平台提供不同类型…

ubuntu18-recvfrom接收不到广播报文异常分析

目录 前言 一、UDP广播接收程序 二、异常原因分析 总结 前言 在ubuntu18.04系统中&#xff0c;编写udp接收程序发现接收不到广播报文&#xff0c;使用抓包工具tcpdump可以抓取到广播报文&#xff0c;在此对该现象分析解析如下文所示。 一、UDP广播接收程序 UDP广播接收程序如…

【解决方案】vue 项目 npm run dev 时报错:‘cross-env‘ 不是内部或外部命令,也不是可运行的程序

报错 cross-env 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! estate1.0.0 dev: cross-env webpack-dev-server --inline --progress --config build/webpack.dev.conf.js npm ERR! Exit status 1 np…

Go语言用Colly库编写的图像爬虫程序

下面是一个使用Colly库编写的Go语言图像爬虫程序&#xff0c;该程序会爬取news.qq上的图片&#xff0c;并使用proxy_host:duoip和proxy_port:8000的爬虫IP服务器进行抓取。 package mainimport ("fmt""net/http""github.com/crawlab-collective/go-co…