这个系列会很长,先讲理论,后实战,设计到的内容也是JVM那本书里比较基础的,大神请忽略这篇文章,图也大多是盗的
JVM内存模型
JVM规范之JVM内存
下图是这几个区域会涉及到的配置参数和异常,需要注意的是 虚拟机栈 其实就是人们平常所说的栈区,方法区是JDK1.8之前的叫法,JDK1.8之后称为元空间 针对的设置参数也不同 1.7是:X:PermSize:16MXX:MaxPermSize64M 这两个 1.8是:X:MetaspaceSize=16M XX:MaxMetaspaceSize=64M 这两个
版本内存结构差异
1.7内存结构介绍
五个部分 方法区 堆 栈 本地方法栈 程序计数器
1.8内存结构介绍
对比1.7 没有了方法区 在1.8里叫元空间 位置也变了 变到了本地内存 对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢? 当然不是,方法区只是一个规范,只不过它的实现变了。 在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是 存在于本地内存(Native memory)。 方法区Java8之后的变化 移除了永久代(PermGen),替换为元空间(Metaspace) 永久代中的class metadata(类元信息)转移到了native memory(本地内存,而不是虚拟机) 永久代中的interned Strings(字符串常量池) 和 class static variables(类静态变量)转移到了Java heap 永久代参数(PermSize MaxPermSize)-> 元空间参数(MetaspaceSize MaxMetaspaceSize)
Java8为什么要将永久代替换成Metaspace? 字符串存在永久代中,容易出现性能问题和内存溢出。 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太 大则容易导致老年代溢出。 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。 Oracle 可能会将HotSpot 与 JRockit 合二为一,JRockit没有所谓的永久代。
1.8内存详解
上图是粗略的比较了1.7和1.8的区别,这个图是详细的1.8的内存结构,虚拟机栈我们都知道 会有栈帧 做过算法的朋友都知道如果一个回溯算法做不好的话 就会出现栈溢出,而为什么可以回溯也是因为栈帧有顺序的压入和弹出
参数配置
整体堆内存空间的分配 上代码 下面这段代码 运行的时候没有设置任何的JVM参数 我们看下默认情况内存能给到多少 还有就是totalMemory的弹性伸缩问题
public static void main(String[] args) {
System.out.println(Runtime.getRuntime().maxMemory()/1024/1024);
System.out.println(Runtime.getRuntime().freeMemory()/1024/1024);
System.out.println(Runtime.getRuntime().totalMemory()/1024/1024);
}
结果如下 也就是说 我这个16G的电脑 在不设置任何JVM参数的情况下 最大内存给到了3596M 也就是3.5G 但是目前的总内存使用情况 仅仅有243M 那么现在我们在代码里加入这样一句 分配了200M给这个程序,这就是弹性伸缩了,在没有超过最大内存的时候 total是会弹性的根据已经创建出的内存空间 合理分配,不会一次性直接拿来3596左右的内存,但是在实际生产中我们应该尽量避免这种抖动,
byte[] b=new byte[1024*1024*200];
3596
238
444
所以在正常生产环境下 我们应该设置Xmx和Xms设置成一样的值
-Xmx1344M -Xms1344M
下图是堆空间对应的JVM参数 新生代与老年代比例 配置新生代和老年代堆结构占比 默认 -XX:NewRatio=2 , 标识新生代占1 , 老年代占2 ,新生代占整个堆的1/3 修改占比 -XX:NewPatio=4 , 标识新生代占1 , 老年代占4 , 新生代占整个堆的1/5 新生代----eden和s0 s1比例 Eden空间和另外两个Survivor空间占比分别为8:1:1 可以通过操作选项 -XX:SurvivorRatio 调整这个空间比例。 比如 -XX:SurvivorRatio=8 几乎所有的java对象都在Eden区创建, 但80%的对象生命周期都很短,创建出来就会被销 设置8 那就是 8:1:1 eden:s0:s1 如果设置4 那就4:1:1 也就是说eden占内存的4/6 s0s1各占1/6
这里我们需要思考 在什么情况下需要修改新生代老年代的比例 eden和s0s1的比例
年轻代 老年代内存分配过程
1.new的对象先放在伊甸园区。该区域有大小限制 2.当伊甸园区域填满时,程序又需要创建对象,JVM的垃圾回收器将对eden进行垃圾回收(Minor GC),将伊 甸园区域中不再被其他对象引用的额对象进行销毁,再加载新的对象放到伊甸园区 3.然后将伊甸园区中的剩余对象移动到幸存者0区 4.如果再次触发垃圾回收,此时上次幸存下来的放在幸存者0区的,如果没有回收,就会放到幸存者1区 5.如果再次经历垃圾回收,此时会重新返回幸存者0区,接着再去幸存者1区。 6.如果累计次数到达默认的15次,这会进入养老区。 可以通过设置参数,调整阈值 -XX:MaxTenuringThreshold=N 7.养老区内存不足是,会再次出发GC:Major GC 进行养老区的内存清理 8.如果养老区执行了Major GC后仍然没有办法进行对象的保存,就会报OOM异常 先触发YGC/MinorGC去回收年轻代,eden中回收不了的对象放入S0/S1(这里总会有一个是空着的),比如上次回收的对象放到了S0 这次回收S0里头的对象如果还是回收不掉那就转移到S1并且Age加1 下图的老年代已经出现了对象 这种对象有两种可能 要么是eden区回收的时候想放入S0/S1但是对象太大了 放不进去 就会直接进入老年代,还有一种就是年轻代回收的时候一直回收不掉 在S0和S1之间闪转腾挪 最后Age达到15进入了老年代
1.8GC类别
|