JVM
局部变量表的槽位复用
public void Gc4() {
{
int[] a = {1, 2, 3};
System.out.println(a);
}
int b = 100;
System.gc();
}
从中可以发现,b复用了a的槽位,也就是index。那么为什么b可以复用a的槽位呢?仅仅只因为它是个b吗……
当然不是这样!
那是因为a是局部变量,当局部变量执行完毕后,局部变量的内存空间就会被释放,所以b也就能使用a的index了
栈上分配
在给对象分配内存空间时,首先会进行栈上分配,如果栈上分配不行,才会进行TLAB分配,TLAB分配不行的话,就会进行堆上分配,堆上分配分两种,如果是小对象的话会在年轻代分配,如果是大对象的话会在老年代分配
针对小而多的对象,会优先采用栈上分配。栈是线程私有的,所以线程私有的对象才能进行栈上分配——逃逸分析
如果一个对象满足栈上分配的话,需要先打散然后放到栈中——标量替换
逃逸分析
分析对象是不是逃出了线程私有
标量替换
Java中的一些基本类型
聚合量:可以被进一步拆解
TLAB分配
- Thread Local Allocation Buffer:线程本地分配缓冲区
- TLAB空间很小,放在Eden区,占1%的空间
主要的垃圾回收算法
引用计数法
对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用
但引用计数器有两个严重问题
- 无法处理循环引用的情况
- 引用计数器要求再每次引用产生和消除的时候,需要伴随一个加减法操作,对系统性能会有一定的影响
因此JVM并未选择此算法作为垃圾回收算法
标记清除法
- 标记清除算法是现代垃圾回收算法的思想基础
- 分为两个阶段:标记阶段和清除阶段
- 标记清除算法产生最大的问题就是清除之后的空间碎片
从根节点出发,就能知道哪些对象是活跃的,哪些对象是死亡的,引用的对象就标记;把没有标记的对象清除掉
复制算法
将原有的内存空间分为2块,每次只使用其中一块内存,例如:A内存,GC时将存活的对象复制到B内存中。然后清除掉A内存所有对象。开始使用B内存
复制算法没有内存碎片,并且如果垃圾对象很多,那么这种算法效率很高。但是它的缺点是系统内存只能使用1/2
复制算法在JVM中的使用
因为新生代90%的对象都是临时对象,所以在新生代串行GC中,使用了复制算法
老年代中不使用复制算法,因为老年代可能只有10%的对象需要回收,如果使用复制算法,需要复制的对象很多,会导致垃圾回收的效率非常低
标记压缩法
标记压缩算法是一种老年代的回收算法
它首先标记存活的对象,然后将所有存活的对象压缩到内存的一端,然后再清除所有存活对象之外的空间
标记压缩算法的效率不如复制算法,但是标记压缩算法用于老年代,而复制算法用于新生代
分代算法
堆空间中分为新生代和老年代,为了提高垃圾回收的效率,不同的代中使用不同的垃圾回收算法
分区算法
将堆空间分为不同的小区间,对每个小区间进行回收,而不是每次都回收整个堆空间,这样能提高回收的的效率,当堆空间大时,执行GC会很费时
JVM垃圾回收器
串行回收器
串行回收器只使用单线程进行GC,而且是独占式GC
串行收集器是JVM Client模式下默认的垃圾回收器,所有应用都要等待GC执行完毕,才可以继续执行
JVM参数 | 作用 |
---|---|
-XX:SurvivorRatio | 设置Eden区与survivor区比例 |
-XX:PretenureSizeThreshold | 设置大对象直接进入老年代的阈值 |
-XX:MaxTenuringThreshold | 设置对象进入老年代的年龄阈值 |
启用指定的回收器
JVM参数 | 新生代 | 老年代 |
---|---|---|
-XX:+UseSerialGC | 串行回收器 | |
串行回收器 | ||
-XX:+UseParNewGC | ParNew | 串行回收器 |
-XX:+UseParallelGC | ParalllelGC | 串行回收器 |
并行回收器
将串行回收器多线程化,与串行回收器有相同的回收策略、算法、参数
JVM参数 | 新生代 | 老年代 |
---|---|---|
-XX:+UseParNewGC | ParNew | 串行回收器 |
-XX:+UseConcMarkSweepGC | ParNew | CMS |
-XX:+UseParallelGC | ParallelGC | 串行回收器 |
-XX:+UseParallelOldGC | ParallelGC | ParallelGC |
并行回收器JVM参数
收集器 | JVM参数 | 作用 |
---|---|---|
ParNew | -XX:ParallelGCThreads | 指定GC时工作的线程数 |
ParallelGC | -XX:MaxGCPauseMillis | 最大的垃圾收集暂停时间 |
-XX:GCTimeRatio | 设置垃圾收集吞吐量 | |
-XX:+UseAdaptiveSisePolicy | 打开自适应垃圾收集策略 | |
ParallelOldGC | -XX:+ParallelGCThreads | 指定GC时工作的线程数量 |
CMS | -XX:-CMSPrecleaningEnabled | 禁用预清理操作 |
-XX:ConcGCThreads | 设置并发线程数量 | |
-XX:PartallelCMSThreads | 设置并发线程数量 | |
-XX:CMSInitiatingOccupancyFraction | 当老年代空间使用量达到某百分比时,会执行CMS。默认为68 | |
-XX:+CMSCompactAtFullConllection | GC后,进行一次碎片整理 | |
-XX:CMSFullGCsBeforeCompaction | 指定执行多少次GC后,进行一次碎片整理 |
G1点GC收集过程
G1全称Garbage First Garbage Collector,优先回收垃圾比例最高的区域
G1收集器将堆划分为多个区域,每次收集部分区域来减少GC产生的停顿时间
新生代GC
当Eden区被占满后,新生代GC就会启动;回收后,Eden区会被清空,survivor会保留一部分数据;部分新生代对象会晋升到老年代
混合回收
第一阶段新生代GC——>第二阶段并发标记周期——>第三阶段混合回收
由于触发年轻代GC,Eden区域会被清空;被标记的年轻代和老年代都会被回收;垃圾比例高的会被清理,剩余存活对象会被移动其他区域,减少内存碎片
JVM常用参数
参数 | 作用 |
---|---|
-Xms128m | 设置初始化堆内存为128M |
-Xmx512m | 设置最大堆内存为512M |
-Xmn160m | 设置新生代大小为-Xmn160M(堆空间1/4~1/3) |
-Xss128m | 设置最大栈内存为128M |
-XX:SurvivorRatio | 设置新生代eden区与from/to空间的比例关系 |
-XX:PermSize=64M | 设置初始永久区64M |
-XX:MaxPermSize=128M | 设置最大永久区128M |
-XX:MaxMetaspaceSize | 设置元数据区大小(JDK1.8 取代永久区) |
-XX:+DoEscapeAnalysis | 启用逃逸分析(Server模式) |
-XX:+EliminateAllocations | 开启标量替换(默认开启) |
-XX:+TraceClassLoading | 跟踪类的加载 |
-XX:+TraceClassUnloading | 跟踪类的卸载 |
-Xloggc:gc.log | 将gc日志信息打印到gc.log文件中 |
-XX:+PrintGC | 打印GC日志 |
-XX:+PrintGCDetails | 打印GC详细日志 |
-XX:+PrintGCTimeStamps | 输出GC发生的时间 |
-XX:+PrintGCApplicationStoppedTime | GC产生停顿的时间 |
-XX:+PrintGCApplicationConcurrentTime | 应用执行的时间 |
-XX:+PrintHeapAtGC | 在GC发生前后,打印堆栈日志 |
-XX:+PrintReferenceGC | 打印对象引用信息 |
-XX:+PrintVMOptions | 打印虚拟机参数 |
-XX:+PrintCommandLineFlags | 打印虚拟机显式和隐式参数 |
-XX:+PrintFlagsFinal | 打印所有系统参数 |
-XX:+PrintTLAB | 打印TLAB相关分配信息 |
-XX:+UseTLAB | 打开TLAB |
-XX:TLABSize | 设置TLAB大小 |
-XX:+ResizeTLAB | 自动调整TLAB大小 |
-XX:+DisableExplicitGC | 禁用显示GC (System.gc()) |
-XX:+ExplicitGCInvokesConcurrent | 使用并发方式处理显式GC |