热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java垃圾收集与内存分配

目录1、对象已“死”吗?1.1、引用计数算法1.2、可达性分析算法1.3、对象引用1.4、finalize自救1.5、回收方法区2、GC算法2.1、分代收集理论2.2


目录

  • 1、对象已“死”吗?
    • 1.1、引用计数算法
    • 1.2、可达性分析算法
    • 1.3、对象引用
    • 1.4、finalize自救
    • 1.5、回收方法区
  • 2、GC算法
    • 2.1、分代收集理论
    • 2.2、标记-清除算法
    • 2.3、标记-复制算法
    • 2.4、标记-整理算法
  • 3、垃圾收集器
    • 3.1、Serial
    • 3.2、ParNew
    • 3.3、Parallel Scavenge
    • 3.4、Serial Old
    • 3.5、Parallel Old
    • 3.6、CMS
    • 3.7、Garbage First(G1)
    • 3.8、Shenandoah
    • 3.9、ZGC
  • 4、内存分配与回收策略
    • 4.1、优先在Eden区分配
    • 4.2、大对象直接进入老年代
    • 4.3、晋升老年代
    • 4.4、GC年龄动态判定
    • 4.5、空间分配担保


内容参考《深入理解JVM虚拟机》,本文JVM均指HotSpot虚拟机。


Java程序运行期间无时无刻不在产生对象,JVM为对象动态的分配内存,如果内存不释放肯定会有耗尽的一天。
C语言需要开发者为创建的对象编写的配套的delete/free来释放内存,但是Java开发者却不需要那么做,这一切都要归功于:垃圾收集技术(Garbage Collection)

垃圾收集技术的历史远比Java早,1960年诞生的Lisp是第一门使用垃圾收集技术的语言,Lisp的作者思考了垃圾收集需要完成的三件事:


  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

在JVM的内存模型中,虚拟机栈、本地方法栈、程序计数器是线程私有的,和线程同生共死。栈帧应该分配多少内存基本是已知的,方法结束或线程结束内存自然就释放掉了,所以这几个内存区域不用过多的考虑如何回收的问题,垃圾收集重点管理的内存区域是:

几乎所有的对象都在堆上分配内存,而且只有在程序运行期间才知道创建了多少对象,这部分内存的分配和回收是动态的,充满了各种不确定性。




1、对象已“死”吗?

堆中几乎存放着所有的对象,在GC开始工作之前,首先要做的事情就是判断哪些对象已“死”,即不可能再被任何途径使用的对象,例如:new的对象没有赋值给任何引用。


1.1、引用计数算法

在对象中添加一个引用计数器,每引用一次计数器就加1,每取消一次引用计数器就减1,当计数器为0时表示对象不再被引用,此时就可以将对象回收了。

引用计数算法(Reference Counting)虽然占用了一些额外的内存空间,但是它原理简单,也很高效,在大多数情况下是一个不错的实现方案,但是它存在一个严重的弊端:无法解决循环引用

如下代码所示,对象引用了2次,取消引用1次,引用计数器为1导致对象始终无法被回收。

class MyClass {Object instance;public static void main(String[] args) {MyClass a = new MyClass();//引用+1MyClass b = new MyClass();//引用+1a.instance = b;//引用+1b.instance = a;//引用+1a = null;//引用-1b = null;//引用-1//a、b均不可能再被访问到,但是引用计数器为1,无法被回收}
}

大多数虚拟机,如HotSpot就没有采用引用计数算法。


1.2、可达性分析算法

大部分主流的虚拟机都是通过可达性分许算法来判断对象是否存活的。

基本思路是:通过一系列被称为“GC Roots”的根对象作为起始节点集,从这些节点开始向下搜寻,搜寻走过的路径被称为“引用链”(Reference Chain),如果对象到GC Roots没有任何引用链相连,则表示对象不可达,即对象不可能再被使用到,也就可以进行回收了。

在这里插入图片描述

在Java中,固定可作为GC Roots的对象包含以下几种:


  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象, 譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
  • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象等,还有系统类加载器。
  • 所有被同步锁(synchronized关键字)持有的对象。
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

如下代码,MyClass的静态引用b就可以当做一个GC Root,a引用b,b引用c,引用链为:a->b->c,b和c都是GC Root可达的,即不会被回收。

class MyClass{static A a = new A();static class A{B b = new B();static class B{C c = new C();static class C{}}}
}

1.3、对象引用

无论通过何种算法判断对象是否存活,都和对象“引用”有关。对象存在引用,则表明能够被访问到,即不能被回收,反之则可以回收。

在JDK1.2之前,“引用”的概念过于狭隘,如果Reference类型的数据存储的是另外一块内存的起始地址,就称该Reference数据是某块地址、对象的引用,对象只有两种状态:被引用、未被引用。
这样的描述未免过于僵硬,对于这一类对象则无法描述:内存足够时暂不回收,内存吃紧时进行回收。例如:缓存数据。

在JDK1.2之后,Java对引用的概念做了一些扩充,将引用分为四种,由强到弱依次为:


