串行垃圾回收器

JDK 1.3之前的垃圾回收器,单线程回收,并且会有stop theworld(下文会简称STW),也即GC时,暂停所有用户线程。其运行方式是单线程的,适合Client模式的应用,适合单CPU环境。串行的垃圾收集器有两种,Serial以及SerialOld,一般会搭配使用。新生代使用Serial采取复制算法,老年代使用Serial Old采取标记整理算法。Client应用或者命令行程序可以,通过 -XX:+UseSerialGC 可以开启上述回收模式。

  • Serial:用于新生代垃圾收集,复制算法
  • SerialOld:用于老年代垃圾收集,标记整理算法
  • 并行垃圾回收器

    整体来说,并行垃圾回收相对于串行,是通过多线程运行垃圾收集的。也会stop-the-world。适合Server模式以及多CPU环境。一般会和jdk1.5之后出现的CMS搭配使用。并行的垃圾回收器有以下几种:

  • ParNew:Serial收集器的多线程版本,默认开启的收集线程数和cpu数量一样,运行数量可以通过修改ParallelGCThreads设定。用于新生代收集,复制算法。使用 -XX:+UseParNewGC ,和Serial Old收集器组合进行内存回收。
  • Parallel Scavenge: 关注吞吐量,吞吐量优先,吞吐量=代码运行时间/(代码运行时间+垃圾收集时间),也就是高效率利用cpu时间,尽快完成程序的运算任务
    可以设置最大停顿时间MaxGCPauseMillis以及,吞吐量大小GCTimeRatio。如果设置了-XX:+UseAdaptiveSizePolicy参数,则随着GC,会动态调整新生代的大小,Eden,Survivor比例等,以提供最合适的停顿时间或者最大的吞吐量。用于新生代收集,复制算法。通过 -XX:+UseParallelGC 参数,Server模式下默认提供了其和SerialOld进行搭配的分代收集方式。
  • Parllel Old:Parallel Scavenge的老年代版本。JDK 1.6开始提供的。在此之前Parallel Scavenge的地位也很尴尬,而有了Parllel Old之后,通过 -XX:+UseParallelOldGC 参数使用Parallel Scavenge + Parallel Old器组合进行内存回收。
  • 并发标记扫描垃圾回收器(CMS)

    CMS(Concurrent Mark Sweep)基于“标记—清除”算法,用于老年代,所以其关注点在于减少“pause time”也即因垃圾回收导致的stop the world时间。对于重视服务的响应速度的应用可以使用CMS。因为CMS是“并发”运行的,也即垃圾收集线程可以和用户线程同时运行。 缺点就是会产生内存碎片。
    CMS的回收分为几个阶段:

  • 初始标记:标记一下GC Roots能直接关联到的对象,会“Stop The World”
  • 并发标记:GC Roots Tracing,可以和用户线程并发执行。
  • 重新标记:标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短,会“Stop The World”。
  • 并发清除:清除对象,可以和用户线程并发执行。
  • CMS最主要解决了pause time,但是会占用CPU资源,牺牲吞吐量。CMS默认启动的回收线程数是(CPU数量+3)/ 4,当CPU<4个时,会影响用户线程的执行。另外一个缺点就是内存碎片的问题了,碎片会给大对象的内存分配造成麻烦,如果老年代的可用的连续空间也无法分配时,会触发full gc。并且full gc时如果发生young gc会被young gc打断,执行完young gc之后再继续执行full gc。
    -XX:UseConcMarkSweepGC 参数可以开启CMS,年轻代使用ParNew,老年代使用CMS,同时Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用。

    G1垃圾收集器

    G1(Garbage-First)是在JDK 7u4版本之后发布的垃圾收集器,并在jdk9中成为默认垃圾收集器。通过“-XX:+UseG1GC”启动参数即可指定使用G1 GC。从整体来说,G1也是利用多CPU来缩短stop the world时间,并且是高效的 并发 垃圾收集器。但是G1不再像上文所述的垃圾收集器,需要分代配合不同的垃圾收集器,因为G1中的垃圾收集区域是“分区”(Region)的。G1的分代收集和以上垃圾收集器不同的就是除了有年轻代的ygc,全堆扫描的fullgc外,还有包含所有年轻代以及部分老年代Region的MixedGC。G1的优势还有可以通过调整参数,指定垃圾收集的最大允许pause time。下面会详细阐述下G1分区以及分代的概念,以及G1 GC的几种收集过程的分类。

    G1分区的概念

    在G1之前的垃圾收集器,将堆区主要划分了Eden区,Old区,Survivor区。其中对于Eden,Survivor对回收过程来说叫做“年轻代垃圾收集”。并且年轻代和老年代都分别是连续的内存空间。
    G1将堆分成了若干Region,以下和”分区”代表同一概念。Region的大小可以通过 G1HeapRegionSize 参数进行设置,其必须是2的幂,范围允许为1Mb到32Mb。 JVM的会基于堆内存的初始值和最大值的平均数计算分区的尺寸,平均的堆尺寸会分出约2000个Region。分区大小一旦设置,则启动之后不会再变化。如下图简单画了下G1分区模型。

  • Eden regions(年轻代-Eden区)
  • Survivor regions(年轻代-Survivor区)
  • Old regions(老年代)
  • Humongous regions(巨型对象区域)
  • Free resgions(未分配区域,也会叫做可用分区)-上图中空白的区域
  • 关于分区有几个重要的概念:

  • G1还是采用分代回收,但是不同的分代之间内存不一定是连续的,不同分代的Region的占用数也不一定是固定的(不建议通过相关选项显式设置年轻代大小。会覆盖暂停时间目标。)。年轻代的Eden,Survivor数量会随着每一次GC发生相应的改变。
  • 分区是不固定属于哪个分代的,所以比如一次ygc过后,原来的Eden的分区就会变成空闲的可用分区,随后也可能被用作分配巨型对象,成为H区等。
  • G1中的巨型对象是指,占用了Region容量的50%以上的 一个对象 。Humongous区,就专门用来存储巨型对象。如果一个H区装不下一个巨型对象,则会通过连续的若干H分区来存储。因为巨型对象的转移会影响GC效率,所以并发标记阶段发现巨型对象不再存活时,会将其直接回收。ygc也会在某些情况下对巨型对象进行回收。
  • 通过上图可以看出,分区可以有效利用内存空间,因为收集整体是使用“标记-整理”,Region之间基于“复制”算法,GC后会将存活对象复制到可用分区(未分配的分区),所以不会产生空间碎片。
  • G1类似CMS,也会在比如一次fullgc中基于堆尺寸的计算重新调整(增加)堆的空间。但是相较于执行fullgc,G1 GC会在无法分配对象或者巨型对象无法获得连续分区来分配空间时,优先尝试扩展堆空间来获得更多的可用分区。原则上就是G1会计算执行GC的时间,并且极力减少花在GC上的时间(包括ygc,mixgc),如果可能,会通过不断扩展堆空间来满足对象分配、转移的需要。
  • 因为G1提供了“可预测的暂停时间”,也是基于G1的启发式算法,所以G1会估算年轻代需要多少分区,以及还有多少分区要被回收。ygc触发的契机就是在Eden分区数量达到上限时。一次ygc会回收所有的Eden和survivor区。其中存活的对象会被转移到另一个新的survivor区或者old区,如果转移的目标分区满了,会再将可用区标记成S或者O区。
  • TLAB(Thread Local Allocation Buffer)本地线程缓冲区

    G1 GC会默认会启用Tlab优化。其作用就是在并发情况下,基于CAS的独享线程(mutator threads)可以优先将对象分配在一块内存区域(属于Java堆的Eden中),只是因为是Java线程独享的内存区,没有锁竞争,所以分配速度更快,每个Tlab都是一个线程独享的。如果待分配的对象被判断是巨型对象,则不使用TLAB。
    下面把TLAB分配对象内存的open jdk部分源码附上,有助理解。

    HeapWord * G1CollectedHeap ::allocate_new_tlab (size_t min_size, size_t requested_size, size_t * actual_size) { assert_heap_not_locked_and_not_at_safepoint(); assert( ! is_humongous(requested_size), "we do not allow humongous TLABs" ); return attempt_allocation(min_size, requested_size, actual_size); inline HeapWord * G1CollectedHeap ::attempt_allocation (size_t min_word_size, size_t desired_word_size, size_t * actual_word_size) { assert_heap_not_locked_and_not_at_safepoint(); // 排除巨型对象 assert( ! is_humongous(desired_word_size), "attempt_allocation() should not " "be called for humongous allocation requests" ); // 在当前的region分配 HeapWord * result = _allocator -> attempt_allocation(min_word_size, desired_word_size, actual_word_size); // 可用空间不够,申请新的region分配 if (result == NULL ) { * actual_word_size = desired_word_size; // 可能存在多线程申请,所以通过加锁的方式申请,如果young区没有超出阀值,则会获取新的region result = attempt_allocation_slow(desired_word_size); // 判断没有因gc导致堆locked assert_heap_not_locked(); if (result != NULL ) { assert( * actual_word_size != 0 , "Actual size must have been set here" ); // 脏化年轻代的card(卡片)数据 dirty_young_block(result, * actual_word_size); } else { * actual_word_size = 0 ; return result;

    Remembered Sets(RSets)已记忆集合

    已记忆集合在每个分区中都存在,并且每个分区只有一个RSet。其中存储着 其他分区中的对象对本分区对象的引用 ,是一种points-in结构。ygc的时候,只要扫描RSet中的其他old区对象对于本young区的引用,不需要扫描所有old区。mixed gc时,扫描Old区的RSet中,其他old区对于本old分区的引用,一样不用扫描所有的old区。提高了GC效率。因为每次GC都会扫描所有young区对象,所以RSet只有在扫描old引用young,old引用old时会被使用。
    为了防止RSet溢出,对于一些比较“Hot”的RSet会通过存储粒度级别来控制。RSet有三种粒度,对于“Hot”的RSet在存储时,根据细粒度的存储阀值,可能会采取粗粒度。
    这三种粒度的RSet都是通过 PerRegionTable 来维护内部数据的。可以查看其部分源码如下:

    class PerRegionTable : public CHeapObj < mtGC > { friend class OtherRegionsTable ; friend class HeapRegionRemSetIterator ; HeapRegion * _hr; // 来自其他分区的引用 CHeapBitMap _bm; // card索引存放的位图 jint _occupied; // 已占用的容量 / / next pointer for free/allocated 'all' list PerRegionTable * _next; // prev pointer for the allocated 'all' list PerRegionTable * _prev; // next pointer in collision list PerRegionTable * _collision_list_next; // Global free list of PRTs static PerRegionTable * volatile _free_list;

    简要结构如下图( 图片来源

    下面是三种粒度级别,以及对应的简要数据结构:

  • 细粒度(fine),其PerRegionTable存储了所有对于本Resgion的引用的卡片的索引,其卡片索引都存储在 CHeapBitMap 结构里。伪代码类似:hash_map
  • Snapshot-At-The-Beginning(SATB)

    SATB是在G1 GC在并发标记阶段使用的增量式的标记算法。并发标记是并发多线程的,但并发线程在同一时刻只扫描一个分区。
    在解释SATB前先要了解三色标记法。三色标记法是将对象的存活状态用三种颜色标记,从黑色到灰色逐层标记:

  • 黑:该对象被标记了,并且其引用的对象也都被标记完成。
  • 灰:对象被标记了,但其引用的对象还没有被标记完。
  • 白:对象还没有被标记,标记阶段结束后,会被回收。
  • 在CMS GC中,并发标记阶段使用的是Incremental update批量更新算法,在增加引用时的写屏障中触发新的对象引用的标记(三色标记法)。
    G1的并发标记算法,使用的是SATB。在GC开始时先创建一个对象快照,STAB可以在并发标记时标记所有快照中当时的存活对象。标记过程中新分配的对象也会被标记为存活对象,不会被回收。STAB核心的两个结构就是两个BitMap。如下:

     // from G1ConcurrentMark-可以认为Bitmap的内部存储着对象地址(reference 是8byte,所以Bitmap存储着一个个64bit结构)
     G1CMBitMap*             _prev_mark_bitmap; //  全局的bitmap,存储PTAMS偏移位置,也即当前标记的对象的地址(初始值是对应上次已经标记完成的地址)
     G1CMBitMap*             _next_mark_bitmap; // 全局的bitmap,存储NTAMS偏移位置。标记过程不断移动,标记完成后会和prev_map 互换。 

    bitmap分别存储着每个分区中,并发标记过程里的两个重要的变量:PTAMS(pre-top-at-mark-start,代表着分区上一次完成标记的位置) 以及NTAMS(next-top-at-mark-start,随着标记的进行会不断移动,一开始在top位置)。SATB通过控制两个变量的移动来进行标记。为了直观了解标记过程,如下图所示:( 原图论文

    A:初始标记,因为要扫描所有Root Trace可达的对象,会有STW的暂停时间,会将扫描分区的NTAMS值设置为分区的顶部(Top)。
    B:最终标记,因为并发导致会有新分配的对象,因为并发标记过程中对象会被分配到NTAMS~TOP中间的区域。这些对象会被定义为”隐式对象“。因为NTAMS有很多值了,所以_next_mark_bitmap也会开始存储NTAMS标记的对象的地址。
    C:清除阶段:_next_mark_bitmap和_prev_mark_bitmap会进行Swap。PTAMS和NTAMS也会互换值。清除所有Bottom~PTAMS的对象。对于”隐式对象“会在下次垃圾收集过程进行回收(如图F过程)。这也是SATB存在弊端,会一定程度产生未能在本次标记中识别的浮动垃圾。

    另,以上过程省略了根分区扫描和并发标记。上图是包含了两次标记过程,主要是为了展示B-E过程中,并发情况新对象的分配。

    G1 GC的分类和过程

    JDK10 之前的G1中的GC只有YoungGC,MixedGC。FullGC处理会交给单线程的Serial Old垃圾收集器。

    YoungGC年轻代收集

    在分配一般对象(非巨型对象)时,当所有eden region使用达到最大阀值并且无法申请足够内存时,会触发一次YoungGC。每次younggc会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。到Old区的标准就是在PLAB中得到的计算结果。因为YoungGC会进行根扫描,所以会stop the world。

    YoungGC的回收过程如下:

  • 根扫描,跟CMS类似,Stop the world,扫描GC Roots对象。
  • 处理Dirty card,更新RSet.
  • 扫描RSet,扫描RSet中所有old区对扫描到的young区或者survivor去的引用。
  • 拷贝扫描出的存活的对象到survivor2/old区
  • 处理引用队列,软引用,弱引用,虚引用(下一篇优化中会再讲一下这三种引用对gc的影响)
  • MixGC混合收集

    MixedGC是G1 GC特有的,跟Full GC不同的是Mixed GC只回收部分老年代的Region。哪些old region能够放到CSet里面,有很多参数可以控制。比如 G1HeapWastePercent 参数,在一次younggc之后,可以允许的堆垃圾百占比,超过这个值就会触发mixedGC。 G1MixedGCLiveThresholdPercent 参数控制的,old代分区中的存活对象比,达到阀值时,这个old分区会被放入CSet。源码可以看下 gc/g1/collectionSetChooser
    MixedGC一般会发生在一次YoungGC后面,为了提高效率,MixedGC会复用YoungGC的全局的根扫描结果,因为这个Stop the world过程是必须的,整体上来说缩短了暂停时间。

    MixGC的回收过程可以理解为YoungGC后附加的全局concurrent marking,全局的并发标记主要用来处理old区(包含H区)的存活对象标记,过程如下:

    1. 初始标记(InitingMark)。标记GC Roots,会STW,一般会复用YoungGC的暂停时间。如前文所述,初始标记会设置好所有分区的 NTAMS 值。
    2. 根分区扫描(RootRegionScan)。这个阶段GC的线程可以和应用线程并发运行。其主要扫描初始标记以及之前YoungGC对象转移到的Survivor分区,并标记Survivor区中引用的对象。所以此阶段的Survivor分区也叫根分区(RootRegion)。部分源码如下:

    // 当有需要扫描的的S分区时,该Task会被开启,扫描后会执行scan_finished,notify其他GC活动,如youngGC  
    class G1CMRootRegionScanTask : public AbstractGangTask {
      G1ConcurrentMark* _cm;
    public:
      G1CMRootRegionScanTask(G1ConcurrentMark* cm) :
        AbstractGangTask("G1 Root Region Scan"), _cm(cm) { }
      void work(uint worker_id) {
        assert(Thread::current()->is_ConcurrentGC_thread(),
               "this should only be done by a conc GC thread");
        G1CMRootRegions* root_regions = _cm->root_regions();  //   _root_regions 初始化为待扫描的Survivor分区。   
        HeapRegion* hr = root_regions->claim_next();
        while (hr != NULL) { // 循环分别处理所有待扫描的S分区   
          _cm->scan_root_region(hr, worker_id);  //方法如下
          hr = root_regions->claim_next();
      // 扫描Survivor区 (HeapRegion* hr)   
    void G1ConcurrentMark::scan_root_region(HeapRegion* hr, uint worker_id) {
      assert(hr->next_top_at_mark_start() == hr->bottom(), "invariant");
      G1RootRegionScanClosure cl(_g1h, this, worker_id);
      const uintx interval = PrefetchScanIntervalInBytes; 
      HeapWord* curr = hr->bottom();   // 扫描分区的bottom
      const HeapWord* end = hr->top(); // 扫描分区的top
      while (curr < end) { // 扫描所有bottom到top的分区的对象  
        Prefetch::read(curr, interval);
        oop obj = oop(curr);
        int size = obj->oop_iterate_size(&cl);
        assert(size == obj->size(), "sanity");
        curr += size;
    

    3. 并发标记(ConcurrentMark)。会并发标记所有非完全空闲的分区的存活对象,也即使用了SATB算法,标记各个分区。
    4. 最终标记(Remark)。主要处理SATB缓冲区,以及并发标记阶段未标记到的漏网之鱼(存活对象),会STW,可以参考上文的SATB处理。
    5. 清除阶段(Clean UP)。上述SATB也提到了,会进行bitmap的swap,以及PTAMS,NTAMS互换。整理堆分区,调整相应的RSet(比如如果其中记录的Card中的对象都被回收,则这个卡片的也会从RSet中移除),如果识别到了完全空的分区,则会清理这个分区的RSet。这个过程会STW。

    清除阶段之后,还会对存活对象进行转移(复制算法),转移到其他可用分区,所以当前的分区就变成了新的可用分区。复制转移主要是为了解决分区内的碎片问题。

    FullGC

    G1在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发FullGC。FullGC使用的是stop the world的单线程的Serial Old模式,所以一旦触发FullGC则会STW应用线程,并且执行效率很慢。JDK 8版本的G1是不提供Full gc的处理的。对于G1 GC的优化,很大的目标就是没有FullGC。

    文章对目前JVM的几种垃圾收集器做了简单总结。详细梳理了一下G1 GC的关键概念。本来想一起把G1 GC参数优化和GC Log也加上的,但篇幅有点长了,下一篇会加上[TODO->G1 垃圾收集器性能调优篇]。
    本文内容都是基于JDK 8的版本的,在jdk10版本的G1 GC会有很多优化。Full CG方面,将提供并发标记的Full GC方案:Parallelize Mark-Sweep-Compact。Card Table的扫描也会得到加速。RSet也优化了,目前的RSet会存储在所有的分区里,新版本的RSet只需要在CSet中,并且是在Remark到Clean阶段之间并发构建RSet。这项优化会增加整个并发标记的周期,但是缩减了很多RSet的占用空间。另外,对于PauseTime会有更精准的处理,在MixedGC的对象拷贝阶段,提供了可放弃拷贝的(Abortable)选项。MixedGC会计算下一个Region的对象拷贝,如果可能会超过预期的pause time,则会放弃这次拷贝。对于JDK10的G1 GC更多信息可以看一下2018-Oracle G1 GC

    Garbage-First Garbage Collection
    introduction-g1-garbage-collector
    tuning-tips-G1-GC
    2018-Oracle G1 GC

    点击上面↑「爱开发」关注我们每晚10点,捕获技术思考和创业资源洞察什么是ThreadLocalThreadLocal是一个本地线程副本变量工具类,各个线程都拥有一份线程私...... 来自: 爱开发 前言关于G1 GC以及其他垃圾收集器的介绍可以参考前一篇JVM性能调优实践——G1 垃圾收集器介绍篇。了解了G1垃圾收集器的运行机制之后,就可以针对一些GC相关参数来调整内存分配以及运行策略。下文的调... 来自: 马猴烧酒└(┐卍^o^)卍ドゥルルル~楪詩月「YUZURIHA SHIZUKI」 版权声明:本文为博主原创文章,转载请联系作者并注明出处。详解JVMGarbageFirst(G1)垃圾收集器前言GarbageFirst(G1)是垃圾收集领域的最新成果,同时也是HotSpot在JVM... 来自: coderlius的博客 G1垃圾收集器入门说明concurrent:并发,多个线程协同做同一件事情(有状态)parallel:并行,多个线程各做各的事情(互相间无共享状态)在GC领域:concurrent算法指GC线程和业务... 来自: 铁锚的CSDN博客 并发收集器 Parallel Grabage Collector: 垃圾收回线程工作的同时,应用程序也正常工作,从而系统响应性较好,但垃圾回收过程需要更多的时间。并行收集器 Concurrent Ga... 来自: homtoyeah的博客 很多人都问,技术人员如何成长,每个阶段又是怎样的,如何才能走出当前的迷茫,实现自我的突破。所以我结合我自己10多年的从业经验,总结了技术人员成长的9个段位,希望对大家的职...... 来自: Python之禅的专栏 G1收集器是在JDK1.7开始可以设置使用,在JDK1.9时设置为默认垃圾收集器。G1收集器和其他收集器相比有以下特点并行与并发:G1能充分利用多CPU、多核的硬件优势,来缩短Stop-The-Wor... 来自: summerZBH123的博客 新增:使用IDEA搭建SpringBoot框架整合Mybatis、MySQL、Thymeleaf实现用户查询、注册、登录一、Java基础Java基础-继承 Java基础-抽象 Java基础-接口 Ja... 来自: oneStar的博客 JVM G1源码分析和调优目录:第1章 垃圾回收概述 11.1 Java发展概述 11.2 本书常见术语 41.3 回收算法概述 61.3.1 分代管理算法 71.3.2 复制算法 71.3.3 标记... 来自: itmrchen的博客 点击上方“程序员小明”,选择“星标”今晚可以不加班!网络上虽然已经有了很多关于程序员的话题,但大部分人对这个群体还是很陌生。我们在谈论程序员的时候,究竟该聊些什么呢?各位...... 来自: 程序员小明 来源:https://blog.csdn.net/u013542440/article/details/79358071 G1收集器一. 名词解释MetaSpa... 来自: 翻身咸鱼的博客 前言在遇到实际性能问题时,除了关注系统性能指标。还要结合应用程序的系统的日志、堆栈信息、GClog、threaddump等数据进行问题分析和定位。关于性能指标分析可以参考前一篇JVM性能调优实践——性... 来自: 马猴烧酒└(┐卍^o^)卍ドゥルルル~楪詩月「YUZURIHA SHIZUKI」 原文地址:http://blog.jobbole.com/109170/?utm_source=hao.jobbole.com&utm_medium=relatedArticle本文首先简单介... 引子Hacker(黑客),往往被人们理解为只会用非法手段来破坏网络安全的计算机高手。但是,黑客其实不是这样的,真正的“网络破坏者”是和黑客名称和读音相似的骇客。骇客,是用黑客手段进行非法操作并为己取得... 来自: tiantian520ttjs——Python程序猿~ 1.以前收集器特点年轻代和老年代是各自独立且连续的内存块年轻代收集使用单eden+S0+s进行复制算法老年代收集必须扫描整个老年代区域都是以尽可能少而快速地执行GC为设计原则。2.概念G1(Garba... 来自: 郝大侠的博客 目录1、个人申请2、团队申请3、企业/组织申请3.1、个人/企业/组织和企业/组织联合申请4、补充最近和团队做一个项目:基于NB-IoT技术的城市道路智慧路灯监控系统,决定申请计算机软件著作权便于保护... 来自: 不脱发的程序猿 来源:苦逼的码农(ID:di201805)前言天各一方的两台计算机是如何通信的呢?在成千上万的计算机中,为什么一台计算机能够准确着寻找到另外一台计算机,并且把数据发送给它...... 来自: Java团长的博客 当时买mac的初衷,只是想要个固态硬盘的笔记本,用来运行一些复杂的扑克软件。而看了当时所有的SSD笔记本后,最终决定,还是买个好(xiong)看(da)的。已经有好几个朋友问我mba怎么样了,所以今天... 来自: Diana5253的博客 前提引言:G1计划作为并发商标扫描收集器(CMS)的长期替代品。将G1与CMS进行比较可以发现G1的差异,这是更好的解决方案。一个区别是G1是一个压缩收集器。此外,G1提供比CMS收集器更多的可预测垃... 来自: wolf_love666的博客 可能很多人在大一的时候,就已经接触了递归了,不过,我敢保证很多人初学者刚开始接触递归的时候,是一脸懵逼的,我当初也是,给我的感觉就是,递归太神奇了!可能也有一大部分人知道递归,也能看的懂递归,但在实际... 来自: weixin_30216561的博客 1.为什么等待和通知是在 Object 类而不是 Thread 中声明的?一个棘手的 Java 问题,如果 Java编程语言不是你设计的,你怎么能回答这个问题呢。Java编程的常识和深入了解有助于回答... 来自: aaa13268的博客 (http://blog.jobbole.com/109170/)了解G1G1的第一篇paper(附录1)发表于2004年,在2012年才在jdk1.7u4中可用。oracle官方计划在jdk9中将G... 来自: qqqqq1993qqqqq的博客 前言本文主要基于工作中,关于性能调优的一些零散的信息整理。总结性的信息,以测试环境为例。系统信息如下: os: Linux 64位jdk:java version “1.8.0_121”, Hot... 来自: 马猴烧酒└(┐卍^o^)卍ドゥルルル~楪詩月「YUZURIHA SHIZUKI」 引言1、垃圾回收器需要做三件事: 分配内存:垃圾回收算法的设计往往制约了内存分配的方式; 确保存活对象不会被回收 回收垃圾对象(垃圾是指那些不再被使用的对象)2、对于垃圾回收器的回收来说,不管算... 来自: yecong111的专栏 原文:Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide--Garbage ... 来自: Soongp的博客 介绍    G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用。G1收集器是工作在堆内不同分区上的收集器,分区既可以是年轻代也可以是... 来自: 云袭的博客 G1 GC知识点:Region:1M~64M,2的幂,默认为其大小为将堆分为约2048个region为宜。可以通过-XX:G1HeapRegionSize来设定region大小,但是不推荐这么做,re... 来自: 张伟的专栏 最近翻到一篇知乎,上面有不少用Python(大多是turtle库)绘制的树图,感觉很漂亮,我整理了一下,挑了一些我觉得不错的代码分享给大家(这些我都测试过,确实可以生成)one 樱花树 动态生成樱花效... 来自: 碎片 1. 简介当晋升失败、疏散失败、大对象分配失败、Evac失败时,有可能触发Full GC,在JDK10之前,Full GC是串行的,JEP 307: Parallel Full GC for G1之后... 来自: 860MHz的专栏 此系列包含蓝桥杯所考察的绝大部分知识点,一共有==基础语法==,==常用API==,==基础算法和数据结构==,和==往年真题==四部分,虽然语言以JAVA为主,但算法部分是相通的,C++组的小伙伴也... 来自: GD_ONE的博客 我本科学校是渣渣二本,研究生学校是985,现在毕业五年,校招笔试、面试,社招面试参加了两年了,就我个人的经历来说下这个问题。这篇文章很长,但绝对是精华,相信我,读完以后,你会知道学历不好的解决方案,记... 来自: 启舰 前奏:今天2B哥和大家分享一位前几天面试的一位应聘者,工作4年26岁,统招本科。以下就是他的简历和面试情况。基本情况:专业技能:1、 熟悉Sping了解SpringMVC、SpringBoo... 来自: HarderXin的专栏 点击“技术领导力”关注∆每天早上8:30推送作者|Mr.K 编辑| Emma来源|技术领导力(ID:jishulingdaoli)前天的推文《冯唐:职场人35岁以后,方法论比经验重要》,收到了不少读者... 来自: 技术领导力 首先跟大家说明一点,我们做 IT 类的外包开发,是非标品开发,所以很有可能在开发过程中会有这样那样的需求修改,而这种需求修改很容易造成扯皮,进而影响到费用支付,甚至出现做完了项目收不到钱的情况。那么,... 来自: DavidGoGo_的博客 luo609630199:[reply]qq464135282[/reply] 4.7.4。该StackMapTable属性 该StackMapTable 属性是属性attributes表中的可变长度Code属性(第4.7.3节)。甲StackMapTable 属性验证的由类型检查(处理期间使用§4.10.1)。 一个StackMapTable属性的attributes表中最多可以有 一个Code 属性。 在class版本号为50.0或更高版本的文件中,如果方法的Code属性没有StackMapTable属性,则它具有 隐式堆栈映射属性 (第4.10.1节)。此隐式堆栈映射属性等效于等于零的StackMapTable属性 number_of_entries。

    基于Spring-statemac... u012455387:[reply]zh_haining[/reply] 我来说一下我这边的处理方式,如果所有人公用一个状态机肯定是不行的,如果加锁的话,秒杀等订单业务突增的情况会有很大的性能瓶颈,用状态机主要就是把复杂状态转换业务解耦出来,牺牲了性能就得不尝失了。 我的做法是,一个状态机对应一个订单,然后把状态机对象池化,可以理解成类似数据库连接池的处理。然后一个请求进来从池里获取一个状态机对象后,reset一下context,我们context初始化是从ElasticSearch获取构造出来的,然后状态更变完后再持久化到DB和ES。再归还状态机池。

    通过字节码分析JDK8中Lambd... h13213824850:这个生成的到底是匿名类还是内部类 asm好像没写成内部类啊