5. JVM调优

1 JVM常用命令行参数
package com.mashibing.jvm.c5_gc;

import java.util.List;
import java.util.LinkedList;

public class HelloGC {
    public static void main(String[] args) {
        System.out.println("HelloGC!");
        List list = new LinkedList();
        for(;;) {
            byte[] b = new byte[1024*1024];
            list.add(b);
        }
    }
}
  1. 区分概念
    1. 内存泄露:memory leak,对象永远不被回收
    2. 内存溢出:out of memory,内存彻底不够了
  2. java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
    1. -Xms40M:最小堆大小
    2. -Xmx60M:最大堆大小,一般与最小值设为一样的,防止堆产生弹性的压缩,浪费系统计算资源
    3. -Xmn10M:年轻代大小
    4. -XX:+PrintGC:打印GC日志
//InitialHeapSize:初始堆大小,单位为byte,由-Xms40M设置,也确实是40M
//MaxHeapSize:最大堆大小
//NewSize:最小新生代大小
//MaxNewSize:最大新生代大小
-XX:InitialHeapSize=41943040 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
HelloGC!
//-XX:+PrintGC:可以打印下面的gc信息
//年轻代满了,所以产生YGC,而每次都回收不掉,所以扔进老年代
[GC (Allocation Failure)  7972K->6864K(39936K), 0.0027569 secs]
[GC (Allocation Failure)  14274K->14048K(39936K), 0.0022541 secs]
[GC (Allocation Failure)  21585K->21184K(39936K), 0.0018956 secs]
[GC (Allocation Failure)  28540K->28400K(39936K), 0.0026920 secs]
[Full GC (Ergonomics)  28400K->28264K(54784K), 0.0091581 secs]
[GC (Allocation Failure)  35775K->35529K(54784K), 0.0026164 secs]
[GC (Allocation Failure)  42860K->42705K(54784K), 0.0023221 secs]
//老年代也满了,产生FGC,仍然回收不掉
[Full GC (Ergonomics)  42705K->42582K(60416K), 0.0074762 secs]
[GC (Allocation Failure)  49935K->49878K(60416K), 0.0017456 secs]
[Full GC (Ergonomics)  49878K->49751K(60416K), 0.0076728 secs]
[Full GC (Ergonomics)  57084K->56925K(60416K), 0.0030637 secs]
[Full GC (Ergonomics)  57953K->57945K(60416K), 0.0029954 secs]
[Full GC (Allocation Failure)  57945K->57927K(60416K), 0.0105461 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.mashibing.jvm.c5_gc.HelloGC.main(HelloGC.java:11)
  1. 打印GC日志的其他方式
    1. -XX:+PrintGCDetails:打印GC详细日志
    2. -XX:+PrintGCTimeStamps:打印详细时间
    3. -XX:+PrintGCCause:打印gc产生原因
2 GC日志详解
2.1 PS+PO的GC日志详解
  1. 使用-XX:+PrintGCDetails参数,打印GC详细日志
//1. 开始时,老年代还没东西,所以年轻代占用内存和堆占用内存大小相同
//2. Times:linux中time COMMAND用于测量COMMAND命令执行时所需消耗的时间及系统资源等资讯
//[GC类型,GC表示YGC (GC产生的原因) [PS的年轻代: 回收前年轻代大小->回收后年轻代大小(年轻代总大小)] 回收前堆大小->回收后堆大小(堆总大小), 回收使用时间 secs] [Times: 用户态使用时间 内核态使用时间, 程序实际运行时间 secs] 
[GC (Allocation Failure) [PSYoungGen: 8136K->712K(9216K)] 8136K->6864K(39936K), 0.0036803 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
  1. HeapDump日志:内存溢出后,会将整个堆中信息打印出来
Heap

// 内存起始到使用到的地址,总共8192*91%
// PS的年轻代      eden+survivor的大小, 使用大小 [内存起始地址, 使用到的地址, 内存结束地址)
//  eden区大小, 使用 百分比
 PSYoungGen      total 9216K, used 7476K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 91% used [0x00000000ff600000,0x00000000ffd4d198,0x00000000ffe00000)
  //from:表示其中一个survivor
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 51200K, used 50784K [0x00000000fc400000, 0x00000000ff600000, 0x00000000ff600000)
  object space 51200K, 99% used [0x00000000fc400000,0x00000000ff598198,0x00000000ff600000)
 Metaspace       used 3255K, capacity 4496K, committed 4864K, reserved 1056768K
 //class space:元数据区中,专门存放class的内存空间的情况,reserved:虚拟内存预留,commited:虚拟内存占用,capacity:class总容量,used:使用了多少
  class space    used 353K, capacity 388K, committed 512K, reserved 1048576K
2.2 CMS日志详解
  1. java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01
//开始为年轻代的GC,使用ParNew进行回收
//[GC (产生本次GC的原因) [ParNew: 年轻代收集前->年轻代收集后(整个年轻代大小), 0.0051168 secs] 整个堆收集前->整个堆收集后(整个堆大小), 0.0051920 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 5504K->640K(6144K), 0.0051168 secs] 5504K->1624K(19840K), 0.0051920 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
//老年代使用占整个老年代一定百分比后(7417/13696,默认68%作用),老年代的CMS开始工作,-XX:CMSInitiatingOccupancyFraction指定
//初始标记阶段,stw
//[GC (CMS Initial Mark) [1 CMS-initial-mark: 老年代使用(老年代总大小)] 整个堆使用(整个堆大小), 0.0007117 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 7417K(13696K)] 8080K(19840K), 0.0007117 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
//并发标记开始
[CMS-concurrent-mark-start]
//这里时间意义不大,因为是并发执行,不影响工作线程
[CMS-concurrent-mark: 0.005/0.005 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
//清理之前的一个阶段,该阶段用于将某些card标记为dirty,也称为Card Marking阶段(不重要)
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
//最终标记阶段,stw
//YG occupancy:年轻代占用及容量
//几个阶段
//Rescan (parallel):STW下的存活对象标记,因为工作线程已经停了,不会再产生新的对象
	//weak refs processing: 弱引用处理
//class unloading: 卸载用不到的class,相当于元数据或永久代的清理
//scrub symbol(string) table: 由于class对象已被删除,此时应删除常量池中这些对象对应类的符号引用
	//cleaning up symbol and string tables which hold class-level metadata and 
	//internalized string respectively
//CMS-remark: 7417K(13696K): 阶段过后的老年代占用及老年代容量,9185K(19840K): 阶段过后的堆占用及堆容量
[GC (CMS Final Remark) [YG occupancy: 1768 K (6144 K)][Rescan (parallel) , 0.0005858 secs][weak refs processing, 0.0000225 secs][class unloading, 0.0006967 secs][scrub symbol table, 0.0005223 secs][scrub string table, 0.0001394 secs][1 CMS-remark: 7417K(13696K)] 9185K(19840K), 0.0020718 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
//标记已经完成,进行并发清理
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
//重置内部数据结构,为下次gc做准备(不重要)
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 7307K(13696K)] 9562K(19840K), 0.0006696 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2.3 G1日志详解
  1. java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseG1GC com.mashibing.jvm.gc.T15_FullGC_Problem01
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年轻代 Evacuation-> 复制存活对象 
//initial-mark 表示mixed gc的初始标记阶段,与本次young gc同时执行,如果没有表示纯ygc
   [Parallel Time: 1.5 ms, GC Workers: 1] //一个GC线程
      [GC Worker Start (ms):  92635.7]
      [Ext Root Scanning (ms):  1.1] //从根对象开始搜索
      [Update RS (ms):  0.0] //更新了多少remeber set
         [Processed Buffers:  1]
      [Scan RS (ms):  0.0]
      [Code Root Scanning (ms):  0.0]
      [Object Copy (ms):  0.1]
      [Termination (ms):  0.0]
         [Termination Attempts:  1]
      [GC Worker Other (ms):  0.0]
      [GC Worker Total (ms):  1.2]
      [GC Worker End (ms):  92636.9]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms] //clear cardtable
   [Other: 0.1 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.0 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)] //各个区的回收前与回收后,以及总大小
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
//以下是混合回收其他阶段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
//无法evacuation,进行FGC
//G1中压根不应该有full gc
[Full GC (Allocation Failure)  18M->18M(20M), 0.0719656 secs]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]
3 性能相关参数
3.1 传统意义上
  1. 吞吐量:单位时间内系统能处理的请求数量,体现系统处理请求的能力,这是目前最常用的性能测试指标
    1. tps:每秒事务数=并发数/平均响应时间
    2. qps:每秒查询数
  2. 并发数:系统可以同时处理请求或交易的数量
  3. 响应时间:多久结束