  • 强引用(Strongly Reference)
    指代码中普遍存在的赋值行为,如:Object o = new Object(),只要强引用关系还在,对象就永远不会被回收。
  • 软引用(Soft Reference)
    还有用处,但是非必须存活的对象,JVM会在内存溢出前对其进行回收,例如:缓存。
  • 弱引用(Weak Reference)
    非必须存活的对象,引用关系比软引用还弱,不管内存是否够用,下次GC一定回收。
  • 虚引用(Phantom Reference)
    也称“幽灵引用”、“幻影引用”,最弱的引用关系,完全不影响对象的回收,等同于没有引用,虚引用的唯一的目的是对象被回收时会收到一个系统通知。

1.4、finalize自救

即使在可达性算法中被判定为不可达的对象,也不是“非死不可”的。

一个对象真正意义上的死亡,至少要经历两次标记过程:判定不可达时,进行第一次标记,随后进行筛选,筛选的条件是:是否有必要执行对象的finalize()方法。以下两种情况认为没有必要执行:


  • 没有重写finalize()方法
  • 已经执行过对象的finalize()方法(一个对象只会执行一次)

如果对象被判定需要执行finalize()方法,则会将其放到一个F-Queue的队列中,由JVM自动创建、低优先级的Finalizer线程去依次执行它们的finalize()方法。
注意:JVM并不保证一定会等待finalize()方法执行结束,因为如果finalize()方法执行很慢或发生死循环将导致GC回收特别慢,甚至内存回收子系统崩溃。在回收大量对象时,finalize()方法甚至不会被执行,因为会给GC线程带来很大压力。

finalize()方法是对象拯救自己的最后一次机会,只要在finalize()方法中将对象重新建立引用关系,GC在进行第二次标记时就会将其踢除回收队列,反之对象则开始被回收。

如下代码,由于一个对象的finalize()只会被执行一次,所以第一次自救成功,第二次自救失败。

class MyClass {static MyClass instance;@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("MyClass finalize...");//自救instance = this;}public static void main(String[] args) throws InterruptedException {instance = new MyClass();instance = null;System.gc();//GC异步线程执行,Main等待1秒Thread.sleep(1000);System.out.println("第一次自救:"+instance);instance = null;System.gc();//GC异步线程执行,Main等待1秒Thread.sleep(1000);System.out.println("第二次自救:"+instance);/*输出:MyClass finalize...第一次自救:com.xw.MyClass@5fd0d5ae第二次自救:null*/}
}

由于finalize()方法的执行充满了不确定性,一是可能不会被执行,二是可能不会被执行结束,官方明确声明不推荐大家使用,开发者完全可以忘记finalize()方法。


1.5、回收方法区

Java堆中的对象,绝大多数“朝生夕死”,通常一次GC可以回收70%~99%的内存空间,而方法区的回收成果远低于堆。

方法区垃圾回收的条件非常苛刻,JVM规范也允许可以不对方法区进行垃圾回收,但是HotSpot仍然可以对方法区进行回收。

方法区的GC主要回收两部分内容:


