精华内容
下载资源
问答
  • 当遇到高并发时候,所有Batch空间瞬间被填满,还没持久化,就会触发JVM GC Kafka处理策略是创建了个缓冲池 放了很多块内存空间,如果你需要新 Batch 了,就可以直接从 这个缓冲池拿走内存空间就 ok 了? ...

    在 Kafka客户端,会有缓冲池的机制
    Kafka客户端发送的消息不会立马持久化到磁盘当中。
    而是会放到一个Batch中当Batch填满之后,才会持久化到磁盘中
    当遇到高并发的时候,所有的Batch空间瞬间被填满,还没持久化,就会触发JVM GC
    Kafka的处理策略是创建了个缓冲池 放了很多块内存空间,如果你需要新的 Batch 了,就可以直接从
    这个缓冲池拿走内存空间就 ok 了?
    然后如果 Batch 发送出去了之后,再把内存空间收回来就好了
    这种设计类似于Java中线程池。核心思想一空间换时间。

    展开全文
  • 每一条JVM线程都有自己PC寄存器,各条线程之间互不影响,独立存储,这类内存区域被称为“线程私有”内存 在任意时刻,一条JVM线程只会执行一个方法代码。该方法称为该线程当前方法(Current Method) 如果该...

    目录

    一、JVM内存模型

    1、程序计数器(Program Counter Register)

    2、Java虚拟机栈(Java Virtual Machine Stack)

    3、本地方法栈(Native Method Stack)

    4、Java堆(Java Heap)

    5、运行时常量池(Runtime Constant Pool)

    6、直接内存(Direct Memory)

    7、方法区(Method Area)

    二、内存常见问题

    1、共享内存区划分

    2、常见参数的配置

    堆栈相关

    垃圾收集器参数

    怎么打出线程栈信息。

    堆栈日志信息分析

    3、JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代

    4、几种垃圾收集器:

    jdk1.7或1.8 默认垃圾收集器

    5、CMS收集器和G1收集器的区别

    6、FullGC

    7、GC roots

    8、GC Roots对象

    9、什么原因导致了连续的FullGC?

    10、如何来解决连续的FullGC?

    1.增加JVM的堆内存

    2.增加Perm或者Metaspace内存

    3.增加更多的JVM实例

    11、JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存。

    12、java内存模型中堆和栈的区别

    13、指令重排

    14、内存屏障


     

     

    一、JVM内存模型

    jvm--Java堆、方法区、Java虚拟机栈、本地方法栈、程序计数器

     

    1、程序计数器(Program Counter Register)

     

    它是一块较小的内存空间,它的作用可以看做是当先线程所执行的字节码的信号指示器。

    每一条JVM线程都有自己的PC寄存器,各条线程之间互不影响,独立存储,这类内存区域被称为“线程私有”内存

    在任意时刻,一条JVM线程只会执行一个方法的代码。该方法称为该线程的当前方法(Current Method)

    如果该方法是java方法,那PC寄存器保存JVM正在执行的字节码指令的地址

    如果该方法是native,那PC寄存器的值是undefined。

    此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

     

    2、Java虚拟机栈(Java Virtual Machine Stack)

     

    与PC寄存器一样,Java虚拟机栈也是线程私有的。每一个JVM线程都有自己的java虚拟机栈,这个栈与线程同时创建,它的生命周期与线程相同。

    虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

    每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

    JVM stack 可以被实现成固定大小,也可以根据计算动态扩展。

    如果采用固定大小的JVM stack设计,那么每一条线程的JVM Stack容量应该在线程创建时独立地选定。JVM实现应该提供调节JVM Stack初始容量的手段;

    如果采用动态扩展和收缩的JVM Stack方式,应该提供调节最大、最小容量的手段。

    如果线程请求的栈深度大于虚拟机所允许的深度将抛出StackOverflowError;

    如果JVM Stack可以动态扩展,但是在尝试扩展时无法申请到足够的内存时抛出OutOfMemoryError。

     

    3、本地方法栈(Native Method Stack)

     

    本地方法栈与虚拟机栈作用相似,后者为虚拟机执行Java方法服务,而前者为虚拟机用到的Native方法服务。

    虚拟机规范对于本地方法栈中方法使用的语言,使用方式和数据结构没有强制规定,甚至有的虚拟机(比如HotSpot)直接把二者合二为一。

    这玩意儿抛出的异常跟上面的虚拟机栈一样。

     

    4、Java堆(Java Heap)

     

    虚拟机管理的内存中最大的一块,同时也是被所有线程所共享的,它在虚拟机启动时创建,这货存在的意义就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。

    这里面的对象被自动管理,也就是俗称的GC(Garbage Collector)所管理。用就是了,有GC扛着呢,不用操心销毁回收的事儿。

    Java堆的容量可以是固定大小,也可以随着需求动态扩展(-Xms和-Xmx),并在不需要过多空间时自动收缩。

    Java堆所使用的内存不需要保证是物理连续的,只要逻辑上是连续的即可。

    JVM实现应当提供给程序员调节Java 堆初始容量的手段,对于可动态扩展和收缩的堆来说,则应当提供调节其最大和最小容量的手段。

    如果堆中没有内存完成实例分配并且堆也无法扩展,就会抛OutOfMemoryError。

    这里有一个小例子,来说明堆,栈和方法区之间的关系的

    public class Test2 {
    
    	public static void main(String[] args) {
    
    		public Test2 t2 = new Test2();
    
    		//JVM将Test2类信息加载到方法区,new Test2()实例保存在堆区,Test2引用保存在栈区  
    
    		}
    }

     

    5、运行时常量池(Runtime Constant Pool)

     

    它是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,

    这部分内容将在类加载后存放到方法区的运行时常量池中。

    Java虚拟机对Class文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。但对于运行时常量池,

    Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。

    不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。

    运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,

    运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。

    既然运行时常量池是方法区的一部分,自然会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

     

    6、直接内存(Direct Memory)

     

    直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。

    JDK1.4加的NIO中,ByteBuffer有个方法是allocateDirect(int capacity) ,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,

    然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

    显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括RAM及SWAP区或者分页文件)的大小及处理器寻址空间的限制。

    服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),

    从而导致动态扩展时出现OutOfMemoryError异常。

     

    7、方法区(Method Area)

     

    跟堆一样是被各个线程共享的内存区域,用于存储以被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然这个区域被虚拟机规范把方法区描述为堆的一个逻辑部分,

    但是它的别名叫非堆,用来与堆做一下区别。

    方法区在虚拟机启动的时候创建。

    方法区的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。

    方法区在实际内存空间中可以是不连续的。

    Java虚拟机实现应当提供给程序员或者最终用户调节方法区初始容量的手段,对于可以动态扩展和收缩方法区来说,则应当提供调节其最大、最小容量的手段。

    当方法区无法满足内存分配需求时就会抛OutOfMemoryError。

     

    二、内存常见问题

     

    1、共享内存区划分

    共享内存区 = 持久带 + 堆

    持久带 = 方法区 + 其他

    Java堆 = 老年代 + 新生代

    新生代 = Eden + S0 + S1

     

    默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ,可以通过参数 –XX:NewRatio 配置。

    默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定)

    Survivor区中的对象被复制次数为15(对应虚拟机参数 -XX:+MaxTenuringThreshold)

    为什么要分为Eden和Survivor?为什么要设置两个Survivor区?

    如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。

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

    设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)

    2、常见参数的配置

    堆栈相关

    -Xmx3550m: 最大堆大小为3550m。

    -Xms3550m: 设置初始堆大小为3550m。

    -Xmn2g: 设置年轻代大小为2g。

    -Xss128k: 每个线程的大小为128k。

    -XX:MaxPermSize: 设置持久代大小为16m

    -XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。

    -XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

    -XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。

    垃圾收集器参数

    -XX:+UseParallelGC: 选择垃圾收集器为并行收集器。

    -XX:ParallelGCThreads=20: 配置并行收集器的线程数

    -XX:+UseConcMarkSweepGC: 设置年老代为并发收集。

    -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

    -XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

     

    怎么打出线程栈信息。

    思路: 可以说一下jps,top ,jstack这几个命令,再配合一次排查线上问题进行解答。

    我的答案:

    输入jps,获得进程号。

    top -Hp pid 获取本进程中所有线程的CPU耗时性能

    jstack pid命令查看当前java进程的堆栈状态

    或者 jstack -l > /tmp/output.txt 把堆栈信息打到一个txt文件。

    可以使用fastthread 堆栈定位,fastthread.io/

    例如:

    1、jps:查看本地正在运行的java进程和进程ID(pid)

    2、jinfo pid,查看指定pid的所有JVM信息

      1)jinfo -flags pid 查询虚拟机运行参数信息。

      2)jinfo -flag name pid,查询具体参数信息,如jinfo -flag UseSerialGC 42324,查看是否启用UseSerialGC

     

    3、jmap

      1)jmap -heap pid:输出堆内存设置和使用情况(JDK11使用jhsdb jmap --heap --pid pid)

      2)jmap -histo pid:输出heap的直方图,包括类名,对象数量,对象占用大小

      3)jmap -histo:live pid:同上,只输出存活对象信息

      4)jmap -clstats pid:输出加载类信息

      5)jmap -help:jmap命令帮助信息

    4、jstat:Java虚拟机统计工具,全称“Java Virtual Machine statistics monitoring tool”。可以用于监视JVM各种堆和非堆内存大小和使用量

      1)jstat -class pid:输出加载类的数量及所占空间信息。

      2)jstat -gc pid:输出gc信息,包括gc次数和时间,内存使用状况(可带时间和显示条目参数)

      其他命令不一一列举。

     

    堆栈日志信息分析

    -XX:+PrintGC 输出形式:

    [GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]

    -XX:+PrintGCDetails 输出形式:

    [GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs

     

    YoungGC:

     

    FullGC:

     

     

     

    3、JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代

    思路: 先描述一下Java堆内存划分,再解释Minor GC,Major GC,full GC,描述它们之间转化流程。

    我的答案:

    Java堆 = 老年代 + 新生代

    新生代 = Eden + S0 + S1

    当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。

    大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态;

    如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。

    老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代。

    Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上。

    5.你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。

    思路: 一定要记住典型的垃圾收集器,尤其cms和G1,它们的原理与区别,涉及的垃圾回收算法。

    我的答案:

     

    4、几种垃圾收集器:

     

    Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。

    ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法。

    Parallel [ˈpærəlel] Scavenge [ˈskævɪndʒ] 收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。

    Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。

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

    CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。

    G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。

     

    jdk1.7或1.8 默认垃圾收集器

    使用参数打印日志
    java -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -version  或者  java -XX:+PrintFlagsFinal
    结论:
    1.8或1.7默认的是 UseParallelGC
    ParallelGC 默认的是 Parallel Scavenge(新生代)+ Parallel Old(老年代)
    在JVM中是+XX配置实现的搭配组合:
    UseSerialGC 表示 “Serial” + "Serial Old"组合
    UseParNewGC 表示 “ParNew” + “Serial Old”
    UseConcMarkSweepGC 表示 “ParNew” + “CMS”. 组合,“CMS” 是针对旧生代使用最多的
    UseParallelGC 表示 “Parallel Scavenge” + "Parallel Old"组合
    UseParallelOldGC 表示 “Parallel Scavenge” + "Parallel Old"组合
    在实践中使用UseConcMarkSweepGC 表示 “ParNew” + “CMS” 的组合是经常使用的

     

    5、CMS收集器和G1收集器的区别

    CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;

    G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;

    CMS收集器以最小的停顿时间为目标的收集器;

    G1收集器可预测垃圾回收的停顿时间

    CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片

    G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片

     

    6、FullGC

    1.CPU利用率飙升

    2.由于应用暂停导致应用的响应时间变长,这会影响服务的可用性和用户体验。

     

    7、GC roots

    GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。

    常说的GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC roots且没有被GC roots引用的对象。

    一个对象可以属于多个root,GC root有几下种:

    Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots。

    Thread - 活着的线程

    Stack Local - Java方法的local变量或参数

    JNI Local - JNI方法的local变量或参数

    JNI Global - 全局JNI引用

    Monitor Used - 用于同步的监控对象

    Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此需要去确定哪些是属于"JVM持有"的了。

     

    8、GC Roots对象

    • 虚拟机栈(栈桢中的本地变量表)中的引用的对象 
    • 方法区中的类静态属性引用的对象 
    • 方法区中的常量引用的对象 
    • 本地方法栈中JNI的引用的对象

     

    9、什么原因导致了连续的FullGC?

    导致连续的FullGC主要只有一个原因:JVM的堆内存不够用或者是Perm、Metaspace内存不够用,说明应用需要比你分配的内存更多的内存。通俗地说就是小马拉大车。

    所以JVM必须要努力的去清理垃圾来给存活的对象分配空间。

     

    你可能会想,我的应用已经好好的跑了很长时间了,为什么忽然就发生连续的FullGC了?这是个好问题,可能是因为以下原因:

    1.可能是自从上次调优JVM的内存以后,应用的流量忽然变大了!可能是业务量增加了,用户数量变多了。

    2.在业务峰值期间应用创建了比平时更多的对象,可能你并没有对峰值时候的应用内存做调优,或者是说应用峰值时候的流量变大了。

     

    10、如何来解决连续的FullGC?

    1.增加JVM的堆内存

    因为连续的FullGC主要是由于内存不足引起的,增加JVM的堆内存可以解决这个问题。比如之前给应用分配的是2.5G的堆内存,现在增加到3G看能否解决问题,

    通过给JVM传递-Xmx参数可以设置JVM的最大堆内存。-Xmx3G就设置了JVM的最大堆内存是3G。如果还是没解决问题,一点点的继续增加JVM的堆内存。

    不要给JVM分配过多的堆内存,因为这会增加GC的停顿时间。

    2.增加Perm或者Metaspace内存

    如果Perm区或者是Metaspace太小也会导致连续的FullGC,这种情况下就需要增大Perm或者是Metaspace的内存。

    3.增加更多的JVM实例

     

    另一个解决此问题的办法就是增加更多的JVM实例。当有更多的JVM实例以后,应用流量就会分摊到这些实例上,单个JVM承担的流量就降低了,

    那么它上面需要创建的对象随之也变少了,此时可能就不会有连续的FullGC了。

     

    11、JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存。

    思路: 先画出Java内存模型图,结合例子volatile ,说明什么是重排序,内存屏障,最好能给面试官写以下demo说明。

    Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,

    线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,

    线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

     

    12、java内存模型中堆和栈的区别

    1,管理方式,堆需要GC,栈自动释放

        2.大小不同,堆比栈大

        3.碎片相关:栈产生的碎片远小于堆,因为GC不是实时的

        4.分配方式:栈支持静态分配内存和动态分配,堆只支持动态分配

        5.效率:栈的效率比堆高

     

    13、指令重排

    代码指令可能并不是严格按照代码语句顺序执行的。大多数现代微处理器都会采用将指令乱序执行(out-of-order execution,简称OoOE或OOE)的方法,在条件允许的情况下,

    直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待3。通过乱序执行的技术,处理器可以大大提高执行效率。而这就是指令重排。

     

     

    14、内存屏障

    也叫内存栅栏,是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。

     

    LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

    StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

    LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

    StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

     

    展开全文
  • 目录 1、Kafka的客户端缓冲机制 2、内存缓冲造成的频繁...“ 这篇文章,同样给大家聊一个硬核的技术知识,我们通过Kafka内核源码中的一些设计思想,来看你设计Kafka架构的技术大牛,是怎么优化JVM的GC问题的? ...

    目录

    1、Kafka的客户端缓冲机制

    2、内存缓冲造成的频繁GC问题

    3、Kafka设计者实现的缓冲池机制

    4、总结一下

    这篇文章,同样给大家聊一个硬核的技术知识,我们通过Kafka内核源码中的一些设计思想,来看你设计Kafka架构的技术大牛,是怎么优化JVM的GC问题的?

    1、Kafka的客户端缓冲机制

    首先,先得给大家明确一个事情,那就是在客户端发送消息给kafka服务器的时候,一定是有一个内存缓冲机制的。

    也就是说,消息会先写入一个内存缓冲中,然后直到多条消息组成了一个Batch,才会一次网络通信把Batch发送过去。

    整个过程如下图所示:

    1

     

    2、内存缓冲造成的频繁GC问题

    那么这种内存缓冲机制的本意,其实就是把多条消息组成一个Batch,一次网络请求就是一个Batch或者多个Batch。

    这样每次网络请求都可以发送很多数据过去,避免了一条消息一次网络请求。从而提升了吞吐量,即单位时间内发送的数据量。

    但是问题来了,大家可以思考一下,一个Batch中的数据,会取出来然后封装在底层的网络包里,通过网络发送出去到达Kafka服务器。

    那么然后呢?这个Batch里的数据都发送过去了,现在Batch里的数据应该怎么处理?

    你要知道,这些Batch里的数据此时可还在客户端的JVM的内存里啊!那么此时从代码实现层面,一定会尝试避免任何变量去引用这些Batch对应的数据,然后尝试触发JVM自动回收掉这些内存垃圾。

    这样不断的让JVM回收垃圾,就可以不断的清理掉已经发送成功的Batch了,然后就可以不断的腾出来新的内存空间让后面新的数据来使用。

    这种想法很好,但是实际线上运行的时候一定会有问题,最大的问题,就是JVM GC问题。

    大家都知道一点,JVM GC在回收内存垃圾的时候,他会有一个“Stop the World”的过程,也就是垃圾回收线程运行的时候,会导致其他工作线程短暂的停顿,这样可以便于他自己安安静静的回收内存垃圾。

    这个也很容易想明白,毕竟你要是在回收内存垃圾的时候,你的工作线程还在不断的往内存里写数据,制造更多的内存垃圾,那你让人家JVM怎么回收垃圾?

    这就好比在大马路上,如果地上有很多垃圾,现在要把垃圾都扫干净,最好的办法是什么?大家都让开,把马路空出来,然后清洁工就是把垃圾清理干净。

    但是如果清洁工在清扫垃圾的时候,结果一帮人在旁边不停的嗑瓜子扔瓜子壳,吃西瓜扔西瓜皮,不停的制造垃圾,你觉得清洁工内心啥感受?当然是很愤慨了,照这么搞,地上的垃圾永远的都搞不干净了!

    通过了上面的语言描述,我们再来一张图,大家看看就更加清楚了

    1

    现在JVM GC是越来越先进,从CMS垃圾回收器到G1垃圾回收器,核心的目标之一就是不断的缩减垃圾回收的时候,导致其他工作线程停顿的时间。

    所以现在越是新款的垃圾回收器导致工作线程停顿的时间越短,但是再怎么短,他也还是存在啊!

    所以说,如何尽可能在自己的设计上避免JVM频繁的GC就是一个非常考验水平的事儿了。

     

    3、Kafka设计者实现的缓冲池机制

    在Kafka客户端内部,对这个问题实现了一个非常优秀的机制,就是缓冲池的机制

    简单来说,就是每个Batch底层都对应一块内存空间,这个内存空间就是专门用来存放写入进去的消息的。

    然后呢,当一个Batch被发送到了kafka服务器,这个Batch的数据不再需要了,就意味着这个Batch的内存空间不再使用了。

    此时这个Batch底层的内存空间不要交给JVM去垃圾回收,而是把这块内存空间给放入一个缓冲池里。

    这个缓冲池里放了很多块内存空间,下次如果你又有一个新的Batch了,那么不就可以直接从这个缓冲池里获取一块内存空间就ok了?

    然后如果一个Batch发送出去了之后,再把内存空间给人家还回来不就好了?以此类推,循环往复。

    同样,听完了上面的文字描述,再来一张图,看完这张图相信大伙儿就明白了:

    1

    一旦使用了这个缓冲池机制之后,就不涉及到频繁的大量内存的GC问题了。

    为什么呢?因为他可以上来就占用固定的内存,比如32MB。然后把32MB划分为N多个内存块,比如说一个内存块是16KB,这样的话这个缓冲池里就会有很多的内存块。

    然后你需要创建一个新的Batch,就从缓冲池里取一个16KB的内存块就可以了,然后这个Batch就不断的写入消息,但是最多就是写16KB,因为Batch底层的内存块就16KB。

    接着如果Batch被发送到Kafka服务器了,此时Batch底层的内存块就直接还回缓冲池就可以了。

    下次别人再要构建一个Batch的时候,再次使用缓冲池里的内存块就好了。这样就可以利用有限的内存,对他不停的反复重复的利用。因为如果你的Batch使用完了以后是把内存块还回到缓冲池中去,那么就不涉及到垃圾回收了。

    如果没有频繁的垃圾回收,自然就避免了频繁导致的工作线程的停顿了,JVM GC问题是不是就得到了大幅度的优化?

    没错,正是这个设计思想让Kafka客户端的性能和吞吐量都非常的高,这里蕴含了大量的优秀的机制。

    那么此时有人说了,如果我现在把一个缓冲池里的内存资源都占满了,现在缓冲池里暂时没有内存块了,怎么办呢?

    很简单,阻塞你的写入操作,不让你继续写入消息了。把你给阻塞住,不停的等待,直到有内存块释放出来,然后再继续让你写入消息。

    转载于:https://my.oschina.net/u/3266761/blog/3051213

    展开全文
  • 既然要说kafka是如何通过内存缓冲池设计来优化JVM的GC问题,那么,如果不清楚kafka 的架构设计,又怎么更好的调优呢?起码的我们要知道基础的才能往更好的出发呀,对吧?先来看一些技术名词: Topic:用于划分...

    大家都知道Kafka是一个高吞吐的消息队列,是大数据场景首选的消息队列,这种场景就意味着发送单位时间消息的量会特别的大,那既然如此巨大的数据量,kafka是如何支撑起如此庞大的数据量的分发的呢?今天我们从kafka架构以如何优化GC两个方面讲解.

    kafka架构
    既然要说kafka是如何通过内存缓冲池设计来优化JVM的GC问题,那么,如果不清楚kafka 的架构设计,又怎么更好的调优呢?起码的我们要知道基础的才能往更好的出发呀,对吧?先来看一些技术名词:

    Topic:用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上。

    Partition:是Kafka中横向扩展和一切并行化的基础,每个Topic都至少被切分为1个Partition。

    Offset:消息在Partition中的编号,编号顺序不跨Partition。

    Consumer:用于从Broker中取出/消费Message。

    Producer:用于往Broker中发送/生产Message。

    Replication:Kafka支持以Partition为单位对Message进行冗余备份,每个Partition都可以配置至少1个Replication(当仅1个Replication时即仅该Partition本身)。

    Leader:每个Replication集合中的Partition都会选出一个唯一的Leader,所有的读写请求都由Leader处理。其他Replicas从Leader处把数据更新同步到本地,过程类似大家熟悉的MySQL中的Binlog同步。

    Broker:Kafka中使用Broker来接受Producer和Consumer的请求,并把Message持久化到本地磁盘。每个Cluster当中会选举出一个Broker来担任Controller,负责处理Partition的Leader选举,协调Partition迁移等工作。

    ISR(In-Sync Replica):是Replicas的一个子集,表示目前Alive且与Leader能够“Catch-up”的Replicas集合。由于读写都是首先落到Leader上,所以一般来说通过同步机制从Leader上拉取数据的Replica都会和Leader有一些延迟(包括了延迟时间和延迟条数两个维度),任意一个超过阈值都会把该Replica踢出ISR。每个Partition都有它自己独立的ISR。

    以上几乎是我们在使用Kafka的过程中经常会遇到的名词,同时也无一不是最核心的概念或组件,感觉到从设计本身来说,Kafka还是足够简洁的。这次本文围绕Kafka优异的吞吐性能,逐个介绍一下其设计与实现当中所使用的各项“黑科技”。

    Broker
    不同于Redis和MemcacheQ等内存消息队列,Kafka的设计是把所有的Message都要写入速度低容量大的硬盘,以此来换取更强的存储能力。实际上,Kafka使用硬盘并没有带来过多的性能损失,“规规矩矩”的抄了一条“近道”。

    首先,说“规规矩矩”是因为Kafka在磁盘上只做Sequence I/O,由于消息系统读写的特殊性,这并不存在什么问题。关于磁盘I/O的性能,引用一组Kafka官方给出的测试数据(Raid-5,7200rpm):

    Sequence I/O: 600MB/s

    Random I/O: 100KB/s

    所以通过只做Sequence I/O的限制,规避了磁盘访问速度低下对性能可能造成的影响。接下来我们再聊一聊Kafka是如何“抄近道的”。

    首先,Kafka重度依赖底层操作系统提供的PageCache功能。当上层有写操作时,操作系统只是将数据写入PageCache,同时标记Page属性为Dirty。当读操作发生时,先从PageCache中查找,如果发生缺页才进行磁盘调度,最终返回需要的数据。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用。同时如果有其他进程申请内存,回收PageCache的代价又很小,所以现代的OS都支持PageCache。使用PageCache功能同时可以避免在JVM内部缓存数据,JVM为我们提供了强大的GC能力,同时也引入了一些问题不适用于Kafka的设计。

    • 如果在Heap内管理缓存,JVM的GC线程会频繁扫描Heap空间,带来不必要的开销。如果Heap过大,执行一次Full GC对系统的可用性来说将是极大的挑战。

    • 所有在在JVM内的对象都不免带有一个Object Overhead(千万不可小视),内存的有效空间利用率会因此降低。

    • 所有的In-Process Cache在OS中都有一份同样的PageCache。所以通过将缓存只放在PageCache,可以至少让可用缓存空间翻倍。

    • 如果Kafka重启,所有的In-Process Cache都会失效,而OS管理的PageCache依然可以继续使用。

    PageCache还只是第一步,Kafka为了进一步的优化性能还采用了Sendfile技术。在解释Sendfile之前,首先介绍一下传统的网络I/O操作流程,大体上分为以下4步:

    OS 从硬盘把数据读到内核区的PageCache。

    用户进程把数据从内核区Copy到用户区。

    然后用户进程再把数据写入到Socket,数据流入内核区的Socket Buffer上。

    OS 再把数据从Buffer中Copy到网卡的Buffer上,这样完成一次发送。
    在这里插入图片描述

    整个过程共经历两次Context Switch,四次System Call。同一份数据在内核Buffer与用户Buffer之间重复拷贝,效率低下。其中2、3两步没有必要,完全可以直接在内核区完成数据拷贝。这也正是Sendfile所解决的问题,经过Sendfile优化后,整个I/O过程就变成了下面这个样子。
    在这里插入图片描述

    通过以上的介绍不难看出,Kafka的设计初衷是尽一切努力在内存中完成数据交换,无论是对外作为一整个消息系统,或是内部同底层操作系统的交互。如果Producer和Consumer之间生产和消费进度上配合得当,完全可以实现数据交换零I/O。这也就是我为什么说Kafka使用“硬盘”并没有带来过多性能损失的原因。下面是我在生产环境中采到的一些指标:

    (20 Brokers, 75 Partitions per Broker, 110k msg/s)
    在这里插入图片描述
    此时的集群只有写,没有读操作。10M/s左右的Send的流量是Partition之间进行Replicate而产生的。从recv和writ的速率比较可以看出,写盘是使用Asynchronous+Batch的方式,底层OS可能还会进行磁盘写顺序优化。而在有Read Request进来的时候分为两种情况,第一种是内存中完成数据交换。
    在这里插入图片描述

    Send流量从平均10M/s增加到了到平均60M/s,而磁盘Read只有不超过50KB/s。PageCache降低磁盘I/O效果非常明显。

    接下来是读一些收到了一段时间,已经从内存中被换出刷写到磁盘上的老数据。
    在这里插入图片描述

    其他指标还是老样子,而磁盘Read已经飚高到40+MB/s。此时全部的数据都已经是走硬盘了(对硬盘的顺序读取OS层会进行Prefill PageCache的优化)。依然没有任何性能问题。

    Tips

    Kafka官方并不建议通过Broker端的log.flush.interval.messages和log.flush.interval.ms来强制写盘,认为数据的可靠性应该通过Replica来保证,而强制Flush数据到磁盘会对整体性能产生影响。

    可以通过调整/proc/sys/vm/dirty_background_ratio和/proc/sys/vm/dirty_ratio来调优性能。

    脏页率超过第一个指标会启动pdflush开始Flush Dirty PageCache。

    脏页率超过第二个指标会阻塞所有的写操作来进行Flush。

    根据不同的业务需求可以适当的降低dirty_background_ratio和提高dirty_ratio。

    Partition
    Partition是Kafka可以很好的横向扩展和提供高并发处理以及实现Replication的基础。

    扩展性方面。首先,Kafka允许Partition在集群内的Broker之间任意移动,以此来均衡可能存在的数据倾斜问题。其次,Partition支持自定义的分区算法,例如可以将同一个Key的所有消息都路由到同一个Partition上去。同时Leader也可以在In-Sync的Replica中迁移。由于针对某一个Partition的所有读写请求都是只由Leader来处理,所以Kafka会尽量把Leader均匀的分散到集群的各个节点上,以免造成网络流量过于集中。

    并发方面。任意Partition在某一个时刻只能被一个Consumer Group内的一个Consumer消费(反过来一个Consumer则可以同时消费多个Partition),Kafka非常简洁的Offset机制最小化了Broker和Consumer之间的交互,这使Kafka并不会像同类其他消息队列一样,随着下游Consumer数目的增加而成比例的降低性能。此外,如果多个Consumer恰巧都是消费时间序上很相近的数据,可以达到很高的PageCache命中率,因而Kafka可以非常高效的支持高并发读操作,实践中基本可以达到单机网卡上限。

    不过,Partition的数量并不是越多越好,Partition的数量越多,平均到每一个Broker上的数量也就越多。考虑到Broker宕机(Network Failure, Full GC)的情况下,需要由Controller来为所有宕机的Broker上的所有Partition重新选举Leader,假设每个Partition的选举消耗10ms,如果Broker上有500个Partition,那么在进行选举的5s的时间里,对上述Partition的读写操作都会触发LeaderNotAvailableException。

    再进一步,如果挂掉的Broker是整个集群的Controller,那么首先要进行的是重新任命一个Broker作为Controller。新任命的Controller要从Zookeeper上获取所有Partition的Meta信息,获取每个信息大概3-5ms,那么如果有10000个Partition这个时间就会达到30s-50s。而且不要忘记这只是重新启动一个Controller花费的时间,在这基础上还要再加上前面说的选举Leader的时间 -_-!!!

    此外,在Broker端,对Producer和Consumer都使用了Buffer机制。其中Buffer的大小是统一配置的,数量则与Partition个数相同。如果Partition个数过多,会导致Producer和Consumer的Buffer内存占用过大。

    Tips

    Partition的数量尽量提前预分配,虽然可以在后期动态增加Partition,但是会冒着可能破坏Message Key和Partition之间对应关系的风险。

    Replica的数量不要过多,如果条件允许尽量把Replica集合内的Partition分别调整到不同的Rack。

    尽一切努力保证每次停Broker时都可以Clean Shutdown,否则问题就不仅仅是恢复服务所需时间长,还可能出现数据损坏或其他很诡异的问题。

    Producer
    Kafka的研发团队表示在0.8版本里用Java重写了整个Producer,据说性能有了很大提升。我还没有亲自对比试用过,这里就不做数据对比了。本文结尾的扩展阅读里提到了一套我认为比较好的对照组,有兴趣的同学可以尝试一下。

    其实在Producer端的优化大部分消息系统采取的方式都比较单一,无非也就化零为整、同步变异步这么几种。

    Kafka系统默认支持MessageSet,把多条Message自动地打成一个Group后发送出去,均摊后拉低了每次通信的RTT。而且在组织MessageSet的同时,还可以把数据重新排序,从爆发流式的随机写入优化成较为平稳的线性写入。

    此外,还要着重介绍的一点是,Producer支持End-to-End的压缩。数据在本地压缩后放到网络上传输,在Broker一般不解压(除非指定要Deep-Iteration),直至消息被Consume之后在客户端解压。

    当然用户也可以选择自己在应用层上做压缩和解压的工作(毕竟Kafka目前支持的压缩算法有限,只有GZIP和Snappy),不过这样做反而会意外的降低效率!!!!Kafka的End-to-End压缩与MessageSet配合在一起工作效果最佳,上面的做法直接割裂了两者间联系。至于道理其实很简单,压缩算法中一条基本的原理“重复的数据量越多,压缩比越高”。无关于消息体的内容,无关于消息体的数量,大多数情况下输入数据量大一些会取得更好的压缩比。

    不过Kafka采用MessageSet也导致在可用性上一定程度的妥协。每次发送数据时,Producer都是send()之后就认为已经发送出去了,但其实大多数情况下消息还在内存的MessageSet当中,尚未发送到网络,这时候如果Producer挂掉,那就会出现丢数据的情况。

    为了解决这个问题,Kafka在0.8版本的设计借鉴了网络当中的ack机制。如果对性能要求较高,又能在一定程度上允许Message的丢失,那就可以设置request.required.acks=0 来关闭ack,以全速发送。如果需要对发送的消息进行确认,就需要设置request.required.acks为1或-1,那么1和-1又有什么区别呢?这里又要提到前面聊的有关Replica数量问题。如果配置为1,表示消息只需要被Leader接收并确认即可,其他的Replica可以进行异步拉取无需立即进行确认,在保证可靠性的同时又不会把效率拉得很低。如果设置为-1,表示消息要Commit到该Partition的ISR集合中的所有Replica后,才可以返回ack,消息的发送会更安全,而整个过程的延迟会随着Replica的数量正比增长,这里就需要根据不同的需求做相应的优化。

    Tips

    Producer的线程不要配置过多,尤其是在Mirror或者Migration中使用的时候,会加剧目标集群Partition消息乱序的情况(如果你的应用场景对消息顺序很敏感的话)。

    0.8版本的request.required.acks默认是0(同0.7)。

    Consumer
    Consumer端的设计大体上还算是比较常规的。

    • 通过Consumer Group,可以支持生产者消费者和队列访问两种模式。

    • Consumer API分为High level和Low level两种。前一种重度依赖Zookeeper,所以性能差一些且不自由,但是超省心。第二种不依赖Zookeeper服务,无论从自由度和性能上都有更好的表现,但是所有的异常(Leader迁移、Offset越界、Broker宕机等)和Offset的维护都需要自行处理。

    • 大家可以关注下不日发布的0.9 Release。开发人员又用Java重写了一套Consumer。把两套API合并在一起,同时去掉了对Zookeeper的依赖。据说性能有大幅度提升哦~~

    Tips

    强烈推荐使用Low level API,虽然繁琐一些,但是目前只有这个API可以对Error数据进行自定义处理,尤其是处理Broker异常或由于Unclean Shutdown导致的Corrupted Data时,否则无法Skip只能等着“坏消息”在Broker上被Rotate掉,在此期间该Replica将会一直处于不可用状态。

    那么Kafka如何做到能支持能同时发送大量消息的呢?
    答案是Kafka通过批量压缩和发送做到的。

    我们知道消息肯定是放在内存中的,大数据场景消息的不断发送,内存中不断存在大量的消息,很容易引起GC

    频繁的GC特别是full gc是会造成“stop the world”,也就是其他线程停止工作等待垃圾回收线程执行,继而进一步影响发送的速度影响吞吐量,那么Kafka是如何做到优化JVM的GC问题的呢?看完本篇文章你会get到。

    Kafka的内存池
    下面介绍下Kafka客户端发送的大致过程,如下图:

    在这里插入图片描述

    Kafka的kafkaProducer对象是线程安全的,每个发送线程在发送消息时候共用一个kafkaProducer对象来调用发送方法,最后发送的数据根据Topic和分区的不同被组装进某一个RecordBatch中。

    发送的数据放入RecordBatch后会被发送线程批量取出组装成ProduceRequest对象发送给Kafka服务端。

    可以看到发送数据线程和取数据线程都要跟内存中的RecordBatch打交道,RecordBatch是存储数据的对象,那么RecordBatch是怎么分配的呢?

    下面我们看下Kafka的缓冲池结构,如下图所示:
    在这里插入图片描述

    名词解释:缓冲池:BufferPool(缓冲池)对象,整个KafkaProducer实例中只有一个BufferPool对象。内存池总大小,它是已使用空间和可使用空间的总和,用totalMemory表示(由buffer.memory配置,默认32M)。

    可使用的空间:它包含包括两个部分,绿色部分代表未申请未使用的部分,用availableMemory表示

    黄色部分代表已经申请但没有使用的部分,用一个ByteBuffer双端队列(Deque)表示,在BufferPool中这个队列叫free,队列中的每个ByteBuffer的大小用poolableSize表示(由batch.size配置,默认16k),因为每次free申请内存都是以poolableSize为单位申请的,申请poolableSize大小的bytebuffer后用RecordBatch来包装起来。

    已使用空间:代表缓冲池中已经装了数据的部分。

    根据以上介绍,我们可以知道,总的BufferPool大小=已使用空间+可使用空间;free的大小=free.size * poolableSize(poolsize就是单位batch的size)。

    数据的分配过程 总的来说是判断需要存储的数据的大小是否free里有合适的recordBatch装得下

    如果装得下则用recordBatch来存储数据,如果free里没有空间但是availableMemory+free的大小比需要存储的数据大(也就是说可使用空间比实际需要申请的空间大),说明可使用空间大小足够,则会用让free一直释放byteBuffer空间直到有空间装得下要存储的数据位置,如果需要申请的空间比实际可使用空间大,则内存申请会阻塞直到申请到足够的内存为止。

    整个申请过程如下图:

    在这里插入图片描述

    数据的释放过程 总的来说有2个入口,释放过程如下图:

    在这里插入图片描述

    再来看段申请空间代码:

    //判断需要申请空间大小,如果需要申请空间大小比batchSize小,那么申请大小就是batchsize,如果比batchSize大,那么大小以实际申请大小为准
    int size = Math.max(this.batchSize, Records.LOG_OVERHEAD + Record.recordSize(key, value));
    log.trace("Allocating a new {} byte message buffer for topic {} partition {}", size, tp.topic(), tp.partition());
    //这个过程可以参考图3
    ByteBuffer buffer = free.allocate(size, maxTimeToBlock);
    

    再来段回收的核心代码:

    public void deallocate(ByteBuffer buffer, int size) {    lock.lock();    try {        //只有标准规格(bytebuffer空间大小和poolableSize大小一致的才放入free)        if (size == this.poolableSize && size == buffer.capacity()) {            //注意这里的buffer是直接reset了,重新reset后可以重复利用,没有gc问题            buffer.clear();            //添加进free循环利用            this.free.add(buffer);        } else {            //规格不是poolableSize大小的那么没有进行重制,但是会把availableMemory增加,代表整个可用内存空间增加了,这个时候buffer的回收依赖jvm的gc            this.availableMemory += size;        }        //唤醒排在前面的等待线程        Condition moreMem = this.waiters.peekFirst();        if (moreMem != null)            moreMem.signal();    } finally {        lock.unlock();    }}
    

    通过申请和释放过程流程图以及释放空间代码,我们可以得到一个结论

    就是如果用户申请的数据(发送的消息)大小都是在poolableSize(由batch.size配置,默认16k)以内,并且申请时候free里有空间,那么用户申请的空间是可以循环利用的空间,可以减少gc,但是其他情况也可能存在直接用堆内存申请空间的情况,存在gc的情况。

    如何尽量避免呢,如果批量消息里面单个消息都是超过16k,可以考虑调整batchSize大小。

    如果没有使用缓冲池,那么用户发送的模型是下图5,由于GC特别是Full GC的存在,如果大量发送,就可能会发生频繁的垃圾回收,导致的工作线程的停顿,会对整个发送性能,吞吐量延迟等都有影响。

    在这里插入图片描述

    使用缓冲池后,整个使用过程可以缩略为下图:

    在这里插入图片描述

    展开全文
  • “ 这篇文章,同样给大家聊一个硬核的技术知识,我们通过Kafka内核源码中的一些设计思想,来看你设计Kafka架构的技术大牛,是怎么优化JVM的GC问题的?  1、Kafka的客户端缓冲机制  首先,先得给大家明确一个事情,...
  • 今天我们从kafka架构以如何优化GC两个方面讲解kafka架构既然要说kafka是如何通过内存缓冲池设计来优化JVM的GC问题,那么,如果不清楚kafka 的架构设计,又怎么更好的调优呢?起码的我们要知道...
  • 前言这篇文章,同样给大家聊一个硬核的技术知识,我们通过Kafka内核源码中的一些设计思想,来看你设计Kafka架构的技术大牛,是怎么优化JVM的GC问题的?1、Kafka的客户端缓冲机制首先,先得给大家明确一个事情,那...
  • 既然要说kafka是如何通过内存缓冲池设计来优化JVM的GC问题,那么,如果不清楚kafka 的架构设计,又怎么更好的调优呢?起码的我们要知道基础的才能往更好的出发呀,对把 先来看技术名词 Topic:用于划分Message的...
  • 这篇文章,同样给大家聊一个硬核的技术知识,我们通过Kafka内核源码中的一些设计思想,来看你设计Kafka架构的技术大牛,是怎么优化JVM的GC问题的?1、Kafka的客户端缓冲机制首先,先得给大家明确一个事情,那就是在...
  • 本文转载自:https://www.rowkey.me/blog/2016/11/02/java-profile/作者:飒然目录调优准备性能分析性能调优其他优化建议JVM参数进阶对于调优这个事情来说,一般就是三个过程:性能监控:问题没有发生,你并不知道你...
  • Java性能优化JVM GC

    2018-02-02 09:28:07
    Java性能优化JVM GC(垃圾回收机制) 韦庆明 https://zhuanlan.zhihu.com/p/25539690 Java性能优化,整理出一篇文章,供以后温故知新。 JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住...
  • JVM的GC机制

    2020-08-20 17:05:37
    JVM的GC概述 GC即垃圾回收,是指jvm用于释放那些不再使用的对象所占用的内存。 在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。 有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是...
  • JVM的GC概述

    2019-09-28 13:12:12
    JVM的GC概述 GC即垃圾回收,是指jvm用于释放那些不再使用的对象所占用的内存。在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。 有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是为了...
  • JVM的gc概述

    2015-06-12 11:27:12
    1.JVM的gc概述  gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作...
  • JVM GC优化

    千次阅读 2018-06-03 09:44:45
    查看JVM的GC过程 在启动你的程序的时候,使用-verbose:gc 参数,将程序的 GC情况输出至 log 文本文件中。对log 文件进行简要分析。 输入的参数为“-verbose:gc -Xloggc:gc.log -XX:+PrintGCTimeStamps -XX:+...
  • JVM内存管理 JVM内存数据区域分布如下图: 各个区域存放数据类型如下图: 1、线程私有区域 程序计数器PC:相当于一个执行代码指示器,用来确认下一行执行代码地址;每个线程都有一个程序计数器;该...
  • JVM GC优化思路

    2019-08-22 10:18:37
    JVM的GC一般分为Young GC和Full GC,而Full GC由于STW(stop the world)需要消耗的时间一般情况比Young GC要多很多,所以GC优化思路是尽量减少Full GC的频率,减少STW以提升性能。 现在我们知道了JVM优化的方向是...
  • 一.背景 最近公司官网隔段时间出现个别人访问处于加载白页情况,判断可能nginx负载(用ip_hash方法)upstream下负载点出现了问题,在逐个访问时... Java heap space”,java堆内存溢出属于jvm最大堆内存-X...
  • 本节主要描述关于垃圾回收器性能三个指标,三个关于垃圾回收器优化的基本原则,以及优化HotSpot VM垃圾回收器信息收集,在这些指标中权衡以及信息收集是非常重要。 性能指标 吞吐量:衡量垃圾回收器...
  • JVMGC以及优化

    2018-02-28 05:30:30
    过完年之后感觉人生荒废 说好的不断学习无奈老婆家的习俗太多 什么办新郎官饭什么的赶场太多 根本没时间 都是开车下车吃饭看电影回家洗漱后已经是大半夜…...Java对比C/C++而言,JVM的存在可谓是一个比较良心的产物,...
  • 安卓内存优化1(JVMGC原理)JAVA虚拟机线程私有1 程序计数器PC2 虚拟机栈3 本地方法栈共享数据区 JAVA虚拟机 线程私有 1 程序计数器PC 相当于一个执行代码指示器,用来确认下一行执行代码地址,每个线程都...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,640
精华内容 1,056
热门标签
关键字:

优化jvm的gc