很多的时间大家都很迷茫。但是想想要走的路,你会发现一切又是那么的清晰。
今天看了一点JVM的第三章,感觉自己理解的不是很透彻。所以还是写点东西来加深点印象吧。
1、概述
由于之前我们将java中的内存大致的分为了堆和栈。所以涉及到内存释放的问题主要就是指这两方面。之前我们介绍道程序计数器、虚拟机栈、本地方法栈都是属于栈的,并且都是线程隔离的。是随线性而生,随线程而死的。但是java的堆和方法区是堆类型的,是线程共享的。所以是需要gc垃圾回收的,垃圾回收机制关注的就是这部分的内存回收。
2、对象的生与死
在堆里面存活了几乎所有的对象实例。在收集垃圾之前我们需要知道哪些对象是活的哪些是死的。
第一种方法是引用计数法。给每个对象添加一个引用计数器。每当有调用的时候就加1,当引用失效的时候就减1。任意时刻计时器的值是0表示该对象不需要使用了就可以清除。不过hotspot并没有使用这种方案。
java中使用的是可达性分析算法。从一系列称为GC Roots的对象作为起点,然后从这些起点开始向下搜索。当无法搜索到的对象就可以将其作为清除的对象。那么,GC Roots的对象是怎么确定的?
在java语言中,可以作为GC Roots对象的有:
1 虚拟机栈(栈帧中的本地变量表)中引用的对象。
2 方法区中类静态属性引用的对象。
3 方法区中常用引用的对象。
4 本地方法栈中JNI引用的对象。
这里在说说引用。如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。java中引用又分为四种:强引用、软引用、弱引用、虚引用。
要宣布一个对象是否死亡,至少要经历两次的标记过程。第一次将不满足可达性分析的没有与GC Roots相连的对象进行标记和筛选。(筛选的条件是是否需要执行finalize())。之后JVM会将这些筛选的对象全部放置在一个F队列中。之后在这个F队列中进行第二次筛选,这时候被选上的对象就可以回收了。
注意:任何对象的finalize()方法只执行一次。
我们知道在hotspot中方法区也属于堆了。但是要知道该区域回收的性价比很低。主要回收的是:废弃的常量和无用的类。对于在方法区中频繁自定义类加载器的场景都需要虚拟机具备类的卸载的功能。
3 垃圾收集算法
a 标记-清除算法。
主要分为标记和清除两部分。主要的不足是:执行的效率不高。第二个就是产生了大量的不连续的内存碎片。碎片太多可能会导致不能产生大一点的对象。
b 复制算法
它主要是将可用的内存按容量划分为大小相等的两块。当一块内存块用完了,先将还存活的对象拷贝到另一块中。然后清理已经使用过空间。这样每次都是对一半的空间进行清理的。
c 标记-整理算法
主要用于清理老年代的内存空间的。其中标记过程和标记-清除是一致的,但是后面不是清理而是让存活的对象都移到一端。然后直接清除端边界以外的内存。
4 堆内存新生代的分区
hotspot是将堆内存中存放新生代的内存分成一块较大的Eden空间和两块较小的Survivor空间。当垃圾回收时,现将Eden和Survivor空间A中存活的对象复制到另一块空闲的Survivor空间B中。最后清理Eden和Survivor空间A。Hotspot默认的Eden和Survivor的容量比是8:1。也就是新生代占整个新生代容量的90%(80%+10%),只有10%的内存暂时没有使用。当我们清理这90%的新生代空间时。如果Survivor空间无法放置存活的对象的时候我们只能将存活的对象通过担保机制进入老年代。
jvm的垃圾收集是按分代思想的。在hotspot中,会将java堆分成新生代和老年代。
5、Hotspot的算法实现
a jvm会使用一组称为OopMap的数据结构来获取哪些地方存放着对象的引用。找到了引用就可以知道GC Roots节点的位置。
b 找到合适的安全点来让程序暂停下来。因为执行gc操作的时候其他的程序是必须要停下来的。主要有两种方法:抢先式中断(几乎没有被使用过)和主动式中断。
c 如果遇到程序在当前的线程不执行,即当前线程是无法响应JVM的中断请求。这个时候需要一个安全区域。当程序在安全区域的时候只要有gc操作就立马执行。
6、垃圾收集器的种类
java一共有7中不同的分代收集器。其中部分的收集器是可以配合使用的。其中,新生代采取复制算法暂停所有的用户线程。老年代采取标记-整理算法暂停所有的用户线程。
a Serial收集器
单线程的收集器。Serial收集器对于运行在Client模式下的虚拟机是很好的选择。主要是收集新生代的垃圾。
b ParNew收集器
Serial收集器的多线程版。功能和Serial收集器一致。但它是运行在Server模式下的虚拟机首选的新生代收集器。
c Parallel Scavenge收集器
与ParNew收集器几乎一样。但在可控制gc的暂停时间的时候是利用吞吐量来控制的。
d Serial Old 收集器
Serial收集器收集老年代的版本。采用的是标记-整理的算法。
e Parallel Old收集器
Parallel Scavenge收集器的老年代版本。
f CMS收集器
是一种以获取最短回收停顿时间为目标的收集器。是基于标记-清除算法实现的。CMS收集器的优点:并发收集、低停顿
g G1收集器
面向服务端应用的垃圾收集器。它的运作可以分为:初始标记、并发标记、最终标记、筛选回归。
初始标记只是标记一下GC Roots能直接关联的对象。这个阶段需要停顿线程但耗时较短。
并发标记是从GC Roots开始对堆中对象进行可达性分析,找出存活的对象。这个阶段耗时较长,但与用户线程并发执行。
最终标记是GC Roots节点对象不可达的对象的引用。
最后在进行筛选回放,因为还是有对象会再次被利用上的。执行完这一步就可以进行垃圾收集了。
7、内存分配和回收的策略
a 对象优先在Eden空间分配
b 大对象直接进入老年代
c 长期存活的对象进入老年代
d 要记住老年代会为新生代进行空间分配的担保
e 新生代GC回收的速度快 ,老年代GC回收速度慢。
今天看了一点JVM的第三章,感觉自己理解的不是很透彻。所以还是写点东西来加深点印象吧。
1、概述
由于之前我们将java中的内存大致的分为了堆和栈。所以涉及到内存释放的问题主要就是指这两方面。之前我们介绍道程序计数器、虚拟机栈、本地方法栈都是属于栈的,并且都是线程隔离的。是随线性而生,随线程而死的。但是java的堆和方法区是堆类型的,是线程共享的。所以是需要gc垃圾回收的,垃圾回收机制关注的就是这部分的内存回收。
2、对象的生与死
在堆里面存活了几乎所有的对象实例。在收集垃圾之前我们需要知道哪些对象是活的哪些是死的。
第一种方法是引用计数法。给每个对象添加一个引用计数器。每当有调用的时候就加1,当引用失效的时候就减1。任意时刻计时器的值是0表示该对象不需要使用了就可以清除。不过hotspot并没有使用这种方案。
java中使用的是可达性分析算法。从一系列称为GC Roots的对象作为起点,然后从这些起点开始向下搜索。当无法搜索到的对象就可以将其作为清除的对象。那么,GC Roots的对象是怎么确定的?
在java语言中,可以作为GC Roots对象的有:
1 虚拟机栈(栈帧中的本地变量表)中引用的对象。
2 方法区中类静态属性引用的对象。
3 方法区中常用引用的对象。
4 本地方法栈中JNI引用的对象。
这里在说说引用。如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。java中引用又分为四种:强引用、软引用、弱引用、虚引用。
要宣布一个对象是否死亡,至少要经历两次的标记过程。第一次将不满足可达性分析的没有与GC Roots相连的对象进行标记和筛选。(筛选的条件是是否需要执行finalize())。之后JVM会将这些筛选的对象全部放置在一个F队列中。之后在这个F队列中进行第二次筛选,这时候被选上的对象就可以回收了。
注意:任何对象的finalize()方法只执行一次。
我们知道在hotspot中方法区也属于堆了。但是要知道该区域回收的性价比很低。主要回收的是:废弃的常量和无用的类。对于在方法区中频繁自定义类加载器的场景都需要虚拟机具备类的卸载的功能。
3 垃圾收集算法
a 标记-清除算法。
主要分为标记和清除两部分。主要的不足是:执行的效率不高。第二个就是产生了大量的不连续的内存碎片。碎片太多可能会导致不能产生大一点的对象。
b 复制算法
它主要是将可用的内存按容量划分为大小相等的两块。当一块内存块用完了,先将还存活的对象拷贝到另一块中。然后清理已经使用过空间。这样每次都是对一半的空间进行清理的。
c 标记-整理算法
主要用于清理老年代的内存空间的。其中标记过程和标记-清除是一致的,但是后面不是清理而是让存活的对象都移到一端。然后直接清除端边界以外的内存。
4 堆内存新生代的分区
hotspot是将堆内存中存放新生代的内存分成一块较大的Eden空间和两块较小的Survivor空间。当垃圾回收时,现将Eden和Survivor空间A中存活的对象复制到另一块空闲的Survivor空间B中。最后清理Eden和Survivor空间A。Hotspot默认的Eden和Survivor的容量比是8:1。也就是新生代占整个新生代容量的90%(80%+10%),只有10%的内存暂时没有使用。当我们清理这90%的新生代空间时。如果Survivor空间无法放置存活的对象的时候我们只能将存活的对象通过担保机制进入老年代。
jvm的垃圾收集是按分代思想的。在hotspot中,会将java堆分成新生代和老年代。
5、Hotspot的算法实现
a jvm会使用一组称为OopMap的数据结构来获取哪些地方存放着对象的引用。找到了引用就可以知道GC Roots节点的位置。
b 找到合适的安全点来让程序暂停下来。因为执行gc操作的时候其他的程序是必须要停下来的。主要有两种方法:抢先式中断(几乎没有被使用过)和主动式中断。
c 如果遇到程序在当前的线程不执行,即当前线程是无法响应JVM的中断请求。这个时候需要一个安全区域。当程序在安全区域的时候只要有gc操作就立马执行。
6、垃圾收集器的种类
java一共有7中不同的分代收集器。其中部分的收集器是可以配合使用的。其中,新生代采取复制算法暂停所有的用户线程。老年代采取标记-整理算法暂停所有的用户线程。
a Serial收集器
单线程的收集器。Serial收集器对于运行在Client模式下的虚拟机是很好的选择。主要是收集新生代的垃圾。
b ParNew收集器
Serial收集器的多线程版。功能和Serial收集器一致。但它是运行在Server模式下的虚拟机首选的新生代收集器。
c Parallel Scavenge收集器
与ParNew收集器几乎一样。但在可控制gc的暂停时间的时候是利用吞吐量来控制的。
d Serial Old 收集器
Serial收集器收集老年代的版本。采用的是标记-整理的算法。
e Parallel Old收集器
Parallel Scavenge收集器的老年代版本。
f CMS收集器
是一种以获取最短回收停顿时间为目标的收集器。是基于标记-清除算法实现的。CMS收集器的优点:并发收集、低停顿
g G1收集器
面向服务端应用的垃圾收集器。它的运作可以分为:初始标记、并发标记、最终标记、筛选回归。
初始标记只是标记一下GC Roots能直接关联的对象。这个阶段需要停顿线程但耗时较短。
并发标记是从GC Roots开始对堆中对象进行可达性分析,找出存活的对象。这个阶段耗时较长,但与用户线程并发执行。
最终标记是GC Roots节点对象不可达的对象的引用。
最后在进行筛选回放,因为还是有对象会再次被利用上的。执行完这一步就可以进行垃圾收集了。
7、内存分配和回收的策略
a 对象优先在Eden空间分配
b 大对象直接进入老年代
c 长期存活的对象进入老年代
d 要记住老年代会为新生代进行空间分配的担保
e 新生代GC回收的速度快 ,老年代GC回收速度慢。