[JVM]二 运行时数据区 (4) 堆

概述

一个JVM实例只存在一个堆内存,堆也是java内存管理的核心区域

Java堆在启动时空间大小也就确定了。是JVM管理的最大一块内存空间。堆内存大小可以调节。

堆在物理上的内存中不连续,但在逻辑上应该被视为是连续的。

所有的线程共享java堆,但堆里还可以划分线程私有的缓冲区

几乎(【逃逸分析】,如果没有逃逸会由栈上分配)所有对象实例和数组都应该在运行时分分配在堆上

栈帧中局部变量表保存的引用就指向在堆区域中的对象或数组

方法结束后,堆中的对象不会马上被移除,GC垃圾回收之后才会被移除

1 堆结构

堆内存逻辑上分为:
新生区 ,老年区 ,(PSPermGen 永久代jdk7)(Metaspace 原空间jdk8)

新生区又分为 伊甸园区(Eden),(Survivor区(新生代0区S0(from区) 和 新生代1区S1(to区))

在这里插入图片描述

几乎所有的java对象都是在Eden区被new出来的

绝大部分的java对象的销毁都在新生代进行

2.对象分配过程

1)new的对象先放到Eden区,此区域有大小限制
2)当Eden区空间填满时,程序又要创建对象,JVM的垃圾回收器将堆Eden区进行YGC,将Eden区中不再被其他对象所引用的对象进行销毁,再加载新的对象放到Eden区
3)将Eden区中剩余的对象移动到survivor区的to区(0区)age+1
4)再次触发YGC时,此时to区逻辑上转换为from区(0区),区域当中没又被回收的被放到 survivor区的to区(1区)之后0区清空,又被转换为to区
5)当有对象age=15时就会被Promotion(晋升)到老年区中

在这里插入图片描述

3.GC

garbage collection

1)YGC或称Minor GC
young GC :年轻代GC

触发条件:当Eden区域满了后发生YGC

红色块表示在Eden区创建又在Eden区就销毁的对象

在这里插入图片描述
在这里插入图片描述

S0 和 S1 区又称为 from 和 to 区
因为两个区是动态转换的 , 每次YGC后将 from 区中对象age累加 并转移到to区中。

在这里插入图片描述

当Survivor区域中有对象age到达15(阈值 默认15)就被Promotion(晋升)到老年区中

因为80%的对象都是在新生代区朝生夕死生存时间比较短暂,所以GC频次最高的即是YGC

survivor区的GC是被动GC,是由于Eden区域内存满了以后发生YGC,survivor被动发生GC,survivor区内存满了不会发生GC

Major GC

Old GC
只是老年代的垃圾回收

出少Major GC 经常会伴随至少一次YGC 但非绝对
也就是说当老年代空间不足时,会先尝试触发YGC。之后空间还不足则触发YGC

Major GC的速度一般会比YGC慢 10 倍以上 STW(Stop-The-World)时间更长

当Major GC之后,内存还不足 就报OOM了

很多时候Major GC 和Full GC会混淆使用,需要具体分辨是老年代回收还是整堆回收

Full GC

整个java堆和方法区的垃圾收集

Full GC的触发情况
1)调用System.gc()
2)老年代空间不足
3)方法去空间不足
4)Minor GC 后进入老年代的平均大小大于老年代的可用内存
5)由Eden区 from区向to区复制时,对象大小大于 to区可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象的大小

GC会引发STW ,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行。 因为GC也是需要GC线程去完成

4.内存分配策略

针对不同年龄段的对象分配原则:

1)优先分配到Eden
2)大对象直接分配到老年代,避免朝生夕死的大对象
3)长期存活的对象分配到老年代
4)动态对象年龄判断:如果survivor区中相同年龄的所有对象大小的综合大于survivor空间的一半,可以直接进入老年代,无需等到要求年龄

$.1 设置堆内存大小 & 查看堆内存

写两个demo
在这里插入图片描述
demo1与demo2代码相同
1)两个都经过编译后(Build-> Recompile ’ demo1 ’ . java)
2)设置VM options (Run-> Edit Configurations)
在这里插入图片描述

//初始堆内存  最大堆内存
Demo1
-Xms10m -Xmx10m   
Demo2
-Xms20m -Xmx20m   

3)让两个程序启动起来
4)在java/jdk/bin/目录下jvisualvm.exe
在这里插入图片描述
demo1
在这里插入图片描述
demo2
在这里插入图片描述

