现在JVM的文章太多了,我就不起什么高大上名字了,主要当给自己做笔记,不过写的还挺明白的,往下看,说不定有哪句话就能点醒你。
一、JVM的内存模型
JVM被分为5个区域
这5个区域按线程是否安全又可分为线程共享和线程独立两类。
线程独立:每个线程自己有一个,因此线程安全。
线程共享:所有线程共享一个,因此相互影响,不安全。
下图中右边黄色的是线程独立的,左边绿色的是线程共享的。
线程独立
1,程序计数器
每个线程都有一个独立的程序计数器,用于记录栈帧,来确定程序运行的位置,使得线程切换时能正常的恢复到之前运行的位置。
如果当前线程是native方法,则其值为null。
2,Java虚拟机栈
每当执行Java方法时,会进行压栈操作,保存一个栈帧,先入后出。保证Java方法的执行顺序
栈帧:栈帧保存了一个方法的信息,包括局部变量表、操作数栈、动态链表、方法出口等等。
3,本地方法栈
每当执行native方法时,会进行压栈操作,保存一个栈帧,先入后出。有时程序会调用非Java代码,这时不会在Java虚拟机栈中操作,而是在本地方法栈中记录栈帧。
线程共享
4,方法区
用于存储已被虚拟机加载的类信息、字段信息、方法信息、常量、静态变量以及运行时常量池。
5,堆
这是JVM内存中最大的一块区域。大多数情况下所有对象实例及数组的创建都要在堆上分配内存。
最常被问到的就是堆中的内存回收机制。这个稍后再说。
二、堆内存结构
1.堆内存结构划分理念
按存储对象存活时间分类存放。
将预计很快被清理的对象放在一边,将预计长时间不需要清理的对象放在另一边。
这样,需要垃圾回收的区域就能大大减小。
2.堆内存结构划分
首先被分成新生代(Young) 和 老年代(Old) 。
默认比例是 Young:Old=1:2。
新生代
用来存放存活时间短的对象。
新生区又被分为伊甸区(Eden)、幸存者0(S0 survivor 0)、幸存者1(S1 survivor 1)。
默认比例是 Eden:S0:S1=8:1:1
伊甸区(Eden)
创建新对象时,其内存会被分配到伊甸区。
幸存者0和幸存者1
对伊甸区进行垃圾回收时,其中还保留引用的对象会被移动到幸存者区
老年代
老年代用来存放存活时间长的对象
3.为什么要这么划分
我们已经知道了堆内存被分为新生代和老年代。而新生代又被分为伊甸区(Eden)、幸存者0(S0 survivor 0)、幸存者1(S1 survivor 1)。
那么为什么要做这样的划分呢?
来自于开发者们巧妙的设计。
如果你敲过一段时间的Java代码你就会明白,80%的代码都是局部变量,用完就回收了,因此存活时间极短,可能一个for循环结束,一个方法结束,就有大量的对象不再被使用。然后,还有20%的对象存活时间较长,甚至整个系统停止之前,这些对象都不能释放内存。
因此,找出那些大概率不需要垃圾回收的对象,以及很快失效的对象变得至关重要。
所以有了新生代和老年代的概念。而大多数情况下只会在新生代中清理存活时间短的对象。为什么新生代和老年代是1:2?
一部分原因当然是,需要垃圾回收的部分当然越小,执行速度越快。当然也不是越小越好,太小会导致多次触发GC执行也会影响JVM的性能。
那么,如何找到这些对象呢?这就是JVM的垃圾回收机制了。且听我细细道来。
首先,我们新创建的对象都存放在伊甸区,伊甸区是很大的,占了新生代的80%。因为新对象的创建是十分频繁的,需要预留更多的空间,当然存活时间也是极短的。
当伊甸区内存用尽时,就会触发新生代的垃圾回收,此时会清理伊甸区所有失效的对象。同时将尚未失效的对象移动到幸存者0中。顾名思义,幸存者待的地方。显然,幸存者们未被清理说明他们在更长时间内将会被使用。因此,最初的垃圾回收不会影响到幸存者0。但在多次清理伊甸区后,幸存者0内存终会耗尽。此时会在幸存者0中进行清理。清理后依然存活的对象并不会直接移动到老年代,因为这个区域的对象虽然没有马上失效,但依然有很多存活时间不长的对象,不值得放入老年代。
因此,当幸存者0中垃圾回收后的剩余对象,会被重新整理,移动到幸存者1,此后伊甸区幸存的对象也会被移动到幸存者1中。这个过程除了释放内存外,还会重新整理对象的位置,把碎片空间整合起来再次利用。
之后当幸存者1内存耗尽后,又会把剩余对象移动到幸存者0中。如此反复。
只有当一个幸存者区无法接收伊甸区和另一个幸存者区的对象时,或者反复15次之后依然存活的对象,才会被移动到老年代中。接下来新生代的垃圾回收都不会影响到老年代的对象。
可以看出新生代会经常触发垃圾回收机制,但开销不大,只需要清理整个堆内存的不到三分之一。
只有当老年代内存耗尽时,才会触发老年代的垃圾回收。此时会清理老年代的堆内存,以便接收从新生代过来的新对象。
对象移动到老年代的情况还有另外一种情况:当创建对象太大,如很长的字符串,或者很长的数组,会占用新生代很大的空间,此时会直接移动到老年代,哪怕他很快就会失去引用。
俗称大对象:指需要大量连续内存空间的java对象。
因此我们应尽量避免大对象,这有可能导致Full GC的发生。
三、垃圾回收算法
标记清理法
从根集合触发,对可以引用到的对象进行标记,一直到叶子节点为止。所有无法被引用的对象视为失效对象。进行清理。
复制算法
从根集合触发,对可以引用到的对象进行标记,一直到叶子节点为止。将有效对象复制后移动到一块新的内存中。放弃旧内存区域。
这种方法可以清理碎片化空间再次利用。
这两种算法可以相互比较,
标记清理法针对无效对象进行清理,因此无效对象少时,效率更高。
复制算法针对有效对象进行复制,直接放弃旧内存空间,因此有效对象少时,效率更高。
因此复制算法应用在新生代,标记清理法应用在老年代。
标记整理法
在标记清理法的基础上整理有效对象的位置,使得可以清理碎片化空间再次利用。使标记清理法的优化版。
JVM中垃圾回收方法分为两种:Minor GC和Full GC。
Minor GC
只清理新生代的垃圾。
发生条件:伊甸区无法为新对象分配内存时。
Full GC
清理整个堆的垃圾。
发生条件:老年代无法为新生代传递过来的对象分配内存时。或者方法区无法分配内存时。以及显示调用System.gc()
时
Full GC执行时会停止响应程序的所有请求,因此因尽量避免Full GC以及减小Full GC的执行时间。
四、常用JVM配置参数
-Xms 最小堆内存
-Xmx 最大堆内存
-Xmn 年轻代大小
-XX:PretenureSizeThreshold 对象如果大于或等于此值,会直接分配到老年代
例:-Xms128m -Xmx128m -Xmn54m
其他,自行百度。