内容简介:点个赞,看一看,好习惯!本文在前面的一篇文章接下来,我们就带着这两个问题展开全文。
点个赞,看一看,好习惯!本文 GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了3个月总结的一线大厂 Java 面试总结,本人已拿腾讯等大厂offer。
在前面的一篇文章 深入理解Java虚拟机-如何利用VisualVM进行性能分析 中讲到了一些关于JVM调优的知识,但是,其实,还是有一些问题没有非常清楚的可以回答的,这里先给出几个问题,然后,我们再展开这篇文章需要讲解的知识。
- 我们生成的对象最开始在哪分配?Eden?Survivor?还是老年代呢?
- 进入到老年代需要满足什么条件呢?
接下来,我们就带着这两个问题展开全文。
1 对象优先在哪分配
其实,通过前面几篇文章的讲解,这个问题其实已经见怪不怪了,在大多数的情况下,对象都是在新生代 Eden区 分配的,在前面的文章我们提到,在Eden区中如果内存不够分配的话,就会进行一次 Minor GC 。同时,我们还知道年轻代中默认下 Eden:Survivor0:Survivor2 = 8:1:1 ,同时,还能通过参数 -XX:SurvivorRatio 来设置这个比例(关于这些参数的分析都可以查看这篇文章: 深入理解Java虚拟机-常用vm参数分析 )。
下面我们通过一个例子来分析是不是这样的。
1.1 实例
给定JVM参数:-Xms40M -Xmx40M -Xmn10M -verbose:gc -XX:+PrintGCDetails -XX:SurvivorRatio=4
前面三个参数设置Java堆的大小为40M,新生代为10M,紧跟着后面两个是用于输入GC信息。更多参数可以查看这篇文章: 深入理解Java虚拟机-常用vm参数分析 。
/**
* @ClassName Test_01
* @Description 参数:-Xms40M -Xmx40M -Xmn20M -XX:+PrintGCDetails -XX:+UseSerialGC -XX:SurvivorRatio=8
* @Author 欧阳思海
* @Date 2019/12/3 16:00
* @Version 1.0
**/
public class Test_01 {
private static final int M = 1024 * 1024;
public static void test() {
byte[] alloc1, alloc2, alloc3, alloc4;
alloc1 = new byte[5 * M];
alloc2 = new byte[5 * M];
alloc3 = new byte[5 * M];
alloc4 = new byte[10 * M];
}
public static void main(String[] args) {
test();
}
}
输入结果:
分析
- eden:from:to=8:1:1,这个因为前面设置了参数
-XX:SurvivorRatio=8。 - 新生代分配了20M的内存,所以前面三个
byte数组可以分配,但是,分配第四个的时候,空间不够,所以,需要进行一次Minor GC,GC之后,新生代从12534K变为598K。 - 前面在新生代分配的内存
Minor GC之后,进入到了Survivor,但是,Survivor不够分配,所以进入到了老年代,老年代已用内存达到了50%。
1.2 回答问题
所以,经过上面的例子我们发现,对象一般优先在新生代分配的,如果新生代内存不够,就进行Minor GC回收内存。
2 进入到老年代需要满足什么条件
先给出答案,分为几点。
- 条件① :大对象直接进入到老年代
- 条件② :长期存活的对象可以进入到老年代
- 条件③ :如果在Survivor空间中相同年龄所有对象的大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象直接进入到老年代
2.1 分析条件①
- 哪些属于大对象呢?
一般来说大对象指的是 很长的字符串及数组 ,或者 静态对象 。
- 那么需要满足多大才是大对象呢?
这个虚拟机提供了一个参数 -XX:PretenureSizeThreshold=n ,只需要大于这个参数所设置的值,就可以直接进入到老年代。
step1:解决了这两个问题,首先,我们不设置上面的参数的例子,将对象的内存大于Eden的大小看看情况。
/**
* @ClassName Test_01
* @Description 参数:-Xms40M -Xmx40M -Xmn20M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
* @Author 欧阳思海
* @Date 2019/12/3 16:00
* @Version 1.0
**/
public class Test_01 {
private static final int M = 1024 * 1024;
public static void test() {
byte[] alloc1, alloc2, alloc3, alloc4;
// alloc1 = new byte[5 * M];
// alloc2 = new byte[5 * M];
// alloc3 = new byte[5 * M];
alloc4 = new byte[22 * M];
}
public static void main(String[] args) {
test();
}
}
我们发现分配失败,Java堆溢出,因为超过了最大值。
step2:下面我们看一个例子:设置 -XX:PretenureSizeThreshold=104,857,600 ,这个单位是B字节(Byte/bait),所以这里是 100M 。
/**
* @ClassName Test_01
* @Description 参数:-Xms2048M -Xmx2048M -Xmn1024M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:PretenureSizeThreshold=104,857,600
* @Author 欧阳思海
* @Date 2019/12/3 16:00
* @Version 1.0
**/
public class Test_01 {
private static final int M = 1024 * 1024;
public static void test() {
byte[] alloc1, alloc2, alloc3, alloc4;
// alloc1 = new byte[5 * M];
// alloc2 = new byte[5 * M];
// alloc3 = new byte[5 * M];
alloc4 = new byte[500 * M];
}
public static void main(String[] args) {
test();
}
}
发现新生代没有分配,直接在老年代分配。
注意: 参数 PretenureSizeThreshold 只对 Serial 和 ParNew 两款收集器有效。
2.2 分析条件②
进入老年代规则: 这里需要知道虚拟机对每个对象有个对象年龄计数器,如果对象在Eden出生经过第一次Minor GC后任然存活,并且能够被Survivor容纳,将被移动到Survivor空间中,并且年龄设置为1。接下来,对象在Survivor中每次经过一次Minor GC,年龄就增加1,默认当年龄达到15,就会进入到老年代。
晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 设置。
在下面的实例中,我们设置 -XX:MaxTenuringThreshold=1 。
/**
* @ClassName Test_01
* @Description 参数:-Xms2048M -Xmx2048M -Xmn1024M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:MaxTenuringThreshold=1
* @Author 欧阳思海
* @Date 2019/12/3 16:00
* @Version 1.0
**/
public class Test_01 {
private static final int M = 1024 * 1024;
public static void test() {
byte[] alloc1, alloc2, alloc3, alloc4;
alloc1 = new byte[300 * M];
alloc2 = new byte[300 * M];
alloc3 = new byte[300 * M];
alloc4 = new byte[500 * M];
}
public static void main(String[] args) {
test();
}
}
从结果可以看出,from和to都没有占用内存,而老年代则占用了很多内存。
2.3 分析条件③
条件③是:如果在Survivor空间中相同年龄所有对象的大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象直接进入到老年代,而不需要等到参数 -XX:MaxTenuringThreshold 设置的年龄。
实例分析
/**
* @ClassName Test_01
* @Description 参数:-Xms2048M -Xmx2048M -Xmn1024M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
* @Author 欧阳思海
* @Date 2019/12/3 16:00
* @Version 1.0
**/
public class Test_01 {
private static final int M = 1024 * 1024;
public static void test() {
byte[] alloc1, alloc2, alloc3, alloc4;
alloc1 = new byte[100 * M];
alloc2 = new byte[100 * M];
//分配alloc3之前,空间不够,所以minor GC,接着分配alloc3=900M大于Survivor空间一半,直接到老年代。
alloc3 = new byte[900 * M];
// alloc4 = new byte[500 * M];
}
public static void main(String[] args) {
test();
}
}
输入结果:
分配alloc3之前,空间不够,所以minor GC,接着分配alloc3=900M大于Survivor空间一半,直接到老年代。从而发现,survivor占用0,而老年代占用900M。
3 总结
这篇文章主要讲解了JVM内存分配与回收策略的原理,回答了下面的这两个问题。
- 我们生成的对象最开始在哪分配?Eden?Survivor?还是老年代呢?
- 进入到老年代需要满足什么条件呢?
另外,我花了3个月时间把Java学习和面试的总结整理成了一本电子书!目录如下
现在免费分享大家,在我的公众号 好好学java 回复 Java面试 即可获取。
有收获?希望老铁们来个三连击,给更多的人看到这篇文章
1、老铁们,关注我的原创微信公众号「 好好学java 」,专注于Java、数据结构和算法、微服务、中间件等技术分享,保证你看完有所收获。
2、给俺点个赞呗,可以让更多的人看到这篇文章,顺便激励下我继续写作,嘻嘻。
点赞是对我最大的鼓励
↓↓↓↓↓↓
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 一篇文章搞定前端面试
- 搞定面试中的系统设计题
- Golang精编100题-搞定golang面试
- 一文搞定HashMap的实现原理和面试
- 一文搞定 UDP 和 TCP 高频面试题!
- 搞定PHP面试 - HTTP协议知识点整理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
编译原理
Alfred V. Aho、Monica S.Lam、Ravi Sethi、Jeffrey D. Ullman / 赵建华、郑滔、戴新宇 / 机械工业出版社 / 2008年12月 / 89.00元
本书全面、深入地探讨了编译器设计方面的重要主题,包括词法分析、语法分析、语法制导定义和语法制导翻译、运行时刻环境、目标代码生成、代码优化技术、并行性检测以及过程间分析技术,并在相关章节中给出大量的实例。与上一版相比,本书进行了全面的修订,涵盖了编译器开发方面的最新进展。每章中都提供了大量的系统及参考文献。 本书是编译原理课程方面的经典教材,内容丰富,适合作为高等院校计算机及相关专业本科生及研......一起来看看 《编译原理》 这本书的介绍吧!
CSS 压缩/解压工具
在线压缩/解压 CSS 代码
SHA 加密
SHA 加密工具