3.2 对于垃圾回收器来讲
  1. 吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
    1. 吞吐量优先:使用PS+PO
  2. 响应时间:STW越短,响应时间越好
    1. 响应时间优先:G1、CSM
4 调优的含义
  1. 根据需求进行JVM规划和预调优
  2. 优化运行JVM运行环境(慢、卡顿)
  3. 解决JVM运行过程中出现的各种问题(OOM只是其中一部分)
5 调优规划
  1. 调优需要从业务场景开始,没有业务场景的调优都是耍流氓
    1. 淘宝历年来,最高并发54w,12306号称百万并发
  2. 无监控(压力测试,能看到结果),不调优,能及时看到调优后,性能是否有所提升
  3. 调优步骤:
  4. 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
  5. 选择回收器组合
  6. 减少stw时间:CMS、G1、ZGC
  7. 减少gc总时间:PS
  8. 计算内存需求:按经验,内存大不一定好
  9. 选定CPU:越高越好,越好回收越快,执行也越快
  10. 设定年代大小、升级年龄
  11. 生产环境设定日志参数
  12. 观察日志情况
//%t:系统时间,UseGCLogFileRotation:循环生成日志,NumberOfGCLogFiles:最多存放日志文件个数,GCLogFileSize:每个日志大小
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
6 案例分析
  1. 垂直电商(只卖某一种产品),最高每日百万订单,处理订单系统需要什么样的服务器配置?
    1. 了解最高峰吞吐量:1000订单/秒
    2. 了解平均响应时间:100ms
    3. 非要计算内存:每次事务,所需内存(将所有涉及对象算出个总和512k),乘以1000,也就是最低需要500M内存
  2. 有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了
    1. 原网站慢的原因:很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢
    2. 内存扩大更卡顿原因:内存越大,FGC时间越长
    3. 解决方案:修改垃圾回收期,由PS+PO->PN + CMS或->G1
  3. 系统CPU经常100%,如何进行调优
    1. CPU100%那么一定有线程在占用系统资源
    2. 找出哪个进程cpu高:top
    3. 找出该进程中的哪个线程cpu高:top -Hp
    4. 如果为java程序,导出该线程的堆栈信息:jstack
    5. 查找哪个方法(栈帧)消耗时间 :jstack
    6. 工作线程占比高 | 垃圾回收线程占比高
  4. 系统内存飙高,如何查找问题
    1. 依然是先查具体是谁占用了内存
    2. 导出堆内存 (jmap)
    3. 分析 (jhat、jvisualvm、mat、jprofiler … )
  5. 如何监控JVM
    1. jstat、jvisualvm、jprofiler、arthas、top…
