JVM内存结构与Java内存模型(JMM)

本文介绍了JVM内存结构,包括JDK1.7以前和1.8以后的区别,强调了堆、方法区、栈等区域的作用。同时,详细讲解了Java内存模型(JMM),探讨了JMM如何定义线程和主内存的关系,以及内存分配优化如标量替换和栈上分配。文章还提到了JVM调优相关参数及其作用,并讨论了堆外内存的使用与管理,以及Java并发编程中的可见性问题和happens-before规则。
摘要由CSDN通过智能技术生成

JVM内存结构和内存模型是不一样的东西,内存结构是指 Jvm 运行时将数据分区域存储,强调对内存空间的划分。

而内存模型(Java Memory Model,简称 JMM )是定义了线程和主内存之间的抽象关系,即 JMM 定义了 JVM 在计算机内存(RAM)中的工作方式,是虚拟机的内存管理模型,是一种虚拟机工程规范。

JVM 内存结构

JDK1.7以前的内存结构

JDK-1.7-以前的结构

JVM内存结构主要有三大块:堆、方法区和栈。

堆是JVM中最大的一块,由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代的这3种空间年轻代按照8:1:1的比例来分配;

方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);

栈又分为java虚拟机栈和本地方法栈主要用于方法的执行;

JDK1.8以后的内存结构

JDK-1.8-开始的结构

以前的方法区(或永久代),用来存放class,Method等元数据信息,但在JDK1.8已经没有了,取而代之的是MetaSpace(元空间),元空间不在虚拟机里面,减肥果真的能减肥吗而是直接使用本地内存。

为什么要用元空间代替永久代?

(1) 类以及方法的信息比较难确定其大小,因此对于永久代的指定比较困难,太小容易导致永久代溢出,太大容易导致老年代溢出。

(2) 永久代会给GC带来不需要的复杂度,并且回收效率偏低。

JVM架构概览

虚拟机运行架构

1. Java堆(Heap)

对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。


为什么“几乎所有的对象实例都在这里分配内存,而不是全部”?

随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换导致了上面的现象。

标量替换

标量是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。相对的,那些还可以分解的数据叫做聚合量,Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。

在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替,这个过程就是标量替换。

```java

public static void main(String[] args) {

  alloc();

}

private static void alloc() {

  Point point = new Point(1,2);

  System.out.println("point.x="+point.x+"; point.y="+point.y);

}

class Point{

    private int x;

    private int y;

}```

以上代码中,point对象并没有逃逸出alloc方法,并且point对象是可以拆解成标量的。那么,JIT就会不会直接创建Point对象,而是直接使用两个标量int x ,int y来替代Point对象。

以上代码,经过标量替换后,就会变成:

private static void alloc() {

  int x = 1;

  int y = 2;

  System.out.println("point.x="+x+"; point.y="+y);

}

可以看到,Point这个聚合量经过逃逸分析后,发现他并没有逃逸,就被替换成两个聚合量了。那么标量替换有什么好处呢?就是可以大大减少堆内存的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了。

标量替换为栈上分配提供了很好的基础。

2.3栈上分配(为什么有对象不再堆上分配)

在Java虚拟机中,对象是在Java堆中分配内存的,这是一个普遍的常识。在一般情况下,对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。这样就无需在堆上分配内存,也无须进行垃圾回收了。

public static void main(String[] args) {

  long a1 = System.currentTimeMillis();

  for (int i = 0; i < 1000000; i++) {

      alloc();

  }

  // 查看执行时间

  long a2 = System.currentTimeMillis();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pxr007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值