JVM 之 对象内存分配

目录

JVM内存分配策略

1.优先分配到Eden

2.大对象直接分配到老年代

3.长期存活的对象分配到老年代

4.空间分配担保

栈上分配

逃逸分析

逃逸状态

逃逸分析优化


JVM内存分配策略

1.优先分配到Eden

过程描述
        当Eden分配内存大小不足时,会向有内存的区域借,Survivor如果不足,则向老年代借,即内存担保(将先创建的对象移动到老年代,新对象继续创建到Eden中)

如何打印GC信息?
    配置:-verbosegc  -XX:+PrintGCDetails

JVM设置

限制内存区域大小:
        -Xms20M :设定程序启动时占用内存大小
        -Xmx20M :设定程序运行期间最大可占用的内存大小
        -Xmn10M :设定年轻代大小
        -Xss128k:设置每个线程的堆栈大小
        -XX:SurvivorRatio=8 :限制Eden区域占新生代比为0.8

2.大对象直接分配到老年代

指定多大对象范围

    -XX:PretenureSizeThreshold=6M 

原因
    避免Eden gc时频繁的移动对象

3.长期存活的对象分配到老年代

设置多久为长期

   -XX:MaxTenuringThreshold=15 (对象被复制的次数)

4.空间分配担保

(Eden内存不够,老年代借给内存,前提是老年代内存足够)
开启空间担保
    -XX:+HandlePromotionFailure

关闭空间担保
    -XX:-HandlePromotionFailure

栈上分配

    优点:
        变量分配根据方法的开始和完成进行处理,不需要GC去释放。

逃逸分析

(可以使对象分配到栈上)

目标
分析对象的作用域
当分析对象只在方法内使用(局部变量),则分配到栈上。

逃逸状态

1、全局逃逸(GlobalEscape)

即一个对象的作用范围逃出了当前方法或者当前线程,有以下几种场景:

  • 对象是一个静态变量
  • 对象是一个已经发生逃逸的对象
  • 对象作为当前方法的返回值

2、参数逃逸(ArgEscape)

即一个对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸,这个状态是通过被调方法的字节码确定的。

3、没有逃逸

即方法中的对象没有发生逃逸。

逃逸分析优化

针对上面第三点,当一个对象没有逃逸时,可以得到以下几个虚拟机的优化。

1) 锁消除

我们知道线程同步锁是非常牺牲性能的,当编译器确定当前对象只有当前线程使用,那么就会移除该对象的同步锁。

例如,StringBuffer 和 Vector 都是用 synchronized 修饰线程安全的,但大部分情况下,它们都只是在当前线程中用到,这样编译器就会优化移除掉这些锁操作。

锁消除的 JVM 参数如下:

  • 开启锁消除:-XX:+EliminateLocks
  • 关闭锁消除:-XX:-EliminateLocks

锁消除在 JDK8 中都是默认开启的,并且锁消除都要建立在逃逸分析的基础上。

2) 标量替换

首先要明白标量和聚合量,基础类型和对象的引用可以理解为标量,它们不能被进一步分解。而能被进一步分解的量就是聚合量,比如:对象。

对象是聚合量,它又可以被进一步分解成标量,将其成员变量分解为分散的变量,这就叫做标量替换。

这样,如果一个对象没有发生逃逸,那压根就不用创建它,只会在栈或者寄存器上创建它用到的成员标量,节省了内存空间,也提升了应用程序性能。

标量替换的 JVM 参数如下:

  • 开启标量替换:-XX:+EliminateAllocations
  • 关闭标量替换:-XX:-EliminateAllocations
  • 显示标量替换详情:-XX:+PrintEliminateAllocations

标量替换同样在 JDK8 中都是默认开启的,并且都要建立在逃逸分析的基础上。

3) 栈上分配

当对象没有发生逃逸时,该对象就可以通过标量替换分解成成员标量分配在栈内存中,和方法的生命周期一致,随着栈帧出栈时销毁,减少了 GC 压力,提高了应用程序性能。

发布了155 篇原创文章 · 获赞 11 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u013919153/article/details/105251586