  • 废弃的常量
    回收废弃常量与回收堆类似,例如:字符串“hello”曾经保存到常量池中,当前系统没有任何地方存在对“hello”的引用,GC就会将其回收。
  • 不再使用的类

回收类的条件比较苛刻,需要满足以下三个条件:


  • 该类及其子类所有实例都被回收。
  • 加载该类的类加载器被回收。
  • 该类对应的Class对象没有被引用,无法通过反射访问到该类。

JVM对满足这些要求的类仅仅是“允许被回收”,不像对象没有了引用就一定被回收,HotSpot提供了参数-Xnoclassgc来关闭JVM对类的回收。




2、GC算法

GC算法涉及到大量的程序细节,且不同平台下的虚拟机实现也存在较大差异,这里只简单记录一下几种GC算法的思想和大致原理。


2.1、分代收集理论

目前大多数JVM的垃圾收集器都遵循“分代收集”理论,分代收集理论建立在以下三个假说之上:


  • 弱分代假说:绝大多数对象都是朝生夕死的。
  • 强分代假说:熬过越多次GC的对象就越难以回收。
  • 跨分代引用假说:跨代引用相对于同代引用是极少的。

基于前两个假说,收集器将Java堆划分为两块不同的内存区域:新生代、老年代

新生代存放朝生夕死的对象,每一次GC都可以回收大量内存空间,可以较高频次的进行垃圾回收。

老年代存放难以被回收的对象,GC成果远低于新生代,以较低频次进行垃圾回收。

将Java堆划分为新生代和老年代,针对不同的区域使用不同的垃圾回收机制,这样就兼顾了GC的时间开销和内存的空间利用率。垃圾回收器每次只针对一块特定的区域进行回收,按回收类型划分为:


  • Minor GC
    也被称为“Young GC”、“轻GC”,只针对新生代进行的垃圾回收。
  • Major GC
    也被称为“Old GC”,只针对老年代进行的垃圾回收。
  • Mixed GC
    混合GC,针对新生代和部分老年代进行垃圾回收,部分垃圾收集器才支持。
  • Full GC
    整堆GC、重GC,针对整个Java堆和方法区进行的垃圾回收,耗时最久的GC。

对象可能存在跨代引用
如果只针对一块区域进行垃圾回收,可能存在一些问题。因为对象可能存在跨代引用,例如:新生代的对象被老年代引用。为了判断对象是否死亡,还需要额外遍历老年代来进行可达性分析,这无疑会降低GC的性能,因此第三条假说就是针对改问题提出的。

基于第三条假说,收集器不会再遍历整个Java堆,只需在新生代对象上建立一个全局的数据结构——记忆集(Remembered Set),记忆集把老年代划分为若干个小块,标记哪一块区域会存在跨代引用,当发生Minor GC时,只有被标记为存在跨代引用的老年代内存才会被加入到GC Roots进行扫描。


2.2、标记-清除算法

标记清除算法分为两个过程:标记、清除。

收集器首先标记需要被回收的对象,标记完成后统一清除。也可以标记存活对象,然后统一清除没有被标记的对象,这取决于内存中存活对象和死亡对象的占比。

缺点:


  • 执行效率不稳定
    标记和清除的时间消耗随着Java堆中的对象不断增加而增加。
  • 内存碎片
    标记清除后内存会产生大量不连续的空间碎片,不利于后续继续为新生对象分配内存。

在这里插入图片描述


2.3、标记-复制算法

为了解决标记清除算法产生的内存碎片问题,标记复制算法进行了改进。

标记复制算法会将内存划分为两块区域,每次只使用其中一块,垃圾回收时首先进行标记,标记完成后将存活的对象复制到另一块区域,然后将当前区域全部清理。

缺点是:如果大量对象无法被回收,会产生大量的内存复制开销。可用内存缩小为一半,内存浪费也比较大。
在这里插入图片描述

由于绝大多数对象都会在第一次GC时被回收,需要被复制的往往是极少数对象,那么就完全没必要按照1:1去划分空间。
HotSpot虚拟机默认Eden区和Survivor区的大小比例是8:1,即Eden区80%,From Survivor区10%,To Survivor区10%,整个新生代可用内存为Eden区+一个Survivor区即90%,另一个Survivor区10%用于分区复制。

如果Minor GC后仍存活大量对象,超出了一个Survivor区的范围,那么就会进行分配担保(Handle Promotion),将对象直接分配进老年代。


2.4、标记-整理算法

标记复制算法除了在对象大量存活时需要进行较多的复制操作外,还需要额外的内存空间老年代来进行分配担保,所以在老年代中一般不采用这种回收算法。

能够在老年代中存活的对象,一般都是历经多次GC后仍无法被回收的对象,基于“强分代假说”,老年代中的对象一般很难被回收。针对老年代对象的生存特征,引入了标记整理算法。

标记整理算法的标记过程与标记清除算法一致,但是标记整理算法不会像标记清除算法一样直接清理标记的对象,而是将存活的对象都向内存区域的一端移动,然后直接清理掉边界外的内存空间。
在这里插入图片描述

标记整理算法相较于标记清除算法,最大的区别是:需要移动存活的对象
GC时移动存活的对象既有优点,也有缺点。


  • 缺点
    基于“强分代假说”,大部分情况下老年代GC后会存活大量对象,移动这些对象需要更新所有reference引用地址,这是一项开销极大的操作,而且该操作需要暂停所有用户线程,即程序此时会阻塞停顿,JVM称这种停顿为:Stop The World(STW)。
  • 优点
    移动对象对内存空间进行整理后,不会产生大量不连续的内存碎片,利于后续为对象分配内存。

由此可见,不管是否移动对象都有利弊。移动则内存回收时负责、内存分配时简单,不移动则内存回收时简单、内存分配时复杂。从整个程序的吞吐量来考虑,移动对象显然更划算一些,因为内存分配的频率比内存回收的频率要高的多的多。

还有一种解决方式是:平时不移动对象,采用标记清除算法,当内存碎片影响到大对象分配时,才启用标记整理算法。




3、垃圾收集器

GC算法内存回收的方法论,垃圾收集器才是内存回收真正的践行者。JVM规范没有对垃圾收集器作出任何规定,因此不同的JVM所包含的垃圾收集器存在很大差别。


3.1、Serial

最基础,最早的垃圾收集器,仅开启一个线程完成垃圾回收,而且回收时会暂停所有用户线程(STW)。
在这里插入图片描述


3.2、ParNew

Serial的多线程版本,会开启多个线程并行完成垃圾回收(线程数和CPU有关),回收时同样会暂停所有用户线程。由于涉及到上下文切换开销,ParNew的性能未必比Serial好。
在这里插入图片描述


3.3、Parallel Scavenge

和ParNew类似,Parallel Scavenge的目标是达到一个可控制的吞吐量。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX: MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

如果需要减少GC停顿时间,Parallel Scavenge会将新生代内存调小一些,通过提高GC的频率来减少单次GC的停顿时间。


3.4、Serial Old

Serial的老年代版本,同样是单线程收集,暂停所有用户线程,使用标记整理算法。
在这里插入图片描述


3.5、Parallel Old

Parallel Scavenge的老年代版本,多线程并发收集,标记整理算法,JDK6才开始提供。
在这里插入图片描述


3.6、CMS

CMS(Concurrent Mark Sweep)收集器的设计目标是:追求最短回收停顿时间。
CMS一般用于服务器端,整体回收过程分为以下四个步骤:


  • 初始标记
    需要STW,仅标记GC Roots能直接关联到的对象,速度很快。
  • 并发标记
    不需要STW,从GC Roots能直接关联到的对象开始遍历整个对象图的过程,耗时较长但是是与用户线程并发运行的。
  • 重新标记
    需要STW,修正并发标记期间因用户线程运行而导致标记产生变动的部分对象,停顿时间通常比初始标记慢,但也远比并行标记快。
  • 并发清除
    不需要STW,并行清理回收被标记的对象,由于不需要移动对象,可以与用户线程并发执行。

由于整个过程耗时最长的并发标记和并发清除中,可以与用户线程并发执行,不需要STW。所以总体上说,CMS收集器回收过程几乎是与用户线程并发执行的,是一款低停顿的垃圾收集器。
在这里插入图片描述

CMD收集器存在的一些缺点:


