1、首先,GC又分为minor GC 和 Full GC(major GC)。Java堆内存分为新生代和老年代,新生代中又分为1个eden区和两个Survior区域。
2、一般情况下,新创建的对象都会被分配到eden区,这些对象经过一个minor gc后仍然存活将会被移动到Survior区域中,对象在Survior中没熬过一个Minor GC,年龄就会增加一岁,当他的年龄到达一定程度时,就会被移动到老年代中。
3、当eden区满时,还存活的对象将被复制到survior区,当一个survior区满时,此区域的存活对象将被复制到另外一个survior区,当另外一个也满了的时候,从前一个Survior区复制过来的并且此时还存活的对象,将可能被复制到老年代。因为年轻代中的对象基本都是朝生夕死(80%以上),所以年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想是将内存分为两块,每次只有其中一块,当这一块内存使用完,就将还活着的对象复制到另一块上面。复制算法不会产生内存碎片。
4、在GC开始的时候,对象只会存在于eden区,和名为“From”的Survior区,Survior区“to”是空的。紧接着GCeden区中所有存活的对象都会被复制到“To”,而在from区中,仍存活的对象会根据他们的年龄值来决定去向,年龄到达一定只的对象会被复制到老年代,没有到达的对象会被复制到to survior中,经过这次gc后,eden区和fromsurvior区已经被清空。这个时候,from和to会交换他们的角色,也就是新的to就是上次GC前的fromMinor GC:从年轻代回收内存。
5、当jvm无法为一个新的对象分配空间时会触发Minor GC,比如当Eden区满了。当内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden和Survior区不存在内存碎片写指针总是停留在所使用内存池的顶部。执行minor *** 作时不会影响到永久代,从永久带到年轻代的引用被当成GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉(永久代用来存放java的类信息)。如果eden区域中大部分对象被认为是垃圾,永远也不会复制到Survior区域或者老年代空间。如果正好相反,eden区域大部分新生对象不符合GC条件,Minor GC执行时暂停的线程时间将会长很多。Minor may call "stop the world"。
IBM JVM的GC分为三个步骤 Mark phase(标记) Sweep phase(清扫) Compaction phase(内存紧缩) 在了解这些过程之前 我们先看一下IBMJava中的对象的Layout和Heap lay out 一个Java对象在IBM vm中的结构如下 size+flags mptr locknflags objectdata size+flags 这是一个 byte的slot( 平台) 这个slot的主要功能就是描述对象的尺寸 由于IBMJava中的对象都是以 byte的倍数分配的 因此对象的尺寸其实就是真实尺寸/ 存放在 byte的slot中 另外在这个slot的低三位是保留字段起到标记对象的作用 他们分别为 bit :swapped bit 这个交换位被用于Compaction phase即内存紧缩阶段使用 同时 这一位在标记堆栈溢出的时候(mark stack overflow)也被用于标记NotYetScanned状态 bit dosed bit 这个位用于标示这个对象是否被某个堆栈或者寄存器reference到了 如果这个标志被至位则这个对象就不能在当前的GC cycle中被删除 而且如果某个reference指向的内存不是一个真实的reference比如是一个简单的float 或者integer变量但是它的值恰巧就是Heap中某个Object的地址的时候 我们就不能修改这个refernece 这种对象的bit 也被置为 bit :pinned bit 标记一个对象是否是一个一个钉扣对象(PINNED object) 一个Pinned Object也不能被GC删除 因为他们可能在Heap之外被reference到了 典型的一个例子就是Thread 还记得我上面说的僵死县城么?它不能被删除的道理就是这个 另外一种PinnedObject就是 JNI Object 即被本地代码使用的对象 Mptr: 在 平台上也是 byte的slot Mptr有两个功能 如果mptr不是一个数组 则Mptr指向一个方法块(method block) 你可以通过这个method block来得到一个类块(class block) 这个类块 告诉你这个Object是属于哪个class的实例 method block和class block由Class Loader分配 而不是heap在heap中进行分配 如果mptr是一个数组(Array) mptr包含了这个对象中 数组的元素个数 lockflags 在 平台上也是 byte的slot 但是这个slot只有低 位被用到 bit :是array flag 如果这个位被置位 那么这个对象就是一个数组同时mptr字段就包含了数组的元素个数 bit 是hashed和moved bit 如果这个位被置位 那么他就告诉我们这个对象在被hashed以后被删除了 Object Data 就是这个对象本身的数据 Heap layout:heap topheap limitheap base heap base是heap的起始地址 heap top是heap的结束地址 heaplimit 是当前程序使用的那段heap可以进行扩展和收缩的极限 你可以用 Xmx参数在java运行的时候对heap top和heap base进行控制 Alloc bits 和 mark bits heap top allocmax markemaxheap limit alloc size marksizeheap base 上面这个结构描述了heap和alloc bits 以及 markbits之间的关系 allocbits和markbits都是元素为 个bit的vector 他们与heap有同样的长度 下面是两个对象被分配以后在heap和两个vector中的表现 heaptop allocmax markmax heaplimit allocsize marksize object top object base object allocbit object markbit object top object base object allocbit 如上面的结构 如果一个对象在heap被alloc出来 那么在allocbits中就标示出这个对象的起始地址所在的地址 allocbits中只标记起始地址 但是这个过程告诉我们这个对象在那里被创建 但是不告诉我们这个对象是否存活 当在mark phase中如果某一个对象比如object 仍然存活 那么就在markbits中对应的地址上标记一下The free list IBM jvm中的空闲块用用一个free list链标示 如图 freechunck freechunck freechunckn size size size next >next > next >NULL freeStorage freeStorage freeste 有了这些基本概念我们来看看Mark phase的工作情况 MarkPhase GC的Mark phase将标记所有还活着的对象 这个标记所有可达对象的过程称为tracing Jvm的活动状态(active state)是由下面几个部分组成的 每个线程的保存寄存器(saved registers) 描述线程的堆栈 Java类中的静态元素 以及局部和全局的JNI(Java Native Interface)引用 在Jvm中的方法调用都在C Stack上引发一个Frame 这个Frame包含了 对象实例 为局部变量的assignment结果或者传入方法的参数 所有这些引用在Tracing过程中都被同等对待 实际上 我们可以把一个线程的堆栈看城一系列 bytes slot的集合 然后对每一个堆栈都从顶向下对这些slot进行扫描 在扫描的过程中都必须校验每个slot是否指向heap当中的一个真实的对象 因为在前面我就说过 很有可能这些slot值仅仅是一个int或float但是他们的值恰巧就等于heap中的一个对象地址 因此在扫描的时候必须相当的保守 扫描的时候必须保证所有的指针都是一个对象 而且这个对象没有在GC中被删除 只有符合下面条件的slot才是一个指向对象的指针 必须以 byte的倍数分配的内存 必须在heap的范围之内(即大于heapbase小于heaptop) 对应的allocbit必须置为 满足这些条件的对象引用我们称为roots 并且把他们的dosed bit置为 表示不能被GC删除 我想大家已经知道C#中为何连Int和Float都是OBject的原因了吧 在C#中因为都是OBject因此 在tracing的过程中就减少了一次校验 这个减少对性能起到很大的影响 如果扫描完成 那么Tracing过程便能安全精确的执行 也就是说我们可以在roots中通过reference找到他对应的objects 由于他们是真实的reference 那么我们就能够在pactionphase中移动对应的对象并且修改这些reference Trace过程使用了一个可以容纳 k的slots的stack 所有的引用逐个push进入这个堆栈并且同时在markbits中进行标记 当push和mark的工作完成之后 我们开始pop出这些slot并且进行trace 常规的对象(非数组对象)将通过mptr去访问clas lock clas lock将会告诉我们从这个对象中找到的其他对象的reference在那里?当我们在clas lock找到一个refernce以后 如果发现他没有被mark 那么我们就在markallocbits中mark他然后把他再压入堆栈 数组对象利用mptr去访问每个数组元素 如果他们没有mark则mark然后压入堆栈 Trace过程一直持续进行 直到堆栈为空 MarkStack OverFlow 由于markStack限制了尺寸 因此它可能会溢出 如果溢出发生 那么我们就设定一个全局的标志来表明发生了MarkStack OverFlow 然后我们将那些不能push入stack的OBject的bit 设定为NotYetScanned 然后当tracing过程完成以后 检验全局标志如果发现有overflow则把NotYetScanned的对象再次压入堆栈开始新的tracing过程 并行Mark(Parallel Mark) 由于使用逐位清扫(biise sweep)和内存紧缩规避功能 GC将化大部分的时间是用于Mark而非前面两项 这就导致了IBM JVM需要开发一个GC的并行版本 并行GC的目的不是以牺牲单CPU系统上的效能来换取在 路对称CPU系统上的高效率 并行Mark的基本思想就是通过多个辅助线程(helper thread)和一个共享工作的工具来减少Marking的时间 在单CPU系统中 执行GC工作的只有一个主线程 Parallel mark仍然需要这个主线程的参与 他充当了管理协调的角色 这个Thread所要执行的工作和单CPU上的一样多 包括他必须扫描C Stack来鉴别需要收集的roots指针 一个有N路对称CPU的系统自动含有n 个helper thread并且平均分布在每个CPU上 master thread将scan完的reference集合进行分块 然后交给helper thread独立完成mark工作 每个Helper thread都被分配了一个独立的本地mark stack 以及一个shareable queue sharqueue将存放help thread在mark overflow的时候的NotyetScanned对象 然后由master thread将sharequeue中的对象balance到其他已经空闲的thread上去 并发Mark(Concurrent mark) Concurrent mark的主要目的在于当heap增长的时候减少GC的pause time 只要heap到达heap limit的时候 Concurrent mark就会被执行 在Concurrent phase中 GC要求应用中的每个线程(不是指helper thread而是应用程序自己开启的线程以便充分利用系统资源)扫描他们自己的堆栈来得到roots 然后使用这些roots来同步的trace 可达对象 Tracing工作是由一个后台的低优先级的线程执行 同时程序自己开启的线程在分配内存的时候必须执行heap lock allocation 由于使用程序自己开启的线程并发的执行mark live objects 我们必须纪录那些已经trace过的object的变化 这个功能是采用一个叫写闸(write barrier) 来实现的 这个写闸在每次改变引用的时候被激活 它告诉我们什么时候一个对象被跟新过了 以便我们从新扫描那部分heap 写闸的具体实现是Heap会分配出 byte的内存段每个段都分配了一个byte在卡表中(card table) 无论何时一个对象的reference被更新cardtable将同步纪录这个对象的起始地址 使用Byte而不用bit的原因是写byte要比写bit快 倍 而且我们可能希望空余的bit会在未来被用到 当Concurrent mark执行完毕以后 S collection(stop total world)将会被执行 s的意思是指suspend所有程序自己开启的线程 因此我们可以看到如果使用Concurrent mark那 lishixinzhi/Article/program/Java/JSP/201311/19555
java探针采集能够收集下列信息:
1 JVM内存使用情况:Java堆大小、垃圾回收器使用情况、新生代、老年代分布等。
2 JVM线程情况:活动线程、分配线程、死锁线程、线程池信息等。
3 JVM运行状态:CPU使用情况、文件描述符利用情况、系统调用花费时间、监测代码执行时间等。
4 GC收集停顿持续时间:每次垃圾收集时的各项指标和持续时间等。
5 类加载信息:类加载占用时间、定义数量、类失败加载数、类加载内容等。
Java垃圾回收概况_ava_C(Garbage Collection,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者。
当前使用量最大的_ava版本是 8,所以测试者用_ava8与_ava11进行对比测试。
Systemgc()用来强制立即回收垃圾,即释放内存。
java对内存的释放采取的垃圾自动回收机制,在编程的时候不用考虑变量不用时释放内存,java虚拟机可以自动判断出并收集到垃圾,但一般不会立即释放它们的内存空间,当然也可以在程序中使用Systemgc()来强制垃圾回收,但是要注意的是,系统并不保证会立即进行释放内存
java 启动时加参数 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -Xloggc:/var/lib/tomcat7/logs/gclog 就可以配置gc的log日志,/var/lib/tomcat7/logs为保存路径,可以换成你想要的路径。
详细解释可以参考以下链接
>以上就是关于Java垃圾回收:GC在什么时候对什么做了什么全部的内容,包括:Java垃圾回收:GC在什么时候对什么做了什么、IBM Java如何做到高性能GC的实现内幕、java探针采集能收集哪些信息等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)