一、内存分布
Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如下图所示:
本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与
虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
(个人理解:虚拟机栈就是通常所说的栈,本地方法栈服务于native方法)
运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
直接内存(Direct Memory)
直接内存并不是JVM管理的内存,但这部分内存经常被使用,也可能导致OutOfMemoryError异常。
JDK中有一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,将由C语言实现的native函数库直接分配堆外内存,用存储在JVM堆中的DirectByteBuffer来引用。由于直接内存收到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。
二、需要回收的区域和对象
回收区域:堆和方法区
如何判断一个对象已死(即可回收对象),一般有引用计数算法和可达性分析算法
1、引用计数算法:每个对象都有一个引用计数器,每当有一个地方引用它时计数器就加1,引用失效时计数器就减1,当计数器为0时,该对象就可以回收。缺点是循环引用对象不能被回收。
2、可达性分析算法:一系列“GC ROOTs”的对象作为起点,当某个对象与GC ROOTS不可达时,该对象即被判断为可回收对象。可以作为GC ROOTs对象的有以下几种:
3、扩充的引用
引用分为四种:(不会被回收的,可回收也可不回收,肯定会被回收、不需要理会的)
a> 强引用(Strong Reference).就是为刚被new出来的对象所加的引用,它的特点就是,永远不会被回收。
b> 软引用(Soft Reference).声明为软引用的类,是可被回收的对象,如果JVM内存并不紧张,这类对象可以不被回收,如果内存紧张,则会被回收。此处有一个问题,既然被引用为软引用的对象可以回收,为什么不去回收呢?其实我们知道,Java中是存在缓存机制的,就拿字面量缓存来说,有些时候,缓存的对象就是当前可有可无的,只是留在内存中如果还有需要,则不需要重新分配内存即可使用,因此,这些对象即可被引用为软引用,方便使用,提高程序性能。
c> 弱引用(Weak Reference).弱引用的对象就是一定需要进行垃圾回收的,不管内存是否紧张,当进行GC时,标记为弱引用的对象一定会被清理回收。
d> 虚引用(Phantom Reference).虚引用弱的可以忽略不计,JVM完全不会在乎虚引用,其唯一作用对象被回收时会收到一些通知。
4、对象回收处理过程
对象被回收前需要进行两次标记过程。第一次是对象与GC Roots间不可达,即被第一次标记并且筛选该对象是否有必要执行finalize()方法,将筛选的对象放到一个执行队列中,没有必要执行finalize方法的对象会被回收;第二次仅对队列中的对象进行标记,如果在执行finalize方法中将对象与GC ROOTS关联上,则该对象将不会被回收,否则该对象将被回收。
三、内存分配
Java内存分配和回收的机制概括的说,就是:分代分配,分代回收。对象将根据存活的时间被分为:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)
年轻代(Young Generation):对象被创建时,内存的分配首先发生在年轻代(大对象可以直接 被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消 亡的),这个GC机制被称为Minor GC或叫Young GC。注意,Minor GC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。
年轻代上的内存分配是这样的,年轻代可以分为3个区域:Eden区(伊甸园,亚当和夏娃偷吃禁果生娃娃的地方,用来表示内存首次分配的区域,再 贴切不过)和两个存活区(Survivor 0 、Survivor 1)。内存分配过程为:
- 绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快;
- 当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的);
- 此后,每次Eden区满了,就执行一次Minor GC,并将剩余的对象都添加到Survivor0;
- 当Survivor0也满的时候,将其中仍然活着的对象直接复制到Survivor1,以后Eden区执行Minor GC后,就将剩余的对象添加Survivor1(此时,Survivor0是空白的)。
- 当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代)之后,仍然存活的对象(其实只有一小部分,比如,我们自己定义的对象),将被复制到老年代。
年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次 Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时, 将执行Major GC,也叫 Full GC。
四、垃圾收集算法
1、标记-清除算法
分两个阶段,标记阶段和清除阶段。首选标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。
2、复制算法
将内存分为大小相等的两块,每次只使用其中一块。当一块内存用完了,就将活着的对象复制到另一块上面,最后一次性清除已使用过的内存空间。(一般新生代采用这种算法)
3、标记-整理算法
也是分为两个阶段。第一个阶段是标记,第二个阶段是将所有存活对象移动到一端,然后直接清理掉边界以外的内存。
4、分代收集算法
根据对象存活时间不同将内存分为几部分。一般将堆分为新生代和年老代,根据年代的特点采用最适合的收集算法。例如,新生代一般采用复制算法,年老代采用标记-清除或者标记-整理算法。
五、JVM常用内存参数设置
a: -Xmx<n>
指定 jvm 的最大 heap 大小 , 如 :-Xmx=2g
b: -Xms<n>
指定 jvm 的最小 heap 大小 , 如 :-Xms=2g , 高并发应用, 建议和-Xmx一样, 防止因为内存收缩/突然增大带来的性能影响。
c: -Xmn<n>
指定 jvm 中 New Generation 的大小 , 如 :-Xmn256m。 这个参数很影响性能, 如果你的程序需要比较多的临时内存,建议设置到512M, 如果用的少, 尽量降低这个数值, 一般来说128/256足以使用了。
d: -XX:PermSize=<n>
指定 jvm 中 Perm Generation 的最小值 , 如 :-XX:PermSize=32m。 这个参数需要看你的实际情况,。可以通过jmap 命令看看到底需要多少。
e: -XX:MaxPermSize=<n>
指定 Perm Generation 的最大值 , 如 :-XX:MaxPermSize=64m
f: -Xss<n>
指定线程桟大小 , 如 :-Xss128k, 一般来说,webx框架下的应用需要256K。 如果你的程序有大规模的递归行为,请考虑设置到512K/1M。 这个需要全面的测试才能知道。 不过,256K已经很大了。 这个参数对性能的影响比较大的。
g: -XX:NewRatio=<n>
指定 jvm 中 Old Generation heap size 与 New Generation 的比例 , 在使用 CMS GC 的情况下此参数失效 , 如 :-XX:NewRatio=2
h: -XX:SurvivorRatio=<n>
指 定 New Generation 中 Eden Space 与一个 Survivor Space 的 heap size 比例 ,-XX:SurvivorRatio=8, 那么在总共 New Generation 为 10m 的情况下 ,Eden Space 为 8m
i: -XX:MinHeapFreeRatio=<n>
指定 jvm heap 在使用率小于 n 的情况下 ,heap 进行收缩 ,Xmx==Xms 的情况下无效 , 如 :-XX:MinHeapFreeRatio=30
j: -XX:MaxHeapFreeRatio=<n>
指定 jvm heap 在使用率大于 n 的情况下 ,heap 进行扩张 ,Xmx==Xms 的情况下无效 , 如 :-XX:MaxHeapFreeRatio=70
k: -XX:LargePageSizeInBytes=<n>
指定 Java heap 的分页页面大小 , 如 :-XX:LargePageSizeInBytes=128m
六、内存分配与回收策略
对象优先在Eden分配;
大对象直接进入老年代;(例如:长字符串和数组)
长期存活的对象将进入老年代;(Survivor空间中经过一定次数minor GC仍旧存活的对象)
动态对象年龄判定;(Survivor空间中对象不一定非等到一定年龄才能移动到年老代,也可以通过相同年龄对象总大小大于Survivor空间的一半,该年龄及以上大小的对象都被移动到年老代)
空间分配担保;(根据年老代上的最大可用连续空间是否大于整个新生代对象的空间,如果大于可以确保是安全的,可以直接minor GC;如果不是根据情况选择minor GC还是full GC)