JVM内存分配策略

-------------------------------------------------------------------------------JVM内存分配策略-----------------------------------------------------------------------------------------

一:对象内存分配两种方法

为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。

  • 指针碰撞(Serial、ParNew等带Compact过程的收集器) 假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump the Pointer)。
  • 空闲列表(CMS这种基于Mark-Sweep算法的收集器) 如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。

选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。

 

二:Java对象分配流程

三:栈上分配

     我们通过JVM内存分配可以知道JAVA中的对象都是在堆上进行分配,当对象没有被引用的时候,需要依靠GC进行回收内存,如果对象数量较多的时候,会给GC带来较大压力,也间接影响了应用的性能。为了减少临时对象在堆内分配的数量,JVM通过逃逸分析确定该对象不会被外部访问。那就通过标量替换将该对象分解在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。

       逃逸分析:逃逸分析是编译语言中的一种优化分析,而不是一种优化的手段。通过对象的作用范围的分析,为其他优化手段提供分析数据从而进行优化。包括全局变量赋值逃逸,方法返回值逃逸,实例引用发生逃逸,线程逃逸:赋值给类变量或可以在其他线程中访问的实例变量。

       标量替换:标量即不可被进一步分解的量,而JAVA的基本数据类型就是标量(如:int,long等基本数据类型以及reference类型等),标量的对立就是可以被进一步分解的量,而这种量称之为聚合量。而在JAVA中对象就是可以被进一步分解的聚合量。通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替。这些代替的成员变量在栈帧或寄存器上分配空间。

   

四:TLAB分配

五:内存分配规则

     (使用Serial/Serial Old收集器)

        一:对象优先分配在Eden区:大多数情况下,对象在新生代中的Eden区分配,当Eden区没有足够空间进行分配时,虚拟机将发生一次Minor GC。

        二:大对象直接进入老年代:大对象是指,需要大量连续内存空间的Java对象,虚拟机提供了相关参数调整大小。

        三:长期存活的对象进入老年代:每次Minor GC,年龄就增加一岁,默认15岁,进入老年代,也可以通过参数调整。

        四:动态对象年龄判定:如果在Survivor空间中相同年龄所有对象大小的总和大小大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。

        五:空间分配担保:在发生Minor GC前,虚拟机会检查老年代的最大可用连续空间是否大于新生代所有对象的总空间,成立安全,不成立,触发Full GC。

六:测试Demo

AllocDemo.java),工具IDEA,如下图配置VM参数

public class AllocDemo {
    class User {
        int id;
        String name;
        User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
    private void alloc(int i) {
        User user = new User(i, "name" + i);
    }
    public static void main(String[] args) {
        AllocDemo a = new AllocDemo();
        long s1 = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            a.alloc(i);
        }
        long s2 = System.currentTimeMillis();
        System.out.println("time:"+(s2 - s1));//记录分配时间
    }
}

 

  关闭逃逸分析,关闭标量替换,关闭TLAB分配        设置参数: -XX:-DoEscapeAnalysis  -XX:-EliminateAllocations -XX:-UseTLAB  -XX:+PrintGCDetails

 

 关闭逃逸分析,关闭标量替换,开启TLAB分配      设置参数:-XX:-DoEscapeAnalysis  -XX:-EliminateAllocations -XX:+UseTLAB  -XX:+PrintGCDetails

开启逃逸分析,开启标量替换,开启TLAB分配       设置:-XX:+DoEscapeAnalysis  -XX:+EliminateAllocations -XX:+UseTLAB  -XX:+PrintGCDetails

    如果想看java堆上的对象分布情况

    1.通过jps查看Class的Main进程的PID 

    2. jmap -histo [pid]查看java堆上的对象分布情况 。||  jmap -dump:format=b,file=文件名.hprof pid生成dump文件

    如果不熟悉jps,jmap等用法的可以参考官网:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/

   内容主要来源:《深入理解Java虚拟机》

原文地址:https://www.cnblogs.com/dyg0826/p/11039964.html