7 一次具体解决问题
  1. 测试代码:
package com.mashibing.jvm.c5_gc;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 从数据库中读取信用数据,套用模型,并把结果进行记录和传输
 */

public class T15_FullGC_Problem01 {

    private static class CardInfo {
        BigDecimal price = new BigDecimal(0.0);
        String name = "张三";
        int age = 5;
        Date birthdate = new Date();

        public void m() {

        }
    }

    private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
            new ThreadPoolExecutor.DiscardOldestPolicy());

    public static void main(String[] args) throws Exception {
        executor.setMaximumPoolSize(50);

        for (;;){
            modelFit();
            Thread.sleep(100);
        }
    }

    private static void modelFit(){
        List<CardInfo> taskList = getAllCardInfo();
        taskList.forEach(info -> {
            // do something
            executor.scheduleWithFixedDelay(() -> {
                //do sth with info
                info.m();

            }, 2, 3, TimeUnit.SECONDS);
        });
    }

    private static List<CardInfo> getAllCardInfo(){
        List<CardInfo> taskList = new ArrayList<>();

        for (int i = 0; i < 100; i++) {
            CardInfo ci = new CardInfo();
            taskList.add(ci);
        }

        return taskList;
    }
}

  1. 执行程序:java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
  2. 运维团队收到内存、cpu报警
  3. top:内存不断增长 CPU占用率居高不下,记录占比最高的进程号
  4. top -Hp 进程号:观察进程中哪个线程号占比最高
  5. jstack -l 进程号,将该进程下所有线程列出来,通过top -Hp得到的十进制线程号,转为十六进制,在显示结果中进行查询,该值应该是nid的值
    1. 有可能发现该占比很高的线程为垃圾回收线程
    2. 此时说明一定有工作线程占用了大量的内存不释放,导致垃圾清理线程频繁进行fgc,从而浪费cpu
    3. 因此应重点关注线程名是我们自己创建的那种工作线程的名,状态为WAITING、BLOCKED的线程
      1. 这就是为什么阿里规范里规定,应自定义ThreadFactory,从而为线程指定有意义的名称
    4. 同时应查看这种线程的waiting on <0x00000000f89d4ad8>,<0x00000000f89d4ad8>就是synchronized锁住的对象,由于很多线程在等待这个锁的释放,那么说明持有该锁的人,就是导致其他工作线程等待的线程,可以直接复制该字符串去搜索,找到持有该字符串,且线程状态为RUNNABLE的线程
