并发创建时涉及到线程安全问题
多个线程同时创建对象 ,线程不安全
方案:
同步 加锁
本地线程分配缓冲,给每个线程设置分配独立的堆空间
1、基本原则:
- 优先分配到Eden;
- 大对象直接分配到老年代
- 长期存活的对象分配到老年代
- 空间分配担保(跟老年代借用空间)
- 动态对象年龄判断
1.1 优先分配到Eden
jdk作为服务端默认使用parllel收集器
jdk作为client端默认使用serial收集器
使用java -version 可查看当前环境jdk是作为哪个端来使用的;
为什么是server端?
检测内存>2g,cpu是多核环境就默认为server端,所以jdk默认基本都是server端
新创建的对象优先放到Eden区,如果放不下了,尝试放在survival ,仍然放不下就会执行一次MiorGC;再放不下就放在老年代(内存担保,借用老年代),将新生代中原有的对象放在老年代,最新创建的对象放在eden区域;
MiorGC/MajorGC/FullFC ?
MiorGC
清理新生代GC,耗时短,频率高,
MajorGC
清理老年代,耗时久
FullFC
清理整个堆空间,系统调用或者自动调用,耗时最长,频率低
1.2 大对象直接进入老年代
指定大对象的参数
-XX:PretenureSizeTRhreshold
大于这个值的对象直接进入老年代中;
为什么大对象需要进入老年代?
大对象一般是大的字符串和数组,存活时间较长,大对象在Eden区域(采用复制算法)需要经常被移动,所以直接分配到老年代;
1.3 长期存活的对象进入老年代
指定存活年龄的参数
-XX:MaxTenuringThreshold 默认是15
经历过一次gc存活的对象,年龄就会+1;eden区域存活的对象被复制到survival;
jdk1.6之后不严格按这个执行,可能两次之后就直接进入老年代。
1.4 空间分配担保
–XX:+HandlePromotionFailure 启用空间担保 (+开启,-关闭)
启动之后,
- 首先检测老年代是否能容纳下新生代所有内存,是否有能力来进行担保;
- 验证老年代最大的连续可用空间是否大于历次晋升到老年代对象的平均大小;
- (+开启,-关闭)通过参数设置是否进行担保
- 如果有能力且为启用担保状态则给予担保
1.5 逃逸分析+栈上分配
栈上分配内存的好处?
根据方法的执行进行分配与释放,不需要GC回收,性能高;
如何把对象分配到栈空间?
逃逸分析手段—筛选出未逃逸的对象分配到栈上
逃逸分析目标?
分析对象的作用域
什么是逃逸?
- 如果对象作用域(受访问权限)只在本方法体内有效,认为没有发生逃逸
- 一旦该对象引用了外部成员之后就发生了逃逸
- 没有逃逸的对象可以被分配到栈上,不用gc回收
/**
* 逃逸分析
* @author 天赋吉运-杨晓慧
* @create 2019-05-23 9:09
*/
public class StackLocation {
public StackLocation obj;
/**
* 方法返回StackLocation实例对象,发生逃逸
* @return
*/
public StackLocation getInstance(){
return obj == null ? new StackLocation() : obj;
}
/**
* 为成员属性赋值,发生逃逸
*/
public void setObj(){
this.obj = new StackLocation();
}
/**
* 引用成员变量的值,发生逃逸
*/
public void useStackLocation2(){
StackLocation s = getInstance();
}
/**
* 在方法内部使用,没有发生逃逸
*/
public void useStackLocation(){
StackLocation s = new StackLocation();
}
}