JVM内存与垃圾回收-3-运行时数据区/堆区

JVM内存与垃圾回收-3-运行时数据区/堆区JVM堆区详解_jinfonewratio

概述

  • java进程:JVM进程=1:1;JVM进程:runtime实例=1:1;runtime实例:堆区=1:1
  • JVM启动时创建并确定大小
  • 堆区必须逻辑上连续
  • 几乎所有的引用类型(数组,对象)都在堆区分配内存
  • 堆区是GC的重点区域,GC时才会回收堆区中的对象

堆空间

在这里插入图片描述
分析

  • 80%+的对象都在eden区死亡(朝生夕死)

GC

总述

在这里插入图片描述
分析

  • 频繁在新生代收集,很少在老年代收集,几乎不在永久代/元空间收集
分类 GC 触发 介绍
部分收集 YGC(Young/Minor GC) eden空间不足 收集新生代
部分收集 MGC(Major/Old GC) 老年代空间不足 收集老年代,限CMS GC
部分收集 Mixed GC 收集整个新生代部分老年代,限G1 GC
全部收集 Full GC 老年代或永久代/元空间不足 新生代+老年代+永久代/元空间

YGC(Young/Minor GC)

分析

  • eden空间不足
  • YGC回收新生代(eden+survivor)中的垃圾
  • 回收速度非常快
  • STW(stop the world)停止用户线程,专注垃圾收集
  • STW是否影响系统性能
  • Survivor区满不会触发Minor GC
  • -XX:MaxTenuringThreshold=15,默认15

MGC(Magor/Old GC)

分析

  • MGC回收老年代中的垃圾
  • 只有CMS GC会单独回收老年代

FGC(Full GC)

分析

  • 老年代或永久代/元空间不足
  • FGC回收新生代+老年代+永久代/元空间中的垃圾
  • STW是否影响系统性能,FGC的STW时间是YGC的STW时间的10倍以上
  • 尽量避免FGC
  • System.gc()系统建议执行FGC,不一定会执行

分代GC算法

在这里插入图片描述
特殊晋升:

  1. eden区放不下
  • 新建的超大对象,eden区放不下,经过YGC之后,eden区还是放不下,放到老年代中
  1. survivor区放不下
  • eden区中存活的1个大对象大小>to区可用空间,放到老年代中
  • eden区中大量存活对象总大小>to区可用空间,放到老年代中

正常晋升:

  • 对象存活超过阈值(年龄计数器),-XX:MaxTenuringThreshold=,默认等于15,从eden到to区年龄计数器=1
  • from区相同年龄的所有对象占用空间大小>from区的一半

晋升失败处理-XX:+HandlePromotionFailure

  • 是否处理可能的晋升失败,从新生代到老年代的晋升失败
  • 老年代连续空间size>新生代对象总size,不可能晋升失败
# jdk7=+
if (老年代连续空间size>新生代对象总size)||(老年代连续空间size>历史晋升平均size){
    Minor GC
}else {
    Full GC
}

# jdk6=-
if (老年代连续空间size>新生代对象总size)
    Minor GC
}else if (HandlePromotionFailure=true) {
    if (老年代最大可用连续空间>历史晋升到老年代对象的平均大小) {
        Minor GC
    }else {
        Full GC
    }
}else if (HandlePromotionFailure=false) { 
    Full GC
}

堆区大小

堆区

  • 堆区大小=新生代+老年代=(伊甸区+幸存区)+老年代=(eden+s0+s1)+老年代
  • 堆区能存储对象的大小=(eden+s0/s1)+老年代。因为,s0和s1使用的复制算法
  • 堆区大小不包括永久代或元空间。分代收集算法中才设计永久代或元空间
    参数:
  • -Xms(堆区初始内存)
  • -Xmx(堆区最大内存)

默认:

  • -Xms=物理内存大小/64
  • -Xmx=物理内存大小/4

过程:

  • 扩容:堆区内存先分配-Xms(堆区初始内存)》不够》扩容直到-Xmx(堆区最大内存)
  • 缩容:扩容后的堆区内存》存在空闲》缩容直到-Xms(堆区初始内存)

OOM(OutOfMemoryError):

  • -Xmx指定内存

设置:

  • -Xms=-Xmx
  • 目的:频繁的扩容和缩容会影响性能

TLAB(Thread Local Allocation Buffer)

总述

  • 多线程情况下直接中堆区中划分内存空间需要加锁,否则线程不安全,加锁影响分配速度
  • JVM为每个线程分配1个私有的缓存区域TLAB
  • TLAB位于Eden空间中
  • 线程用完了自己私有的TLAB再通过加锁使用Eden中公共的区域

逃逸分析

总述

  • HotSpot并没有实现栈上分配,实现了标量替换
  • jvm以server模式运行,才有逃逸分析,64位JVM默认是server模式
  • 逃逸分析是JIT(即时编译器)中优化技术之一
  • 动态分析对象的作用域,分析对象引用的使用范围
  • 方法逃逸:当一个对象在方法中定义之后,作为参数传递到其它方法中
  • 线程逃逸:如类变量或实例变量,可能被其它线程访问到
  • 逃逸分析是“栈上分配”,“同步省略”,“标量替换”的基础
  • 标量替换为栈上分配提供了很好的基础

标量替换

总述

  • 将对象替换为对象的成员属性
  • 标量:无法再分的最小数据量,基本类型成员属性就是标量
  • 聚合量:对象就是聚合量

栈上分配

总述

  • 经过“逃逸分析”的没有逃逸的对象,可以直接上栈上分配
  • 没有GC,因为是栈上分配的对象

同步省略

总述

  • 如果1个对象只能被1个线程访问,该对象的同步操作会被省略

反编译

设置堆大小

编译

javac -d D:\workspace\idea\JVMDemo\blog\target\classes\ VMHeapSize.java

运行

java -Xmx10m -Xms10m -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapSize

结果
在这里插入图片描述

  • java virtual vm在jdk/bin目录下

结果virtual GC
在这里插入图片描述

  • 红框加起来等于10M

对象和数组位置

代码

package xcrj;

public class VMHeapLocation { 
   
    public static void main(String[] args) { 
   
        VMHeapLocation vmHeap1 = new VMHeapLocation();
        int[] intArr = new int[10];
    }
}

jclasslib
在这里插入图片描述

  • 创建对象实例:new
  • 创建数组实例:newarray

堆内存大小代码分析

代码

package xcrj;

/* * 先设置-Xms500m -Xmx500m * */
public class VMHeapSpaceSize { 
   
    public static void main(String[] args) { 
   
        //返回java虚拟机中的堆内存总量,开始时totalMemory=-Xms
        long mUnit = 1024 * 1024;
        long initialMemory = Runtime.getRuntime().totalMemory() / mUnit;
        System.out.println("-Xms=" + initialMemory + "M");
        //返回java虚拟机试图使用的最大堆内存,maxMemory=-Xmx
        long maxMemory = Runtime.getRuntime().maxMemory() / mUnit;
        System.out.println("-Xmx=" + maxMemory + "M");

        // 64.0和4.0目的是转为浮点数
        System.out.println("系统内存大小为: " + initialMemory * 64.0 / 1024 + "G");
        System.out.println("系统内存大小为: " + maxMemory * 4.0 / 1024 + "G");

        try { 
   
            Thread.sleep(2000000);
        } catch (InterruptedException ie) { 
   
            ie.printStackTrace();
        }
    }
}

结果

# 编译
javac -d D:\workspace\idea\JVMDemo\blog\target\classes\ -encoding UTF-8 VMHeapSpaceSize.java
# 运行
java -Xmx500m -Xms500m -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapSpaceSize

在这里插入图片描述
分析

  • 设置的500m并没有,代码打印的结果为479M<500M
  • 堆区能存储对象的大小=(eden+s0/s1)+老年代。因为,s0和s1使用的复制算法
  • 系统内存大小也并不符合作者实际情况

