⑴ java垃圾回收:GC在什么时候对什么做了什么
GC在什么时候对什么做了什么?
要回答这个问题,先了解下GC的发展史、jvm运行时数据区的划分、jvm内存分配策略、jvm垃圾收集算法等知识。
先说下jvm运行时数据的划分,粗暴的分可以分为堆区(Heap)和栈区(Stack),但jvm的分法实际上比这复杂得多,大概分为下面几块:
1、程序计数器(Program Conuter Register)
程序计数器是一块较小的内存空间,它是当前线程执行字节码的行号指示器,字节码解释工作器就是通过改变这个计数器的值来选取下一条需要执行的指令。它是线程私有的内存,也是唯一一个没有OOM异常的区域。
2、Java虚拟机栈区(Java Virtual Machine Stacks)
也就是通常所说的栈区,它描述的是Java方法执行的内存模型,每个方法被执行的时候都创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等。每个方法被调用到完成,相当于一个栈帧在虚拟机栈中从入栈到出栈的过程。此区域也是线程私有的内存,可能抛出两种异常:如果线程请求的栈深度大于虚拟机允许的深度将抛出StackOverflowError;如果虚拟机栈可以动态的扩展,扩展到无法动态的申请到足够的内存时会抛出OOM异常。
3、本地方法栈(Native Method Stacks)
本地方法栈与虚拟机栈发挥的作用非常相似,区别就是虚拟机栈为虚拟机执行Java方法,本地方法栈则是为虚拟机使用到的Native方法服务。
4、堆区(Heap)
所有对象实例和数组都在堆区上分配,堆区是GC主要管理的区域。堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区。此块内存为所有线程共享区域,当堆中没有足够内存完成实例分配时会抛出OOM异常。
5、方法区(Method Area)
方法区也是所有线程共享区,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。GC在这个区域很少出现,这个区域内存回收的目标主要是对常量池的回收和类型的卸载,回收的内存比较少,所以也有称这个区域为永久代(Permanent Generation)的。当方法区无法满足内存分配时抛出OOM异常。
6、运行时常量池(Runtime Constant Pool)
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。
垃圾收集(Garbage Collection)并不是Java独有的,最早是出现在Lisp语言中,它做的事就是自动管理内存,也就是下面三个问题:
1、什么时候回收
2、哪些内存需要回收
3、如何回收
1、什么时候回收?
上面说到GC经常发生的区域是堆区,堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区。
1.1 对象优先在Eden中分配,当Eden中没有足够空间时,虚拟机将发生一次Minor GC,因为Java大多数对象都是朝生夕灭,所以Minor GC非常频繁,而且速度也很快;
1.2 Full GC,发生在老年代的GC,当老年代没有足够的空间时即发生Full GC,发生Full GC一般都会有一次Minor GC。大对象直接进入老年代,如很长的字符串数组,虚拟机提供一个-XX:PretenureSizeThreadhold参数,令大于这个参数值的对象直接在老年代中分配,避免在Eden区和两个Survivor区发生大量的内存拷贝;
1.3 发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则进行一次Full GC,如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,那只会进行一次Minor GC,如果不允许,则改为进行一次Full GC。
2、哪些内存需要回收
jvm对不可用的对象进行回收,哪些对象是可用的,哪些是不可用的?Java并不是采用引用计数算法来判定对象是否可用,而是采用根搜索算法(GC Root Tracing),当一个对象到GC Roots没有任何引用相连接,用图论的来说就是从GC Roots到这个对象不可达,则证明此对象是不可用的,说明此对象可以被GC。对于这些不可达对象,也不是一下子就被GC,而是至少要经历两次标记过程:如果对象在进行根搜索算法后发现没有与GC Roots相连接的引用链,那它将会第一次标记并且进行一次筛选,筛选条件是此对象有没有必要执行finalize()方法,当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用执行过一次,这两种情况都被视为没有必要执行finalize()方法,对于没有必要执行finalize()方法的将会被GC,对于有必要有必要执行的,对象在finalize()方法中可能会自救,也就是重新与引用链上的任何一个对象建立关联即可。
3、如何回收
选择不同的垃圾收集器,所使用的收集算法也不同。
在新生代中,每次垃圾收集都发现有大批对象死去,只有少量存活,则使用复制算法,新生代内存被分为一个较大的Eden区和两个较小的Survivor区,每次只使用Eden区和一个Survivor区,当回收时将Eden区和Survivor还存活着的对象一次性的拷贝到另一个Survivor区上,最后清理掉Eden区和刚才使用过的Survivor区,Eden和Survivor的默认比例是8:1,可以使用-XX:SurvivorRatio来设置该比例。
而老年代中对象存活率高,没有额外的空间对它进行分配担保,必须使用“标记-清理”或“标记-整理”算法。
⑵ java中GC指的是什么
gc是指垃圾回收机制,当一个对象不能再被后续程序所引用到时,这个对象所占用的内存空间就没有存在的意义了,java虚拟机会不定时的去检测内存中这样的对象,然后回收这块内存空间。
⑶ Java 等语言的 GC 为什么不及时释放内存
c语言没有垃圾回收机制,所有new出来的内存都要手动释放,优点是效率高,
一旦free立即执行,缺点是手动释放xd.
java有独立的gc线程,
而且由于gc线程执行优先级很低,垃圾能不能及时回收取
决于gc策略和工作线程的执行密度.
⑷ Java系统中GC频繁启动是什么原因
GC频繁发生的原因是堆空间不足。
修改permanent的大小是解决不了问题的,一般来说,permanent(持久带)的变化并不大,如果持久带不够用,一般不会GC,而是直接抛出持久带的OOM( out of memory)
所以,解决该公司的问题,最重要的是提高最小堆空间-Xms和最大堆空间-Xmx 的大小,提高年轻带-Xmn有助于在一定的程度解决GC的问题,但是注意,这些只是很简单的讨论。个人觉得,频繁GC发生的问题,最好是看看内存的DUMP文件,进行分析,在对JVM参数进行相对的配置。
JVM相关的问题还是比较复杂的,并不是几句对参数的描述就能解决问题,你还是要多看JVM相关资料。
评论(0)
⑸ 深入理解 Java 之 GC 到底如何工作
GarbageCollection简称为GC,是垃圾回收的意思、内存处理器是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃。Java语言提供的GC功能可以自动的检测对象是否超过作用域,从而达到自动回收内存的目的,java语言没有提供释放已分配内存的显示操作方法,资源回收工作全部交由GC来完成,程序员不能精确的控制垃圾回收的时机。
GC在实现垃圾回收时的基本原理:
Java的内存管理实际就是对象的管理,其中包括对像的分配和释放。对于程序员来说,分配对象使用new关键字,释放对象时只是将对象赋值为null,让程序员不能够再访问到这个对象,该对象被称为“不可达”。GC将负责回收所有“不可达”对象的内存空间。
对于GC来说,当程序员创建对象时,GC就开始监控这个对象地址、大小以及使用情况。通常GC采用有向图的方式记录并管理堆中的所有对象,通过这种方式确定哪些对象是“可达”的,哪些对象是“不可达”的。当GC确定一些对象为“不可达时”GC就有责任回收这些内存空间,但为了GC能够在不同的平台上实现,java规范对GC的很多行为都没有进行严格的规定。例如对于采用什么类型的回收算法、什么时候进行回收等重要问题都没有明确的规定,因此不同的JVM实现着不同的的实现算法,这也给JAVA程序员的开发带来了很多不确定性。
⑹ Java中 gc的作用是什么
System.gc()用来强制立即回收垃圾,即释放内存。
java对内存的释放采取的垃圾自动回收机制,在编程的时候不用考虑变量不用时释放内存,java虚拟机可以自动判断出并收集到垃圾,但一般不会立即释放它们的内存空间,当然也可以在程序中使用System.gc()来强制垃圾回收,但是要注意的是,系统并不保证会立即进行释放内存
⑺ IBM Java如何做到高性能GC的实现内幕
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 top heap limit heap base heap base是heap的起始地址 heap top是heap的结束地址 heaplimit 是当前程序使用的那段heap可以进行扩展和收缩的极限 你可以用 Xmx参数在java运行的时候对heap top和heap base进行控制 Alloc bits 和 mark bits heap top allocmax markemax heap limit alloc size marksize heap 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那 lishixin/Article/program/Java/JSP/201311/19555
⑻ java中GC是什么为什么要有GC
gc是指垃圾回收机制,当一个对象不能再被后续程序所引用到时,这个对象所占用的内存空间就没有存在的意义了,java虚拟机会不定时的去检测内存中这样的对象,然后回收这块内存空间。
⑼ GC是什么GC的作用有了GC那java中还有内存泄露么求解答
它摈弃了C++中一些繁琐容易出错的东西。其中有一条就是这个GC。 写C/C++程序,程序员定义了一个变量,就是在内存中开辟了一段相应的空间来存值。内存再大也是有限的,所以当程序不再需要使用某个变量的时候,就需要释放这个内存空间资源,好让别的变量来用它。在C/C++中,释放无用变量内存空间的事情要由程序员自己来解决。就是说当程序员认为变量没用了,就应当写一条代码,释放它占用的内存。这样才能最大程度地避免内存泄露和资源浪费。但是这样显然是非常繁琐的。程序比较大,变量多的时候往往程序员就忘记释放内存或者在不该释放的时候释放内存了。而且释放内存这种事情,从开发角度说,不应当是程序员所应当关注的。程序员所要做的应该是实现所需要的程序功能,而不是耗费大量精力在内存的分配释放上。 Java有了GC,就不需要程序员去人工释放内存空间。当Java虚拟机发觉内存资源紧张的时候,就会自动地去清理无用变量所占用的内存空间。当然,如果需要,程序员可以在Java程序中显式地使用System.gc()来强制进行一次立即的内存清理。但是要注意的是,系统并不保证会立即进行释放内存。Java的内存泄漏问题Java的一个重要优点就是通过垃圾收集器(Garbage Collection,GC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,因为Java也存在内存泄露,但它的表现与C++不同。随着越来越多的服务器程序采用Java技术,例如JSP,Servlet, EJB等,服务器程序往往长期运行。另外,在很多嵌入式系统中,内存的总量非常有限。内存泄露问题也就变得十分关键,即使每次运行少量泄漏,长期运行之后,系统也是面临崩溃的危险。什么是内存泄漏?在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。
因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。下面给出了一个简单的内存泄露的例子:在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。Vector v=new Vector(10); for (int i=1;i<100; i++) { Object o=new Object(); v.add(o); o=null; }
//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。 综上所述,Java也存在内存泄露问题,其原因主要是一些对象虽然不再被使用,但它们仍然被引用。为了解决这些问题,我们可以通过软件工具来检查内存泄露,检查的主要原理就是暴露出所有堆中的对象,让程序员寻找那些无用但仍被引用的对象。
⑽ Java垃圾回收:GC在什么时候对什么做了什么
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"。