在Java虚拟机中,我是高级管家。他们很怕我,尤其是那些Java对象。我把他们关在一个叫做Heap的“监狱”里,严格管理,生死权都在掌控之中。中文把Stack译成“栈”,把Heap译成“堆”,也有人把Stack译成“栈”。唉,真不知道他们怎么想的,不过这么多年了,你明白就好了。恰好我在Heap中做Java对象的垃圾回收,这个“堆”总是让我想起垃圾堆。说到垃圾回收,这真是一个不小的负担。原因很简单。写Java程序的人只是给出新的对象,然后把它们扔到Heap中,而从不删除它们。删除这些对象的责任落在了我的头上,我怎么能不严格管理呢?有时候我很羡慕C和C++,你要手动分配和释放内存,程序员要为所有的错误负责。在我这里,如果任由这些对象肆意妄为,我的Java虚拟机启动后容量低且无法更改的Heap“监狱”很快就会被填满,只好派我的得力助手,专门找和清理那些不用的Java对象,释放它们占用的空间。为了找到这些麻烦制造者,我发明了一种算法,叫做“可访问性分析”。这个算法估计大部分人都知道,我就不再赘述了。下图说明了背后的思想,聪明的你一眼就可以看出,橙色的对象都是不可达对象,可以回收。我多次抗议,要求他修改。他说微信公众号只能改五个字,他改不了。唉,没办法。HeapPrison好吧,现在稍微介绍一下我运行的Heap“监狱”。你可以把它想象成一个大空间。为了方便管理,我把Heap“监狱”分成了多个区域,然后把那些Java对象搬来搬去。我定的规则是:新人必须进入新生代才能入住,如果新生代不能入住,我会派清洁工进行垃圾回收(MinorGC),如果回收后住不进去,然后那些年长的老家伙赶往养老院(老一代)。我将为堆中的每个Java对象设置一个年龄计数器。Java对象每存活一次GC,年龄就会加1,如果太老,不好意思,请进养老院(老年代)。其实我也会做一个动态的年龄判断,这里就不展示了。你可能会疑惑,为什么在新生代中将Eden、Survivor1、Survivor2这些奇怪的区域分开?那是因为我想在这里实现一个所谓的“复制”算法。最早的时候,我把一个内存区分成两个大小相等的区域,一次只使用其中一个。当区域1用完后,我将进行垃圾收集并将幸存的移动到另一个区域。注意:搬到那里后,它们都会住在一起,这样清理出来的红色碎片会重新平整成一个大空间供后续使用,尤其是大物件。这样倒着使用两个区域,效率高,不碎片,但浪费空间巨大:每次只能使用一半。后来人类发现,新生代中的大部分对象都活不了多久,一次垃圾回收基本就删除掉了。于是我改进了这个只用一半的复制算法,把新生代分为三部分:Eden、Survivor1、Survivor2,它们的比例是8:1:1。每次只使用伊甸园和一名幸存者。收集完垃圾后,将这两个区域的活物复制给另一个Survivor。如果幸存者不能适应,请进入养老院(老一代)。如果不幸的是,连养老院都满了,那你就得做一个FullGC。这是一个非常缓慢的操作,您最好希望它不会经常发生。“监狱”之外有很多事情要做虽然我可以在堆监狱当高手,但有时我不得不接触监狱外的世界。有一次我想通过Socket发送数据出去。明明准备好了数据,就在我的Heap里,但是JVM老大居然把数据拷贝到Heap外的一块内存中,然后再通过Socket发送出去。我问他这到底是怎么回事,为什么要做出这种莫须有的举动,难道是担心我这个堆狱总管?JVM老大说的,真是让人揪心。Sockets底层都是用C语言写的,重点是物理内存的地址。当您进行垃圾回收时,您在Eden、Survivor和老年代之间移动Java对象。对象的地址也会改变。我如何告诉人们将数据发送到哪个地址?你想想也是一样的道理。你得到了一些东西,你失去了它。你们程序员不需要管理内存,但是底层要和内存打交道,多了一个过程:Copy。老大还说:“可能你不知道,除了你的Heap监狱外,其实我在Java进程中还有一个地方叫做“Off-Heapmemory”,数据会被拷贝到这里。为了和你区分开来,我称之为堆外内存。”没想到,有一块我无法控制的“飞地”!但它和我没有竞争关系,随它去吧。但是没过几天,JVM老大又给我带来了“惊喜”。他说:“拷贝数据太麻烦了,我想了个办法,在Java代码中直接分配一块属于Off-Heap的内存。”我头晕:“直接在堆外分配内存?怎么分配?”老大给了我一段代码:“看,这不是分配了128M的堆外内存吗?对这个buffer的读写操作,会直接写入堆外内存,不需要copy它通过你。“ByteBufferbuffer=ByteBuffer.allocateDirect(1024*1024*128);该死的面向接口编程,这个ByteBuffer分配的堆外内存就像一个普通的Java对象被使用一样,根本看不出来是在堆中还是在堆中。出堆。完了,我根本管不了这段记忆。老大看我心情不好,安慰他说:“这个buffer也是一个Java对象,存放在你的Heap中,只不过保存的是内存中的128M信息。”差不多吧!既然是Java对象,就得放在我的Heap监狱里,由我控制!可以想象,当这个对象被垃圾回收时,它指向的直接内存将被释放。我突然冒出一个恶念:如果这样的对象越来越多,还没有被垃圾回收,那对应的直接内存岂不是也无法释放,然后OutofMemory?老大似乎看穿了我的想法:“这些东西,你要非常小心,一定要保证能放出来。”直接分配堆外内存的功能正式上线了,发现分配堆外内存比堆上内存慢一点,还以为用的人不多。。没想到特别合适针对分配次数少,读写操作频繁的场景,受到了Netty等通信系统的热烈欢迎。为了减少创建堆外内存的开销,Netty还引入了object技术pool,就像数据库连接池一样,先分配一些堆外内存,然后不断的复用,没想到堆外内存能玩这么多花样,一想到还是java程序而且还得用Java对象打包,反正跳不出我的手掌心,我就放心了。代码上升求转载】点此查看该作者更多好文