堆内存大小命令分析

java命令执行同上
命令行查看

# 查看在运行的java进程
jps
# 查看java进程内存情况
jstat -gc java进程id号

在这里插入图片描述
分析

  • E:eden;S:survivor;U:used;C:count;O:old
  • 堆空间大小=新生代+老年代=(EC+S0C+S1C)+OC=500M
  • 堆空间能够存储对象大小=(EC+S0C)+OC=(EC+S1C)+OC=479M

OOM

java

package xcrj;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/* * 先设置 -Xms=10m -Xmx=10m * */
public class VMHeapOOM { 
   
    public static void main(String[] args) { 
   
        List<Image> list = new ArrayList<>();
        while (true) { 
   
            try { 
   
                Thread.sleep(10);
            } catch (Exception e) { 
   

            }
            int value = new Random().nextInt(1024 * 1024);
            Image img = new Image(value);
            list.add(img);
        }
    }
}

class Image { 
   
    private byte[] pixels;

    public Image(int len) { 
   
        this.pixels = new byte[len];
    }
}

OOM结果
在这里插入图片描述

java virsualVM/抽样器
在这里插入图片描述

  • 发现byte[]对象占用了绝大部分内存

java virsualVM/Visual GC
在这里插入图片描述

-XX:NewRatio

代码

package xcrj;

/* * 先设置-Xms500m -Xmx500m * */
public class VMHeapSpaceSize { 
   
    public static void main(String[] args) { 
   
        //返回java虚拟机中的堆内存总量,开始时totalMemory=-Xms
        long mUnit = 1024 * 1024;
        long initialMemory = Runtime.getRuntime().totalMemory() / mUnit;
        System.out.println("-Xms=" + initialMemory + "M");
        //返回java虚拟机试图使用的最大堆内存,maxMemory=-Xmx
        long maxMemory = Runtime.getRuntime().maxMemory() / mUnit;
        System.out.println("-Xmx=" + maxMemory + "M");

        // 64.0和4.0目的是转为浮点数
        System.out.println("系统内存大小为: " + initialMemory * 64.0 / 1024 + "G");
        System.out.println("系统内存大小为: " + maxMemory * 4.0 / 1024 + "G");

        try { 
   
            Thread.sleep(2000000);
        } catch (InterruptedException ie) { 
   
            ie.printStackTrace();
        }
    }
}

jstat结果
在这里插入图片描述
分析

  • 老年代:新生代=3
  • OC:(EC+S0C+S1C)=3

jinfo结果

# 查看JVM参数值
jinfo -flag NewRatio java进程id号

在这里插入图片描述

-XX:SurvivorRatio

分析

  • 默认,具有自适应机制

强制设置为7:1:1

java -Xmx500m -Xms500m -XX:SurvivorRatio=7 -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapSpace

结果
在这里插入图片描述

-Xmn100m -XX:NewRatio=3

总述

  • -Xmn100m -XX:NewRatio=3一起设置,-XX:NewRatio=3失效
    指令
java -Xmx600m -Xms600m -Xmn100m -XX:NewRatio=3 -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapSpaceSize

结果
在这里插入图片描述

  • 发现老年代:新生代=OC:(EC+S0C+S1C)!=3:1
  • -XX:NewRatio=3失效

-XX:+PrintGCDetails

代码

package xcrj;

import java.util.ArrayList;
import java.util.List;

public class VMHeapGCDetail { 
   
    public static void main(String[] args) { 
   
        try { 
   
            List<String> list = new ArrayList<>();
            String str = "maybe you are a dream";
            do { 
   
                list.add(str);
                str += str;
            } while (true);
        // OutOfMemoryError继承Error继承Throwable
        } catch (Throwable t) { 
   
            t.printStackTrace();
        }
    }
}

命令

# 编译
javac -d D:\workspace\idea\JVMDemo\blog\target\classes\ -encoding UTF-8 VMHeapGCDetail.java
# 运行
java -Xmx6m -Xms6m -XX:+PrintGCDetails -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapGCDetail

结果
在这里插入图片描述

[GC (Allocation Failure) [PSYoungGen: 1008K->498K(1536K)] 1008K->722K(5632K), 0.0526596 secs] [Times: user=0.00 sys=0.00, real=0.05 secs]
[GC (Allocation Failure) [PSYoungGen: 1454K->496K(1536K)] 1678K->1173K(5632K), 0.0017728 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1140K->504K(1536K)] 1817K->1597K(5632K), 0.0012724 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1356K->0K(1536K)] [ParOldGen: 3589K->3162K(4096K)] 4945K->3162K(5632K), [Metaspace: 2589K->2589K(1056768K)], 0.0287266 secs] [Times: user=0.01 sys=0.00, real=0.03 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 3162K->3162K(5632K), 0.0021192 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 3162K->3118K(4096K)] 3162K->3118K(5632K), [Metaspace: 2589K->2589K(1056768K)], 0.0096239 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Java heap space

分析

  • 抛出OutOfMemoryError之前有一次Full GC;可知执行一次FGC收集了PSYoungGen,ParOldGen和Metaspace中的垃圾
  • [GC (Allocation Failure) [PSYoungGen: 1008K->498K(1536K)] 1008K->722K(5632K), 0.0526596 secs] [Times: user=0.00 sys=0.00, real=0.05 secs]:1008K,YGC调用之前新生代大小;498K,YGC调用之后剩余新生代大小;1536K,新生代总大小;722K,YGC调用之后剩余堆内存大小;5632K,堆内存总大小,大约6M;0.0526596 secs,YGC耗时时间,单位s;Times: user=0.00 sys=0.00, real=0.05 secs分别为YGC用户耗时时间、系统耗时时间、实际耗时时间, 单位为秒

eden超大对象

代码

package xcrj;

/* * -Xms30m -Xmx30m -XX:NewRatio=2 -XX:+PrintGCDetails * 老年代=20M * 新生代=10M * */
public class VMHeapEdenBIgObj { 
   
    public static void main(String[] args) { 
   
        //10M大小对象
        byte[] m10Bytes = new byte[1024 * 1024 * 10];
    }
}

结果
在这里插入图片描述
分析

  • 新生代=eden+from+to=10240K=10M
  • 老年代=20480K=20M
  • 创建的大对象m10Bytes=10M>eden的8192K,放到老年代

-XX:+PrintFlagsInitial

结果-默认初始值

java -Xms30m -Xmx30m -XX:+PrintFlagsInitial -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapEdenBIgObj

在这里插入图片描述
结果-赋值初始值

java -Xms30m -Xmx30m -XX:NewRatio=3 -XX:+PrintFlagsInitial -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapEdenBIgObj

在这里插入图片描述

-XX:+PrintFlagsFinal

指令

java -Xms30m -Xmx30m -XX:NewRatio=3 -XX:+PrintGCDetails -XX:+PrintFlagsFinal -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapEdenBIgObj

结果
在这里插入图片描述

HotSpot标量替换

代码

package xcrj;

/* * 标量替换 * 先:关闭标量替换 * -Xms100m -Xmx100m -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+PrintGC * 再:打开标量替换,默认打开 * -Xms100m -Xmx100m -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+PrintGC * */
public class VMHeapScalarReplace { 
   
    /* * user没有发生逃逸,可以进行标量替换 * */
    public static void createUser() { 
   
        User user = new User("xcrj", 20);
    }

    public static void main(String[] args) { 
   
        long startTime = System.currentTimeMillis();
        int w200 = 20000000;
        for (int i = 0; i < w200; i++) { 
   
            createUser();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("花费时间:" + (endTime - startTime) + "ms");

        try { 
   
            Thread.sleep(1000000);
        } catch (InterruptedException ie) { 
   
            ie.printStackTrace();
        }
    }
}

class User { 
   
