GC学习

java的内存大致可以分为5个区域:堆,方法区,jvm栈,本地方法栈,程序计数器

 

程序计数器:主要用于记录当前线程执行的字节码执行到了第几行。

 

jvm栈:当每个方法执行的时候都会创建一个栈帧(statck frame),栈帧里面存储了局部变量表、操作站、动态链接、方法出口等,局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等。在这里一般会有2种异常,当你的线程深度大于虚拟机规定的栈深度的时候,就会产生StatckOverFlowError(栈溢出)。但是一般虚拟的的栈都可以动态地扩展,当你扩展的大小大于分配的内存的时候就会抛出OutOfMemoryError(内存溢出)。

 

本地方法栈:它的实现和jvm栈类似,只是它执行的是native的方法,在很多虚拟机中,会将本地方法栈与虚拟机栈放在一起使用。

 

:这是java最大的一块内存区域,也是最重要的一块内存区域,一般在这里存放对象的实例,在逻辑上我们要求堆内存是连续的。我们可以把它想象为一个传送带,每当你创建一格对象的时候它就会往后移动一格,所以java中对象的创建速度很快。

 

方法区:在java虚拟机中,方法区被认为是堆逻辑实现的一部分,在分代收集中把它定为永久代。里面主要存储的是虚拟机已经加载的类信息,final常量、静态变量、编译器即时编译的代码等。运行时常量池是方法区的一部分,用于存储编译时就产生的字面常量,符号引用等。常量池也可以存储运行时生成的常量,比如字符串‘abc’已经在常量池中存在了,那么直接返回字符串在常量池中的地址。

 

在java中一个对象的访问方式

 

怎么理解GC?

要理解GC我们首先要知道java的内存分配和回收机制,概括来说就是分代分配,分代回收。

 

年轻代(Young Generation):对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉,这个GC机制被称为Minor GC或叫Young GC。注意,Minor GC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。

  年轻代可以被分为3个区域:

  1. 绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快;
  2. 最初一次,当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的);
  3.  下次Eden区满了,再执行一次Minor GC,将消亡的对象清理掉,将存活的对象复制到Survivor1中,然后清空Eden区;
  4.  将Survivor0中消亡的对象清理掉,将其中可以晋级的对象晋级到Old区,将存活的对象也复制到Survivor1区,然后清空Survivor0区;
  5. 当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代,但这只是个最大值,并不代表一定是这个值)之后,仍然存活的对象(其实只有一小部分,比如,我们自己定义的对象),将被复制到老年代。

  从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,一个Survivor中保存着当前还活着的对象,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。因此,这种方式分配内存和清理内存的效率都极高,这种垃圾回收的方式就是著名的“停止-复制(Stop-and-copy)”清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中),这不代表着停止复制清理法很高效,其实,它也只在这种情况下高效,如果在老年代采用停止复制,则效果会很差。

 

年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行Major GC,也叫 Full GC。

 

垃圾回收机制基于的思想是对象必须是“活”的,这么理解对象必须是“活”的呢?其实就是一组活的引用(GC roots),因为一个活的对象必然会被引用,因此我们只要遍历所有GC roots引用的对象,被引用到的对象就是活的。

  一般是

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象;

  总结就是,方法运行时,方法中引用的对象;类的静态变量引用的对象;类中常量引用的对象;Native方法中引用的对象。

  被称为是gc roots。

找到了存活的对象要怎么处理了,JVM虚拟机采用了一种自适应的技术:

1.停止复制(stop-and-copy):jvm虚拟机会暂停程序的运行,然后把存活的对象从旧堆复制到新堆,并使它们紧密地排列,但是这样会有一个问题,就是势必要给jvm虚拟机2倍的内存才能干这种事情。某些jvm虚拟机地做法是在堆里面分配2块较大地内存。还有一个问题就是,一般我们产生的垃圾不会特别多,我们把所有的对象复制过去会很浪费。

2.标记清扫(mark-and-sweep):它的思路和前面一样,遍历所有的引用,找出存活的对象打上一个标记,当所有对象都打完标记之后,把打上标记的对象清楚掉。

 

我遇到的问题,测试机升级了16G内存的时候,xms和xmx设置为10G出现了GC out of memory的问题。

xms的意思就是堆内存的初始分配空间,xmx的意思就是堆内存的最大分配空间。当可用堆内存小于40%时会设置到xmx,可用堆内存大于70%时会设置到xms。一般建议xms=xmx=内存的70%-75%,为什么要设置一样,因为当xms很小的时候会频繁地触发GC后调整堆内存的大小,这样很浪费。还有一个要注意设置的是新生代和老年代的大小,新生代的意思是所有新创建的对象都会在这里,尽量把新生代设置地大一点,因为如果设置地比较小的话就会频繁地执行Minor GC。但是也不能太大,因为太大会导致老年代很小,老年代太小会引起堆内存碎片的问题。

 

posted on 2017-04-30 01:10  cch_java  阅读(99)  评论(0编辑  收藏  举报

导航