java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000f89d4ad8> (a java.lang.ref.Reference$Lock)
//Reference Handler:处理reference的jvm自己的内部线程
//Finalizer:垃圾回收对应线程
//""中为线程名,找自己的线程名对应的线程
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000000301b800 nid=0x42d4 in Object.wait() [0x0000000014b8f000]
  1. jps:列出所有java进程,和ps -ef|grep java作用相似
  2. jinfo 进程号:打印java进程的一些启动参数,使用的jdk版本信息
  3. jstat -gc 进程号 毫秒数:每隔毫秒数打印gc日志
  4. 图形界面远程监控
    1. 一般linux、aix都不安装图形化界面
    2. java有一个标准的访问远程服务的协议jmx,用于远程管理、监控java进程,只需在服务器开启jmx服务,然后用一个支持标准jmx的客户端去远程连接即可实现远程图形界面监控
    3. 支持jmx服务的图形客户端包括jsvisual VM、jconsole、Jprofiler(收费)
    4. 但使用图形界面远程监控,会浪费大量资源,因此生产环境通常不用其进行监控
    5. 而是使用命令行,或阿里的arthas进行监控
    6. 图形界面只用于测试的时候进行监控(压测观察)
  5. 查看堆中对象信息
    1. jmap -histo 进程号| head -20:查找占用内存最大的前20名对象
      1. 可以对死掉但没重启的进程执行jmap
    2. jmap -dump:format=b,file=xxx pid:在线转储文件,就是将整个堆中信息以文件形式导出
      1. 这个命令对在线系统影响大,不能生产环境使用
      2. jmap -histo影响较小可以使用
      3. 设定参数-XX:+HeapDumpOnOutOfMemoryError,OOM的时候会自动产生堆转储文件
  6. 使用MAT、jhat、jvisualvm对dump文件分析
    1. MAT:https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
    2. jhat -J-mx512M xxx.dump:分析后,会启动在服务器上启动一个7000端口,用于查询结果
      1. http://192.168.17.11:7000
      2. 拉到最后:找到对应链接
      3. 可以使用OQL查找特定问题对象
    3. jvisual vm:文件->装入
8 jconsole远程连接
  1. 程序启动加入参数:
//在服务器上,启动程序同时,开启一个11111端口,用于jmx服务
java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX
  1. 如果遭遇 Local host name unknown:XXX的错误,修改/etc/hosts文件,把XXX加入进去
