jvm高级特性(4)(内存分配回收策略)
一. 内存分配 和 回收策略
1,对象内存分配的概念:
往大方向讲,它就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),
- 对象主要分配在新生代的Eden区上,如果启动了局部线程分配缓冲,将按线程优先在TLAB上分配。
- 少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,
- 其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存有关的参数设置。
补充:线程TLAB局部缓存区域(Thread Local Allocation Buffer)
2,Java堆内存
3,两个重要的概念
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭(即生命周期特别短)的特征,所以MinorGC非常频繁,一般回收速度也比较快。
老年代GC(Major GC / Full GC):指发生在老年代的GC,
出现了Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。
老年代GC 的速度一般比 新生代GC慢10倍以上。
4,代码实战设置环境条件
下面代码测试是在Client模式虚拟机运行,未特意指定收集器组合,也就是说验证的是 Serial/ Serial Old收集器
(ParNew / Serial Old收集器组合的规则也基本一致)下的内存分配和回收策略。
以下代码测试都将加上了以下参数:
-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
以上设置意味着将Java堆内存大小限制设置为20M,由于新生代和老年代各占一半,所以新生代占10M内存。
Eden区和Survivor区的比例是8,在新生代中由一块Eden区和两块大小相等的Survivor区组成,所以Eden区内存为8M,每个Survivor区大小为1M。
补充:
XX:NewRatio=4:表示年老代与年轻代的比值为4:1
XX:SurvivorRatio的解释是:Eden区与Survivor区的大小比值,
二. 五大策略
1 对象优先在 Eden 分配
对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次新生代GC(Minor GC)。
代码实践与日志展示
【新生代 Minor GC】
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
*/
public static void testAllocation() {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB]; // 出现一次Minor GC
运行结果:
[GC [DefNew: 6487K->152K(9216K), 0.0040116 secs] 6487K->6296K(19456K), 0.0040436 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4576K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, % used [0x32750000, 0x32ba1fa8, 0x32f50000)
from space 1024K, % used [0x33050000, 0x33076150, 0x33150000)
to space 1024K, % used [0x32f50000, 0x32f50000, 0x33050000)
tenured generation total 10240K, used 6144K [0x33150000, 0x33b50000, 0x33b50000)
the space 10240K, % used [0x33150000, 0x33750030, 0x33750200, 0x33b50000)
compacting perm gen total 12288K, used 376K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, % used [0x33b50000, 0x33bae2c0, 0x33bae400, 0x34750000)
ro space 10240K, % used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000)
rw space 12288K, % used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000)
补充:
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,(即方法区)
read-only space,只读空间;read-write space 可读写空间
[GC [DefNew: 6487K->152K(9216K), 0.0040116 secs] 6487K->6296K(19456K), 0.0040436 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 6487K->152K:新生代使用的内存,gc后从6487变为152
(9216k) : 新生代总大小 (只是伊甸园区+1个生存区(只算from区,不算to区))
6487K->6296K:堆内存使用的内存,gc后变化不大;(对象从新生代到老年代)
(19456k) : 堆的大小(新生代+老年代)
结果分析:
- 在
testAllocation()
方法中,尝试分配3个2MB大小和1个4MB大小的对象。 - 从输出结果中可以发现“eden space 8192K、from space 1024K、to space 1024K”的信息,新生代总可用空间为9216KB(Eden区 + 1个 Survivor区的总容量)。
- 执行
testAllocation()
方法中的分配 allocation4 对象的语句会发生一次 Minor GC,这次GC的结果是新生代6651KB变为148KB,而总内存占用量则几乎没有减少, - 因为allocation1 、allocation2 、allocation3 三个对象都是存活的,虚拟机几乎没有找到可回收的对象。
这次GC发生的原因:
- 给allocation4 分配内存时,发现Eden区 已经被占用了6MB,剩余空间已不足以分配allocation4 所需的4MB内存,因此发生了 Minor GC。
- GC期间虚拟机又发现已有的3个2MB大小的对象全部无法放入 Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。
- 此次GC结束后,4MB的 allocation4 对象顺利分配在 Eden中,因此程序执行完后的结果是 Eden区占用4MB(被allocation4占用 ),
- Survivor空间处于空闲状态,老年代被占用6MB(被allocation1、allocation2、allocation3占用)。
2 ,大对象直接进入老年代
“大对象”是:需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组(例如如下代码中的byte[]数组)。
大对象对虚拟机的内存分配就是一个坏消息
- (对Java虚拟机而言,比遇到一个大对象更坏的情况时遇到一群“朝生夕灭”的“短命大对象”,编写程序时应当避免此现象产生)
- 经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。
2,1,测试环境设置:
大体的新生代与老年代内存大小设置同以上一样,只是这里多设置了一个限制:
- 虚拟机提供了一个
-XX:PretenureSizeThreshold
参数,令大于这个设置的对象直接在老年代分配。 - 目的是为了避免在Eden区及两个Survivor区之间发生大量的内存复制(注意:新生代采用复制算法)。
2,2,代码实践与日志展示:
【大对象直接进入老年代】 private static final int _1MB = 1024 * 1024; /**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:PretenureSizeThreshold=3145728
*/
public static void testPretenureSizeThreshold() {
byte[] allocation;
allocation = new byte[4 * _1MB]; //直接分配在老年代中
}
运行结果:
Heap
def new generation total 9216K, used 507K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, 6% used [0x32750000, 0x327cef38, 0x32f50000)
from space 1024K, 0% used [0x32f50000, 0x32f50000, 0x33050000)
to space 1024K, 0% used [0x33050000, 0x33050000, 0x33150000)
tenured generation total 10240K, used 4096K [0x33150000, 0x33b50000, 0x33b50000)
the space 10240K, 40% used [0x33150000, 0x33550010, 0x33550200, 0x33b50000)
compacting perm gen total 12288K, used 376K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, 3% used [0x33b50000, 0x33bae3b8, 0x33bae400, 0x34750000)
ro space 10240K, 55% used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000)
rw space 12288K, 55% used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000)
2,3,结果分析:
- 执行完
testPretenureSizeThreshold()
方法后,查看打印日志的“the space 10240K, 40% used”,可以发现Eden空间几乎没有被使用, - 而老年代的10MB空间被使用了40%,也就是4MB的allocation 对象直接被分配到老年代中,
- 因为PretenureSizeThreshold被设置为3M(就是3145728,此参数不能像 -Xmx之类的参数那样写成3MB),因此超过3MB的对象都会直接在老年代进行分配。
3,长期存活的对象将进入老年代
3,1,对象年龄(Age)计数器
既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别对象应放在新生代还是老年代。
为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。
- 如果对象在Eden出生并经过第一次 Minor GC后仍然存活,并且能被Survivor 容纳的话,将被移动到 Survivor空间中,并且对象年龄设为1。
- 对象在Survivor 区 每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会晋升到老年代中。
3,2,测试环境设置
大体的新生代与老年代内存大小设置都是一样,这里多出现了一种参数:
-XX:MaxTenuringThreshold
,可通过它来设置对象晋升老年代的年龄阀值。
3,3,实践与日志展示
【长期存活的对象进入老年代】 private static final int _1MB = 1024 * 1024; /**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
* -XX:+PrintTenuringDistribution
*/
@SuppressWarnings("unused")
public static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 4]; // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
以 MaxTenuringThreshold=1 参数来运行的结果:
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 418144 bytes, 418144 total
: 4695K->408K(9216K), 0.0054252 secs] 4695K->4504K(19456K), 0.0054708 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 136 bytes, 136 total
: 4668K->0K(9216K), 0.0013601 secs] 8764K->4504K(19456K), 0.0013867 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4260K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, 52% used [0x32750000, 0x32b78fe0, 0x32f50000)
//☆☆☆
from space 1024K, 0% used [0x32f50000, 0x32f50088, 0x33050000)
to space 1024K, 0% used [0x33050000, 0x33050000, 0x33150000)
tenured generation total 10240K, used 4504K [0x33150000, 0x33b50000, 0x33b50000)
//☆☆☆
the space 10240K, 43% used [0x33150000, 0x335b60a0, 0x335b6200, 0x33b50000)
compacting perm gen total 12288K, used 377K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, 3% used [0x33b50000, 0x33bae5c0, 0x33bae600, 0x34750000)
以 MaxTenuringThreshold=15 参数来运行的结果:
......
from space 1024K, 39% used [0x32f50000, 0x32f50088, 0x33050000)
......
the space 10240K, 40% used [0x33150000, 0x335b60a0, 0x335b6200, 0x33b50000)
......
3,4,结果分析
以上分别将参数 -XX:MaxTenuringThreshold
设置成1 和15来进行测试代码中的 testTenuringThreshold()
方法,
此方法中的allocation1 对象需要256KB内存,Survivor空间可以容纳。
- 当MaxTenuringThreshold = 1 时,allocation1 对象在第二次GC 时进入老年代,新生代已使用的内存GC 后非常干净地变成 0KB。
- 当MaxTenuringThreshold = 15 时,在第二次GC后,allocation1 对象还留在新生代 Survivor空间,此时新生代仍然有404KB 被占用。
PS:年轻=10m;eden=8m;survivor(to+from)=2m;to区只是过度,实际年轻代只有eden+to_survivor=9m。老年代=10m。
1,allocation1=0.25m,allocation2=4m,依次分配在年轻代的eden区,当需要存放allocation2=4m(4+0.25+4>8)内存不够,触发第一次gc,
2,将allocation1=0.25m移到to区,年龄=1,allocation2=4m(4>1)直接移到老年代中,eden区清空了,然后将to区转换为from区。
3,然后将allocation2+allocation3=8m存入eden(刚好存满),然后allocation3=null即让其成为垃圾对象。
当allocation3再赋值新对象,eden空间不够触发第二次gc,eden中两个对象一个被清理一个将被移到老年代。
from区的allocation1=0.25也被第二gc处理,年龄+1,然后进入老年代( 因为设置年龄大于1可以进入老年代)。
第二次gc年轻代内存都被清空。 参考堆分代(年轻代,老年代)
4 动态对象年龄判断
- 为了能够更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象年龄必须达到MaxTenuringThreshold规定值才能晋升老年代,
- 如果在 Survivor 空间中相同年龄所有对象大小的总和大于Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到参数的规定值。
4,1,代码实践与日志展示
大体的新生代与老年代内存大小设置一样,这里将参数-XX:MaxTenuringThreshold
(对象晋升老年代的年龄阀值)设置为15。
【动态对象年龄判断】
private static final int _1MB = 1024 * 1024; /**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
* -XX:+PrintTenuringDistribution
*/
@SuppressWarnings("unused")
public static void testTenuringThreshold2() {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[_1MB / 4];
// allocation1+allocation2大于survivo空间一半
allocation2 = new byte[_1MB / 4];
allocation3 = new byte[4 * _1MB];
allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB];
}
运行结果:
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 680304 bytes, 680304 total
: 4951K->664K(9216K), 0.0033210 secs] 4951K->4760K(19456K), 0.0033442 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 136 bytes, 136 total
: 4924K->0K(9216K), 0.0011772 secs] 9020K->4760K(19456K), 0.0011987 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4260K [0x32750000, 0x33150000, 0x33150000)
eden space 8192K, 52% used [0x32750000, 0x32b78fe0, 0x32f50000)
from space 1024K, 0% used [0x32f50000, 0x32f50088, 0x33050000)
to space 1024K, 0% used [0x33050000, 0x33050000, 0x33150000)
tenured generation total 10240K, used 4760K [0x33150000, 0x33b50000, 0x33b50000)
the space 10240K, 46% used [0x33150000, 0x335f60b0, 0x335f6200, 0x33b50000)
compacting perm gen total 12288K, used 377K [0x33b50000, 0x34750000, 0x37b50000)
the space 12288K, 3% used [0x33b50000, 0x33bae5c0, 0x33bae600, 0x34750000)
结果分析:
执行完代码后,结果中的 Survivor空间占用仍为 0%,而老年代比预期增加了 6%,
也就是说:allocation1 、allocation2 对象都直接进入老年代,而没有 等到15岁的临界年龄。
因为这两个对象加起来已达了512KB,并且它们是同年的,满足同年对象达到Survivor 空间的一半规则。
(只需注释掉其中一个对象的new操作,就会发现另外一个就不会晋升到老年代中)
5,空间分配担保
5,1,在发生 Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间。
1,如果以上条件成立,那么 Minor GC可确保时安全的。
2,若不成立,则虚拟机会查看HandlePromotionFailure参数设置值是否允许担保失败。
如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。
1,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;
2,如果小于或者HandlePromotionFailure参数设置不允许“冒险”,此时改为进行一次 Full GC。
5,2,“冒险”概念解析:
- 在前面提过,新生代使用复制收集算法,但为了内存利用率,只使用其中一个 Survivor空间作为轮换备份,
- 因此当出现大量对象在 Minor GC 后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),
- 就需要老年代进行分配担保,把 Survivor无法容纳的对象直接进入老年代。
- 前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来在实际完成内存回收之前是无法明确知道的,
- 所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行 Full GC 来让老年代腾出更多空间。
5,3,测试环境设置
大体的新生代与老年代内存大小设置都是一样,新加了一个参数:-XX:HandlePromotionFailure
,用来设置是否允许担保失败。
【空间分配担保】
private static final int _1MB = * ;
/**
* VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
*/
@SuppressWarnings("unused")
public static void testHandlePromotion() {
byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
allocation1 = new byte[ * _1MB];
allocation2 = new byte[ * _1MB];
allocation3 = new byte[ * _1MB];
allocation1 = null;
allocation4 = new byte[ * _1MB];
allocation5 = new byte[ * _1MB];
allocation6 = new byte[ * _1MB];
allocation4 = null;
allocation5 = null;
allocation6 = null;
allocation7 = new byte[ * _1MB];
}
以 HandlePromotionFailure = false参数来运行的结果:
[GC [DefNew: 6487K->152K(9216K), 0.0040346 secs] 6487K->4248K(19456K), 0.0040639 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6546K->6546K(9216K), 0.0004896 secs] 10642K->4248K(19456K), 0.0005141 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
以 HandlePromotionFailure = true参数来运行的结果:
[GC [DefNew: 6487K->152K(9216K), 0.0040346 secs] 6487K->4248K(19456K), 0.0040639 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6546K->152K(9216K), 0.0004896 secs] 10642K->4248K(19456K), 0.0006143 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
5,4,结果分析:
从日志可看出,设置HandlePromotionFailure 参数不同的值,影响到虚拟机的空间分配担保原则,当参数为true时,即允许担保失败,
会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,来决定后续是 Minor GC 还是 Full GC。
jvm高级特性(4)(内存分配回收策略)的更多相关文章
- JVM之垃圾收集器与内存分配回收策略(二)
上一篇JVM垃圾收集器与内存分配策略(一),下面是jdk1.7版本的垃圾收集器之间的关系,其中连线两端的两种垃圾收集器可以进行搭配使用,下面来总结一下这些收集器的一些特点以及关系. 一.Serial收 ...
- jvm高级特性(1)(内存泄漏实例)
jvm内存结构回顾: .8同1.7比,最大的差别就是:元数据区取代了永久代.元空间的本质和永久代类似,都是对JVM规范中方法区的实现. 不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中, ...
- jvm高级特性(2)(判断存活对象算法,finaliza(),方法区回收)
JVM高级特性与实践(二):对象存活判定算法(引用) 与 回收 垃圾回收器GC(Garbage Collection) 于1960年诞生在MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语 ...
- 《深入理解Java虚拟机:JVM高级特性与最佳实践》【PDF】下载
<深入理解Java虚拟机:JVM高级特性与最佳实践>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062566 内容简介 作为一位 ...
- jvm高级特性(6)(线程的种类,调度,状态,安全程度,实现安全的方法,同步种类,锁优化,锁种类)
JVM高级特性与实践(十三):线程实现 与 Java线程调度 JVM高级特性与实践(十四):线程安全 与 锁优化 一. 线程的实现 线程其实是比进程更轻量级的调度执行单位. 线程的引入,可以把一个检查 ...
- 读书笔记-《深入理解Java虚拟机:JVM高级特性与最佳实践》
目录 概述 第一章: 走进Java 第二章: Java内存区域与内存溢出异常 第三章: 垃圾收集器与内存分配策略 第四章: 虚拟机性能监控与故障处理 第五章: 调优案例分析与实战 第六章: 类文件结构 ...
- jvm高级特性(5)(1)(原子性,可见性,有序性,volatile,概述)
JVM高级特性与实践(十二):高效并发时的内外存交互.三大特征(原子.可见.有序性) 与 volatile型变量特殊规则 简介: 阿姆达尔定律(Amdahl):该定律通过系统中并行化与串行化的比重来描 ...
- 了解JVM运行时的内存分配
了解JVM运行时的内存分配 前言 上文中,在介绍运行时数据区域中的 JAVA 堆时,提到了 JVM 中的堆,一般分为三大部分:新生代.老年代.永久代,本文将进一步了解运行时的内存分配情况. 正文 1. ...
- JVM垃圾回收器、内存分配与回收策略
新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...
随机推荐
- DevExpress TextEdit Focus问题
在标签切换时设置第一个TextEdit获取输入焦点无效,需要采用消息Post方式设置 //标签切换事件 xtraTabControl1.Selected += (s, e) => { if (e ...
- bootstrap-treeview的 简单使用
理论:http://blog.csdn.net/babyxue/article/details/73835444 插依赖Bootstrap 和jQuery <link href="~/ ...
- excel绝对引用中间添加符号
=F1&"_"&J1 绝对引用 相对引用 按F4 然后复制全部,选择性黏贴,值和数字即可
- 信息管理代码分析<二>读取二进制文件数据
first和end做为全局变量,分别指向链表的头和尾.建立链表的方式也比较简易,从二进制文件数据块中,依次从头到尾读取,每读取一个就建立一个结点. /*基本模型*/ EMP *emp1; while( ...
- 2015 - 4- 21 iOS开发越狱环境的搭建1
2015 - 4- 20 1. 越狱环境的搭建 http://www.iduuke.com/2030.html http://www.cnblogs.com/xiongwj0910/archi ...
- NSDictionary NSMutableDictionary NSSet NSMutableSet
//description只是返回了一个字符串 // [person description]; // //如果想要打印需要NSLog // NSLog(@"%@& ...
- osgearth
https://max.book118.com/html/2018/0521/167783983.shtm https://baike.1688.com/doc/view-d36134276.html
- (匹配 匈牙利)棋盘游戏 -- Hdu --1281
链接: http://acm.hdu.edu.cn/showproblem.php?pid=1281 http://acm.hust.edu.cn/vjudge/contest/view.action ...
- 201521123035-个人作业4——alpha阶段个人总结
个人总结 在alpha 结束之后, 每位同学写一篇个人博客, 总结自己的alpha 过程: 第一部分 类别 具体技能和面试问题 现在的回答(大三) 语言 最拿手的计算机语言是一?,代码量多少?(偏前端 ...
- 《Python3网络爬虫开发实战》PDF+源代码+《精通Python爬虫框架Scrapy》中英文PDF源代码
下载:https://pan.baidu.com/s/1oejHek3Vmu0ZYvp4w9ZLsw <Python 3网络爬虫开发实战>中文PDF+源代码 下载:https://pan. ...