目录
概述
对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出的问题。由虚拟机管理内存。
Java虚拟机运行时数据区
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域各有各自的用途,以及创建和销毁的 时间,有的区域随着虚拟机进程的 启动而存在,有些区域则依赖于用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(Java SE 7 版)》的规定,Java虚拟机所管理的 内存将会包括以下几个运行时区域:
Java堆
对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建。 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存(随着JIT编译器 的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换-优化技术会导致一些微妙的变化发生,也就不是所有的对象分配在堆上了)。
Java堆是垃圾回收收集器管理的主要机制。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。在实现时,既可以实现固定大小的,也可以实现可拓展的。当前主流的虚拟机都是按照可拓展来实现 (通过-Xmx和-Xms控制)。
Java堆内存的划分
从垃圾回收的角度来看,由于现在收集器基本采用分代收集算法,所以Java堆还可以细分为新生代和老年代。其中新生代 ( Young ) 又被划分为:Eden空间、From Survivor空间和To Survivor空间三个区域,如下图所示:
从JDK8开始,Metaspace(元空间)替代了永久代,如下图所示:
1、堆大小 = 新生代( Young ) + 老年代( Old ),其可以通过参数 –Xms、-Xmx 来指定:–Xms用于设置初始分配大小,默认为理内存的1/16;-Xmx用于设置最大分配内存,默认为物理内存的1/4。默认情况下,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小,老年代 ( Old ) = 2/3 的堆空间大小,如下代码:
public class Test { public static void main(String[] args) { System.out.println(); long maxMemory = Runtime.getRuntime().maxMemory();//返回Java虚拟机最大内存量(单位:字节) long totalMemory = Runtime.getRuntime().totalMemory();//返回Java虚拟机内存总量(单位:字节) System.out.println("MAX_MEMORY:"+(maxMemory/(double)1024/1024)+"MB"); System.out.println("TOTAL_MEMORY:"+(totalMemory/(double)1024/1024)+"MB"); } }
2、新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,为了便于区分,两个 Survivor 区域分别被命名为 from 和 to。默认情况下,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。JVM 每次只使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的,因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
工作原理
Java技术体系中所提倡的自动内存管理最终可以归结为自动化的解决两个问题:1、给对象分配内存。2、回收分配给对象 的内存。
对象的内存分配,就是在堆上分配(随着JIT编译器 的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换-优化技术会导致一些微妙的变化发生,也就不是所有的对象分配在堆上了)。对象主要分配在新生代的Eden上,如果启动了本地线程分配缓冲,将按照线程优先在TLAB上分配。少数情况也可能分配在老年代中,分配的规则并不是固定的,主要取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数设置。
a、Eden区为Java对象分配堆内存,当 Eden 区没有足够空间分配时,JVM发起一次Minor GC,将Eden区仍然存活的对象放入Survivor from区,并清空 Eden 区;
b、Eden区被清空后,继续为新的Java对象分配堆内存;
c、当Eden区再次没有足够空间分配时,JVM对Eden区和Survivor from区同时发起一次 Minor GC,把存活对象放入Survivor to区,同时清空Eden 区和Survivor from区;
d、Eden区继续为新的Java对象分配堆内存,并重复上述过程:Eden区没有足够空间分配时,把Eden区和某个Survivor区的存活对象放到另一个Survivor区;
e、JVM给每个对象设置了一个对象年龄(Age)计数器,每熬过一场Minor GC,对象年龄增加1岁,当它的年龄增加到阈值(默认为15,可以通过-XX:MaxTenuringThreshold 参数自定义该阀值),将被“晋升”到老年代,当 Old 区也被填满时,JVM发起一次 Major GC,对 Old 区进行垃圾回收。