    private String name;
    private int age;

    public User() { 
   
    }

    public User(String name, int age) { 
   
        this.name = name;
        this.age = age;
    }
}
# jvm以server模式运行,才有逃逸分析,64位JVM默认是server模式
java -version
# 逃逸分析是标量替换的基础
# 关闭标量替换
java -Xms2G -Xmx2G -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+PrintGCDetails -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapScalarReplace
# 开启标量替换
java -Xms2G -Xmx2G -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+PrintGCDetails -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapScalarReplace

结果--server模式
在这里插入图片描述

  • 可以看到是server模式
    结果--XX:-EliminateAllocations,关闭标量替换
    在这里插入图片描述
  • 创建的200w个对象都在堆中
    结果--XX:+EliminateAllocations,开启标量替换,默认开启
    在这里插入图片描述
  • 开启标量替换之后,发现堆中的对象少了很多,只有71901个了

结果--XX:PrintGCDetails -XX:-EliminateAllocations,关闭标量替换
在这里插入图片描述

  • YGC调用之前,新生代使用了503809K
    结果--XX:PrintGCDetails -XX:+EliminateAllocations,开启标量替换,默认开启
    在这里插入图片描述
  • YGC调用之前,新生代使用了41984K 远远小于503809K,标量替换进行了优化

调优

参数

分类 参数 默认 作用 建议
堆空间大小 -Xms10m 物理内存大小/64 min堆大小
堆空间大小 -Xmx10m 物理内存大小/4 max堆大小
新生代大小 -Xmn10m 新生代内存大小,和-XX:NewRatio=2一起设置,这个参数失效
老年代:新生代 -XX:NewRatio=2 2 老年代:新生代=2
eden:survivor -XX:SurvivorRatio=8 自适应 eden:s0:s1=8:1:1
新生代自适应 -XX:+UseAdaptiveSizePolicy 开启 +号开启自适应,-号关闭自适应 是。大流量、低延迟系统 建议关闭
年龄计数器 -XX:MaxTenuringThreshold=15 15 年龄计数器,控制survivor区到老年代
-XX:+HandlePromotionFailure 开启 开启非安全转移
TLAB启用 -XX:+UseTLAB 开启 +号开启TLAB
TLAB/Eden -XX:TLABWasteTargetPercent Eden的1% TLAB占用Eden百分比
参数值 -XX:+PrintFlagsInitial 所有参数的默认初始值
参数值 -XX:+PrintFlagsFinal 所有参数的最终值,赋值值
GC -XX:+PrintGCDetails 打印GC细节,常用
GC -XX:+PrintGC 打印GC简略信息
GC -verbose:gc 打印GC简略信息
逃逸分析 -XX:+DoEscapeAnalysis jdk6u23=+默认开启 逃逸分析
逃逸分析 -XX:+PrintEscapeAnalysis 打印逃逸分析筛选结果
标量替换 -XX:+EliminateAllocations 开启 标量替换
JVM运行模式 -server 64位jvm默认server模式 server模式,server模式下才有逃逸分析

指令

指令 作用
javac -d D:\workspace\idea\JVMDemo\blog\target\classes\ -encoding UTF-8 VMHeapScalarReplace.java 编译
java -Xms100m -Xmx100m -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapScalarReplace 运行
javap -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ -v xcrj.VMHeapScalarReplace 反编译
jps 查看在运行的java进程
jstat -gc java进程id号 查看java进程GC内存情况
jinfo -flag NewRatio java进程id号 查看JVM参数值
jmap -histo [pid] 查看堆中对象个数,占用内存byte,柱状图(histogram)

javap -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ -v xcrj.VMHeapScalarReplace

-Xms=-Xmx

设置:

  • -Xms=-Xmx
  • 目的:频繁的扩容和缩容会影响性能

今天的文章JVM内存与垃圾回收-3-运行时数据区/堆区分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/65831.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注