您的位置:首页 > 编程语言 > Java开发

Java虚拟机专题之内存分配(读书笔记)

2017-12-19 16:31 232 查看

一 虚拟机内存分配策略

1.1 对象优先在Eden区域分配

1.2 大对象直接进入老年代

1.3 长期存活的对象进入老年代

1.4 空间分配担保

1.5 动态对象年龄判定

 

二 对象优先在Eden区域分配

假设构建4M大小的数组,通过打印GC详细日志,也可以看到是优先在Eden上分配的。

public
class
EdenMemoryAllocation {
     
public static
void
main(String[]
args) {
          
byte[] arr  =
new byte[4 * 1024 * 1024];
      }
}



public
class
EdenMemoryAllocation {
     
public static
void
main(String[]
args) {
          
byte[] arr1  =
new byte[2 * 1024 * 1024];
          
byte[] arr2  =
new byte[2 * 1024 * 1024];
          
byte[] arr3  =
new byte[2 * 1024 * 1024];
          
byte[] arr4  =
new byte[4 * 1024 * 1024];
      }
}

 

三 大对象直接进入老年代

3.1 大对象为什么会放入老年代

大对象一般都需要连续的内存空间,典型的就是很长的字符串或者数组。我们知道在垃圾回收在新生代采用复制算法,如果大的对象继续放在Eden区域,那么Eden区域在执行复制的时候,都需要移动,影响性能,所以放在老年代,很少进行GC。

 

3.2 怎么对大对象定义呢

多大的对象才算大对象呢,虚拟机提供了一个参数可以设置一个阀值,如果超过这个阀值就会分配到老年代。

-XX:PretenureSizeThreshold

情况一:不使用-XX:PretenureSizeThreshold参数

public
class
EdenMemoryAllocation {
     
public static
void
main(String[]
args) {
          
byte[] arr1  =
new byte[8 * 1024 * 1024];
      }
}
-verbose:gc -XX:+PrintGCDetails -Xmx20m -Xms20m-Xmn10m -XX:SurvivorRatio=8

-Xmn: 指定新生代内存大小

XX:SurvivorRatio:指定Eden和Survivor的比例,这也是默认比例。

上面的参数其实就是指定了虚拟机堆大小为20M,但是新生代只有10M, 那么老年代分配10M.

这样方便我们测试:



老年代10M,使用8M,使用了80%的老年代的内存量。

 

情况二: 使用-XX:PretenureSizeThreshold

-verbose:gc -XX:+PrintGCDetails -Xmx20m -Xms20m-Xmn10m -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=4194304

public
class
EdenMemoryAllocation {
     
public static
void
main(String[]
args) {
          
byte[] arr  =
new byte[3 * 1024 * 1024];
      }
}

由于没有超过4M,并不算大对象,所以还是在Eden区分配的。



public
class
EdenMemoryAllocation {
     
public static
void
main(String[]
args) {
          
byte[] arr1  =
new byte[8 * 1024 * 1024];
      }
}



四 长期存活的对象进入老年代

我们知道,在新生代中默认情况下,Survivor区中对象的年龄值大于15的就会被转移到老年代中。

 

这个年龄阀值通过参数-XX:MaxTenuringThreshold控制

比如:-verbose:gc-XX:+PrintGCDetails -Xmx20m -Xms20m -Xmn10m -XX:SurvivorRatio=8-XX:MaxTenuringThreshold=12

 

五 空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果这个条件成立,那我们可以确保Minor GC是安全的,如果不成立则虚拟机会查看HandlePromorionFailure设置的值是否允许担保失败。如果允许,那么就继续检查老年代最大可用的连续内存空间是否大于历次晋升到老年代对象的平均大小,如果大于将尝试着进行一次Minor GC,如果小于或者HandlePromorionFailure设置的值是不允许担保失败(冒险),那么此时会进行Full
GC。

设置是否允许担保失败的参数:-XX:+HandlePromotionFailure

 

六 逃逸分析与栈上分配

6.1 什么是逃逸分析

简单的说,逃逸分析就是进行分析对象的作用域,判断是否有可能逃出当前的方法体。举个逃逸例子:

public
class
EscapeAnalysis {
     
private
EscapeModel
model;
     

     
/**
       *
发生逃逸,因为返回EscapeModel对象,可能会被外部对象引用或者访问
       *
@return
       */
     
public
EscapeModel getEscapeModel(){
          
return
model != null ?
model : new EscapeModel();
      }
     

     
/**
       *
为全局变量赋值,发生逃逸
       */
     
public void
setEscapeModel() {
          
this.model =
new EscapeModel();
      }
     

     
/**
       *
没有发生逃逸:作用域只在方法内部,也不会被其他外部所引用或者访问
       */
     
public void
analysis() {
           EscapeModel
m = new EscapeModel();
      }
     
class
EscapeModel{
 
      }
}

6.2 什么是栈上分配

栈上分配:就是在栈上分配对象,而不是局限于堆上。它是Java虚拟机提供一种优化技术。

我们知道,在执行方法的时候,会创建栈帧,保存局部变量表,操作数栈等信息,然后入栈,到方法结束时,栈帧出栈,自动销毁,而不需要垃圾回收器的介入。

所以栈上分配基本思想就是对于那些线程私有的对象直接在分配在栈里面,而不是堆中。

 

对于小对象,栈上分配是一种很好分配策略,栈上分配的速度快,并且可以有效地避免垃圾回收带来的负面的影响,但由于和堆空间相比,栈空间比较小,因此对于大对象无法也不适合在栈上进行分配。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: