运行时数据区详解<2>堆
一.堆核心概述
如图所示,开启了两个进程
代码一样,在JDK自带的jvisualvm.exe工具中可以查看到两个进程分别拥有一个堆空间,
内存细分
目前堆空间主要是由新生代和老年代组成的,元空间相当于编外人员, 元空间的具体实现是由方法区来实现的.
是
可使用此jvm参数打印GC参数 -XX:+printGCDetails
二.设置堆的大小与OOM
堆空间大小设置
开发中建议将默认堆大小和最大堆大小设置成一样的, 比如手动设置 -Xms600m -Xmx600m
原因是: 默认值如果设置小了.后面堆回去申请内存,直到到达最大值.因为系统的GC其实也挺消耗资源的,没必要再让堆的扩容和回收来浪费系统资源,主要是减轻系统运行的资源压力.
通过在jvm参数中添加-XX:+printGCDetails 打印堆内存信息可查看和cmd命令行查看是一样的
OutOfMemorry(堆空间溢出)举例
public class OOMTest {
public static void main(String[] args) throws InterruptedException {
ArrayList<Picture> list = new ArrayList<Picture>();
while (true) {
Thread.sleep(20);
list.add(new Picture(new Random().nextInt(1024 * 1024)));
}
}
}
class Picture {
private byte[] picels;
public Picture(int length) {
this.picels = new byte[length];
}
}
一刻不停的在造对象
可以在java VisualVM(这个软件在jdk的bin目录中)中查当前java进程的内存占用情况
三.年轻代与老年代
对象刚刚创建的时候是在伊甸园区的,当进行GC的时候,如果发现对象还存活的话,就会将对象放到幸存者区(from或者to区<也叫1区或者0区,因为from或者to是不断交换的>),经过一定的时间之后,如果对象还是存活,就会放入到老年区.
如果同时使用了-XX:SurvicirRatio和-Xmn两个参数,其中以-Xmn为准,因为已经显式指明了大小,一般不会直接使用这个参数设置大小
设置比例已经生效,需要显式加参数
通过jinfo -flag NewRatio 30512 查看指定的进程的老年代和新生代比例
四.图解对象分配过程
概述:
总结:
特殊情况
常用调优工具
JProfiler v11.1.5 64位 免费特别版(附注册码+安装教程) : https://www.jb51.net/softs/608640.html#downintro2
https://blog.csdn.net/qq_43012792/article/details/107437471
五.Minor GC,Major GC , Full GC
最简单的分代式GC策略的触发条件
年轻代GC(Minor GC) 触发机制
老年代GC(Major GC / Full GC) 触发机制
Full GC 触发机制(简单描述)
示例:
JVM参数: -Xms9m -Xmx9m -XX:+PrintGCDetails 设置堆空间的大小和打印GC的日志
public class GCTest {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<String>();
String a = "huqi";
while (true) {
list.add(a);
a = a + a;
i++;
}
}catch (Throwable e) {
e.printStackTrace();
System.out.println("遍历次数是: " + i);
}
}
}
六.堆空间的分代思想
七.内存分配策略(对象提升(promotion)规则)
示例:
-Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
八.为对象分配内存:TLAB
对象分配过程: TLAB
TLAB是在新生代中每个线程独有分配的空间
为什么会有TLAB(Thread Local Allocation Buffer)
什么是TLAB
TLAB的再说明
TLAB 默认是开启的
首先将字节码文件进行加载,遇到了new对象的,首先看这个对象能不能在TLAB分配的空间中存放,可以的话就会直接进行实例化,如果无法放入,就会尝试放入伊甸园的公共内存空间,如果还是不能进行存放,则说明这个对象是个大对象,则将对象放入老年区中进行初始化.
九.小结堆空间的参数设置
* 测试堆空间中常用的JVM参数
* -XX:+PrintFlagsInitial -> 查看所有的参数的默认初始值
* -XX:+PrintFlagsFinal -> 查看所有的参数的最终值(可能会存在修改,不再是初始值)
* 具体查看某个参数的指令: jps: 查看当前运行中的进程
* jinfo -flag SurvivorRatio 进程id
* -Xms -> 初始堆空间内存(默认为物理内存的1/64)
* -Xmx -> 最大堆空间内存(默认为物理内存的1/4)
* -Xmn -> 设置新生代的大小(初始值及最大值)
* -XX:NewRatio -> 配置新生代与老年代在堆结构的占比
* -XX:SurvivorRatio -> 设置新生代中Eden和S0/S1 空间的比例
* -XX:MaxTenuringThreshold -> 设置新生代垃圾的最大年龄
* -XX:+PrintGCDetails -> 输出详细的GC处理日志(包含GC记录和内存分配信息)
* 打印GC简要信息:: 1> -XX:+PrintGC 2> -verbose:gc
* -XX:HandlePromotionFailure -> 是否设置空间分配担保
在设置新生代中伊甸园区和幸存者区的比例的时候,如果伊甸园区的比例设置的过大,幸存者区很容易就满了,这个时候容易导致yGC失去意义,如果将伊甸园区设置的过小,会频繁的进行YGC,影响性能.
-XX:HandlePromotionFailure -> 是否设置空间分配担保
十.堆是分配对象的唯一选择吗
逃逸分析概述:
StringBuffer给返回来,对象逃逸了,使用toString()之后,返回的是一个新的String对象, sb对象留在了方法内部,没有进行逃逸,所以可以进行栈上分配
如何快速判断是否发生了逃逸 -> 就看new 的对象实体是否可能载类外部进行调用
如果obj是static的依然会发生逃逸
参数设置:
逃逸分析:代码优化
栈上分配
可以使用JVM参数 : -Xmx1G -Xms1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
-XX:+DoEscapeAnalysis + 和 - 来控制是否开启逃逸分析,在7之后是默认开启的,可以使用-来进行关闭通过开启和关闭逃逸分析可以看出,开启之前需要77ms才能执行完,开启之后只需4ms就可以执行完,打开vm工具查看堆内存的占用情况,发现内存中的对象由之前的10000000个减少到3000+个,大大减少了,而且伊甸园区的占用也大减少
public class StackAllocation {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
alloc();
}
// 查看执行时间
long end = System.currentTimeMillis();
System.out.println("花费的时间是 : " + (end - start) + "ms");
Thread.sleep(1000000);
}
private static void alloc() {
User user = new User();
}
static class User{
}
}
同步省略(消除)
标量替换
例子
-Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
设置堆空间大小 开启逃逸分析(默认即开启) 打印GC日志 关闭标量替换
public class ScalarReplace {
public static class User {
public int id;
public String name;
}
public static void alloc() {
User user = new User();
user.id = 5;
user.name = "www.huqi.com";
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
alloc();
}
long end = System.currentTimeMillis();
System.out.println("时间 : " + (end - start) + "ms");
}
}
关闭标量替换的结果:
打开标量替换的结果, 没有发生GC
逃逸分析并不是特别的成熟,所以,其实Hotspot没有使用逃逸分析…
所以,对象实例都是分配在堆上是没有问题的