最近在整理Java内存有关的东西

内存空间的划分

Sun JDK也是遵照jvm规范,将内存空间划分为方法区、堆、本地方法栈、pc寄存器、jvm方法栈。如下图:

关于jvm内存与垃圾回收算法_JVM

方法区
[-XX:PermSize-XX:MaxPermSize]

      方法区存放了要加载的类的信息、类中的静态变量、类中定义为final类型的常量、类中的field信息、类中的方法信息。方法区是全局共享的,特定条件下会进行GC,当方法区要使用的内存大于运行大小时会抛OOM异常。

      Sun JDK中这块内存对应Permanent Generation,也叫持久代,默认最小16M,最大64M,通过-XX:PermSize和-XX:MaxPermSize参数指定持久代的最小和最大值。

堆(线程共享)

1、JVM 会试图为相关Java对象在Eden Space中初始化一块内存区域。

2、当Eden空间足够时,内存申请结束;否则到下一步。 
3、JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收)。释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区。 
4、Survivor区被用来作为Eden及Old的中间交换区域,当Old区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区。 
5、当Old区空间不够时,JVM 会在Old区进行完全的垃圾收集(0级)。 
6、完全垃圾收集后,若Survivor及Old区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现“outofmemory”错误。

      堆用于存储对象实例及数组值,可以认为java中所有通过new操作符创建的对象都放在堆中,堆中对象由GC进行回收。这块内存大小可以通过两个参数进行指定:-Xms和-Xmx。

-Xms表示jvm启动时申请的最小heap内存,默认为物理内存的1/64但小于1G。

-Xmx表示jvm可申请的最大heap内存,默认为物理内存的1/4但小于1G。

默认当空闲堆内存小于40%是,jvm会增大heap到-Xmx指定的大小,这个比例可以通过参数-XX:MinHeapFreeRatio=来指定;默认当空闲堆内存大于70%时,jvm会减小heap到-Xms指定的大小,这个比例可以通过参数-XX:MaxHeapFreeRatio=来指定。建议将-Xms和-Xmx设置为相同的值,以避免频繁调整jvm堆大小。

 

由于不同对象在内存中存活的时间不同,有的很快就可以回收,有的可能生命周期贯穿整个jvm的生命周期,所以在Sun JDK从1.2开始就对堆内存进行分代管理。如下图:

新生代(New Generation)
[-Xmn]

大多数情况下java程序中创建的对象都是从新生代分配内存,新生代有两部分组成:Edenspace和两块大小相等的Survivorspace(S0和S1)。

可以通过参数-Xmn来指定新生代的大小,通过-XX:SurvivorRatio来指定Eden space和Survivor space的大小。

操作系统内存。

JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。

Sun JDK中通过参数-Xss来指定jvm方法栈大小,当jvm方法栈空间不足时抛StackOverflowError.

内存的分配

 jvm堆是所有线程共享的,因此在堆上分配内存需要加锁,从而导致创建对象开销较大。当堆空间不足时会触发GC,如果GC后堆空间仍然不足会跑OutOfMemory异常。

Sun JDK为提升内存分配效率,在新生代的Eden space为每个线程创建一个叫做TLAB(Thread Local Allocation Buffer)的区域,当在线程中创建对象时jvm会尽量在TLAB中分配内存,这时就不需要加锁,节省了创建对象的开销。

还有一种基于逃逸分析的方法,jvm会在栈上直接分配内存,线程结束时自动就释放掉。

 

内存的回收(判断该对象是否可达,从而判断对象是否存活)

1.复制算法(用于新生代)

用于新生代从Eden区到survivor0或者survivor1移动的时候

从根集合扫描如果对象被引用,就会被copy到survivor0或者survivor1,然后剩下的都是不可达对象,就可以被回收掉,然后S0或者S1的对象就会到老年代中,在存活对象比较少的时候很高效并且不会产生内存碎片,就是内存需要额外划分一块survivor区出来

2.标记清除算法(用于老年代)

就是使用根搜索算法扫描,如果可达就标记,当扫描完成后,就对未标记的对象进行回收,它不需要像复制算法一样,需要一个新的内存区,而且不会对对象进行copy,这种对对象存活的多的情况下很高效,但是这样会产生内存碎片

3.标记整理压缩算法(用于老年代)

就是在标记清除算法之后对内存碎片进行整理,只是他会对没有清理的对象进行移动,代价高

最后

创建对象(尤其是大对象)可能会触发GC,所以需要频繁创建的对象可以考虑通过池来解决;

注意对象的作用域,不用的及时显示设置为null,GC就可以收掉;

通过设置jvm参数对应用调优,有很多工具来帮助分析jvm内存使用清空,茹jconsole,jstat,jmap等等,但要具体系统具体分析,总之尽量减少GC,尤其是Full GC(会导致线程阻塞)。

 

更多内容请关注微信公众号“外里科技

关于jvm内存与垃圾回收算法_方法区_02