  • 对CPU敏感
    CMS虽然不会暂停用户线程,但是仍需要占用一部分线程资源来进行垃圾回收工作,GC期间导致应用程序变慢是肯定的,降低总吞吐量。默认的回收线程数是:(CPU核心数+3) / 4。
  • 无法处理“浮动垃圾”
    并发标记和并发清理阶段,用户线程仍然是运行的,即仍然在不停的创建对象,这时新增的对象就称为“浮动垃圾”,本次GC无法清理,只能留作下次GC时清理。为此必须额外预留一些空间给浮动垃圾,如果浮动垃圾过多,预留空间不足时将导致GC失败,此时JVM将冻结所有用户线程执行预备方案临时启动Serial Old重新对老年代进行垃圾回收,这样性能反而更低了。
  • 内存碎片
    由于CMS采用标记清除算法,会产生内存不连续的碎片,导致在为大对象分配内存时,内存空间明明够用但就是无法找到一块连续的内存分配,导致JVM不得不提前触发Full GC进行回收。

3.7、Garbage First(G1)

开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
G1之前的收集器回收的目标范围很明确:新生代、老年代,整个Java堆。G1跳出了这个樊笼,它可以面向堆内存的任何部分来组成“回收集”进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收成果最好,这就是G1开创的“Mixed GC”混合GC模式。
在这里插入图片描述


3.8、Shenandoah

Shenandoah不是由Oracle团队开发的HotSpot垃圾收集器,只存在于OpenJDK中,OracleJDK并不包含。
最初由RedHat公司独立发展,2014年贡献给了OpenJDK,并推动它称为JDK12的正式特性之一。


3.9、ZGC

ZGC与Shenandoah都是低延迟垃圾收集器,都希望在尽可能对吞吐量影响不大的情况下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内。

ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。




4、内存分配与回收策略

Java语言的自动内存管理,目标是自动化的解决两个问题:自动分配内存、自动回收内存

几乎所有的对象都是在堆中分配内存,当然,随着即时编译和逃逸分析技术的成熟,对象也可能直接在栈中分配内存。在分代的设计思想下,新创建的对象一般都会分配在新生代中。
实际上,对象分配的规则并不是固定的,JVM规范并未对此作出要求,这取决于虚拟机使用哪一种垃圾收集器以及相关的JVM参数。


4.1、优先在Eden区分配

绝大多数情况下,新生对象在Eden区分配内存。


HotSpot虚拟机提供了-XX:+PrintGCDetails参数,JVM进行垃圾回收时会输出GC日志,并在进程退出时输出堆中各个区域的内存情况。


如下代码,设置Java堆内存20M,新生代和老年代各10M,Eden区和Survivor区比例为8:1,运行查看JVM内存划分状况。

class Memory{/*VM Args: -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8*/public static void main(String[] args) {}
}

输出如下,新生代总可用内存为9216K,10M的90%,即Eden区+一个Survivor区,另一个Survivor区用作分区复制。Survivor区还未使用,因为没有触发Minor GC,老年代也未使用。

HeapPSYoungGen total 9216K, used 2391K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)eden space 8192K, 29% used [0x00000007bf600000,0x00000007bf855f80,0x00000007bfe00000)from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)ParOldGen total 10240K, used 0K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)object space 10240K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf600000)Metaspace used 2915K, capacity 4496K, committed 4864K, reserved 1056768Kclass space used 318K, capacity 388K, committed 512K, reserved 1048576K

测试代码修改如下,创建一个2M大小的数组,再次查看内存分配情况,发现新生代内存使用比上一次多了两千多K。

class Memory{static int _1MB = 1024 * 1024;/*VM Args: -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8*/public static void main(String[] args) {byte[] bytes = new byte[2 * _1MB];}
}
//PSYoungGen total 9216K, used 4798K

4.2、大对象直接进入老年代


-XX:PretenureSizeThreshold参数用于设定对象所占内存大于该值直接进入老年代,只针对Serial和ParNew收集器有效。