192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
  1. 关闭linux防火墙(实战中应该打开对应端口)
service iptables stop
chkconfig iptables off #永久关闭
  1. windows上打开 jconsole远程连接 192.168.17.11:11111
9 jvisualvm远程连接:简单做法

https://www.cnblogs.com/liugh/p/7620336.html

10 jprofiler (收费)
11 arthas:在线排查工具,无需远程挂图形界面
  1. https://github.com/alibaba/arthas
  2. 必须全量安装:https://img.shields.io/maven-central/v/com.taobao.arthas/arthas-packaging.svg?style=flat-square
  3. 进入bin目录
  4. java -jar arthas-boot.jar
  5. 输入进程在arthas下的编号
  6. 此时arthas将自己attach到这个进程上,可以使用arthas的命令,来查看这个进程状态,arthas@1442,就表示当前arthas位于1442进程上
  7. help:查看常用命令
  8. jvm:类似jinfo功能
  9. thread:查看有多少线程,占用信息,类似jstack功能
  10. thread 1:查看编号为1的线程中的一些堆栈的调用情况
  11. arthas没提供类似jmap的功能
  12. dashboard:类似top -Hp
  13. heapdump:导出堆文件,相当于jmap导出整个堆情况,heapdump /root/20200104.hprof,表示导入到指定路径,也是对主进程有很大影响
  14. jhat:对导出的堆文件,进行分析
    1. jhat -J-mx512 文件名:指定jhat命令使用的内存大小,不然dump文件较大时,会导致jhat命令内存溢出,如果512仍然不够用还是会内存溢出,导致分析失败
    2. 分析完成后,会在服务器的7000端口上启动一个服务,然后我们就可以远程通过一个端口来访问分析结果
    3. show instance counts for all classes:类似jmap导出的信息
    4. execute Object Query Language query:使用OQL语句查询对象信息,例如select s from java.lang.String s,可以把所有String对象都显示出来
  15. jad:对内存中的class进行反编译,jad 全限定类名,用于动态代理生成类的问题定位、查看第三方的类、查看自己编写的代码是否为最新版本
  16. redefine:热替换,目前只能改方法实现,不能改方法名、属性等
    1. 正常替换,必须停服务,然后重新更新,然后重启,但生产环境有时不允许停
    2. 也可以用于临时加一些日志,比如在一些关键的函数里打印出入参数,适用于上线流程复杂而且审核比较严的公司
    3. 先attach到进程上
    4. jad查看要替换的类,确实不是最新版本
    5. 直接修改原来的类并编译到/root/TT.class
    6. redefine /root/TT.class,使用/root/TT.class,替代内存中的TT类,完成热替换
12 纤程(协程) Fiber
  1. 程序–进程–线程–纤程:线程是进程的优化,纤程是线程的优化
  2. 一个QQ程序可以启动多个进程(可以登录多个账号)
  3. 一个进程中可以启动多个线程,例如UI线程、Socket线程
  4. 一个线程中可以启动多个纤程
  5. 创建一个新线程后,CPU将老线程的线程栈信息保存在内存,然后再通过内核空间切换到新线程linux操作系统分为两个执行级别,用户空间和内核空间,JVM是运行在用户空间的,当JVM需要启动新线程,或在两个线程间切换时,需要通过内核进行创建与切换,耗费资源和时间较大,是重量级的
  6. 纤程相当于是存在于用户空间的线程,每个纤程也有自己的内存栈,因为他们本身就在用户空间,所以他们之间的切换不通过内核空间,耗费资源和时间较少
  7. 做内存计算的适合纤程,以实际情况为准
  8. Go语言、Python中支持纤程,可以使用第三方纤程库Quasar来使用纤程,截止JDK13,Java官方还不支持纤程,所以Quasar fiber依赖java instrumentation修改你的代码,可以在运行时通过java Agent实现,也可以在编译时使用ant task实现。但该第三方jar包还没到1.0版本,暂时不建议使用
  9. 启一个线程大概占1M,当线程太多,会卡死,大量CPU与时间浪费在线程间切换,但纤程不一样,可以同时启几万个纤程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值