$.2 显示GC信息

1 设置VM options (Run-> Edit Configurations)

-XX:+PrintGCDetails

2 apply OK 后运行程序 在控制台看到堆中GC信息
在这里插入图片描述

$.3 参数相关

堆空间的参数还是比较重要
官网参数查看 600多个参数
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

//查看所有参数的默认初始值
-XX:PrintFlagsInitial

//查看所有参数的最终值 可能存在被自动修改
-XX:PrintFlagsFinal

//初始堆空间内存 默认物理内存的1/64
-Xms

//最大堆空间内存 默认1/4
-Xmx

//设置新生代大小
-Xmn

//配置新生代与老年代在堆结构的占比 默认2 表示新生代占1,老年代占2,新生代占整个堆的1/3
-XX:NewRatio

//配置Eden空间和Survivor比例  默认例是8:1:1
-XX:SurvivorRatio 
//但是由于自适应分配策略有时候并不是8:1:1
//自适应内存分配策略:默认开启
±UseAdaptiveSizePolicy

//养老阈值  默认 15
--XX:MaxTenuringThreshold

//输出详细GC处理日志
-XX:PrintGCDetails

//是否设置空间分配担保 jdk7后不再使用
//思想: 只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小会进行Minor  GC 否则进行Full GC
-XX:HandlePromotionFailure

$ 4 TLAB

Thread Local Allocation Buffer

堆区是线程共享区域,任何线程都可以访问到堆区中的数据
由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的,加锁会减慢程序运行

TLAB:JVM为每个线程在Eden区分配的一个私有缓存区域。
使用TLAB可以避免线程安全问题,还能够提升内存分配吞吐量,我们将这种内存分配方式称为 快速分配策略

在这里插入图片描述

TLAB空间的内存非常小,只有Eden空间的1%。但JVM将TLAB作为内存分配的首选。
如果对象在TLAB空间分配内存失败,JVM就会尝试通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存

$5 逃逸分析

Escape Analysis JDK 7 以后默认开启

随着JIT 即时编译器的发展 与逃逸分析主角按成熟,栈上分配,标量替换优化技术会使得 并非所有对象都在堆上分配

如果逃逸分析(Escape Analysis) 后发现 一个对象并没有逃逸出方法得话,那么就可能被优化成 栈上分配,就无需在堆上分配,也无需垃圾回收,是常见的堆外存储技术

逃逸分析:
当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
当一个对象在方法中被定义后,他被外部方法所引用,则认为发生逃逸,列入作为调用参数传递到其他方法中

通过逃逸分析能够分析出一个对象的引用使用范围从而决定是否要将这个对象分配到堆上

判断是否发生逃逸:new的对象是否有可能在方法外被调用

结论:能使用局部变量,就不要使用在方法外定义

栈上分配

在这里插入图片描述

在VM options中设置关闭逃逸分析

-XX:-DoEscapeAnalysis

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
开启逃逸分析

在这里插入图片描述
在这里插入图片描述

同步省略(锁消除)

如果一个对象只能从一个线程被访问,那么对于这个对象的操作也可以不考虑同步

因为线程同步的代价很高,同步后果会降低并发和性能

借助逃逸分析可以判断同步代码块所使用的锁对象是否只能被一个线程访问。如果是,JIT编译器在编译这个同步代码块时就会取消对这部分代码的同步,也叫做锁消除

标量替换

有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分可以不存储在内存,而是存储在CPU寄存器(栈空间)

标量(Scalar) 是指一个无法在被分解成更小的数据的数据。java中的原始数据类型就是标量。相对的还可以分解的数据叫做聚合量 ,java中的对象就是聚合量。对象还可以分解成其他的聚合量和标量

DoEscapeAnalysis

class Customer {       // 聚合量
        String name;   //标量
        int id;        //标量
        Account acct;  //聚合量
    }

    class Account {
        double balance; //标量
    }

标量替换:在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问,会经过JIT优化把这个对象分解成若干个其中包含的若干个成员变量来代替。减少堆空间占用和GC

public class EAdemo2 {

    public static class User {
        public int id;
        public String name;
    }

    public static void alloc() {
        User u = new User();
        u.id = 5;
        u.name = "www.atqn.com";
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            alloc();
        }
        long end = System.currentTimeMillis();
        System.out.println("使用时间");
        System.out.println((end - start) + "ms");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值