JVM为大对象分配内存时,压力会比较大,首先大对象需要一块连续的较大内存,如果存在内存碎片则分配更加艰难,其次是如果大对象经历GC后存活的话,还需要高额的内存复制开销(标记复制算法)。

如下代码,4M大小的数组将会直接分配到老年代。

//-XX:+UseSerialGC 只针对Serial和ParNew收集器有效
byte[] bytes = new byte[4 * _1MB];
//tenured generation total 10240K, used 4096K

4.3、晋升老年代

大多数垃圾收集器采用了分代思想,为了便于垃圾回收,JVM将Java堆划分为新生代和老年代。

在对象头的Mark Word中,用了4bit来保存对象的GC年龄。对象首先在Eden区诞生,当Eden区内存不够用时,触发一次Minor GC,如果对象仍存活,将会进入其中一个Survivor区,且GC年龄会设为1,后面每经历一次GC,对象就会在From Survivor和To Survivor区之间复制一次(标记复制算法),每复制一次对象的GC年龄就加1,当年龄达到15时如果对象仍存活,就会晋升至老年代。


对象晋升老年代的GC年龄阈值通过参数-XX:MaxTenuringThreshold控制。



4.4、GC年龄动态判定

实际上,JVM并一定要等到对象的GC年龄达到阈值后才会将其晋升至老年代,内部会进行年龄的动态判定。

试想这样一种情况,如果程序突然产生大量新生对象,将会导致新生代区域快被撑满了,但老年代却非常空闲。
为了解决这种问题,JVM会这么处理:如果Survivor区中相同年龄的对象总和超过了一半,那么大于等于该年龄的对象就可以直接进入老年代,而无需达到GC年龄阈值。


4.5、空间分配担保

在触发Minor GC之前,JVM会先检查老年代中的最大连续内存是否大于新生代所有对象总和,因为如果不满足这个条件的话,一旦新生代中所有对象都晋升到老年代将导致内存溢出。
如果满足该条件,那么JVM认为此次GC是安全的,可以直接执行。
否则,认为此次GC是有风险的,会先检查-XX:HandlePromotionFailure参数是否允许担保失败,如果不允许,则将Minor GC改为Full GC。如果允许,则检查老年代最大连续内存是否大于历次新生代晋升到老年代的对象总和,如果大于,JVM将尝试进行Minor GC,反之则将Minor GC改为Full GC。

取历史平均值是存在风险的,一旦某次Minor GC后存活的对象激增,那么JVM在触发Minor GC后又不得不发起一次Full GC,将导致GC的停顿时间变得更长。


推荐阅读
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 本文介绍了一道经典的状态压缩题目——关灯问题2,并提供了解决该问题的算法思路。通过使用二进制表示灯的状态,并枚举所有可能的状态,可以求解出最少按按钮的次数,从而将所有灯关掉。本文还对状压和位运算进行了解释,并指出了该方法的适用性和局限性。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 广度优先遍历(BFS)算法的概述、代码实现和应用
    本文介绍了广度优先遍历(BFS)算法的概述、邻接矩阵和邻接表的代码实现,并讨论了BFS在求解最短路径或最短步数问题上的应用。以LeetCode中的934.最短的桥为例,详细阐述了BFS的具体思路和代码实现。最后,推荐了一些相关的BFS算法题目供大家练习。 ... [详细]
  • 第七课主要内容:多进程多线程FIFO,LIFO,优先队列线程局部变量进程与线程的选择线程池异步IO概念及twisted案例股票数据抓取 ... [详细]
  • 1.为什么会有树?因为当有大量的输入数据时,链表的线性访问时间就显得略长了。而树结构,其大部分操作的运行时间平均为O(logN)。2.树 ... [详细]
  • Java面试题系列:将面试题中比较经典和核心的内容写成系列文章持续在公众号更新,可巩固基础知识,可梳理底层原理,欢迎大家持续关注【程序新视界】。本篇为面试题系列第2篇。常见面试问 ... [详细]
author-avatar
广东快乐笨人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有