JVM专题一:JVM内存模型


java中是把内存的管理交给java虚拟机来管的,有java虚拟机中的垃圾回收机制来清理内存

Java虚拟机(Java virtualmachine)实现了Java语言最重要的特征:即平台无关性。

平台无关性原理:编译后的 Java程序(.class文件)由 JVM执行。JVM屏蔽了与具体平台相关的信息,使程序可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。因此实现Java平台无关性。

一、内存区域

根据《Java虚拟机规范(Java SE 7版)》规定,Java虚拟机所管理的内存将包括以下几个运行时数据区域,如图:

在这里插入图片描述

1、线程私有的内存区域:

  • 程序计数器:可看做当前线程执行字节码的行号指示器,字节码解释器工作时通过改变计数器的值来选择下一条所需执行的字节码指令
  • 虚拟机栈:Java方法执行的栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用至执行完成的过程,都对应一个栈帧在虚拟机栈的入栈到出栈的过程
    • 局部变量表:存放编译期可知的基本数据类型(boolean、byte、char、int等)、对象引用(reference类型)和 returnAddress类型(指向一条字节码指令的地址)
  • 本地方法栈:Native方法执行的栈帧

2、所有线程共享的内存区域:

  • 堆:存放对象实例和数组
  • 方法区:存储被虚拟机加载的Class类信息、final常量、static静态变量、即时编译器编译后的代码等数据
    • 运行时常量池:存放编译生成的各种字面量和符号引用,运行期间也可能将新的常量放入池中

在整个JVM运行时数据区之中,关键部分在于需要进行堆的优化,既然要进行优化,那就么必须清除Java的对象访问模式。Java在进行对象引用的时候并没有使用到句柄的概念(步骤多一些,导致性能下降),它直接采用的HotSpot虚拟机标准的指针引用。

LS:json qxr4383$ Java -version
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)

JVM = 类加载器 classloader+ 执行引擎 executionengine + 运行时数据区域 runtime data area

首先Java源代码文件被Java编译器编译为字节码文件,然后JVM中的类加载器加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM中的运行时数据区(内存)会用来存储程序执行期间需要用到的数据和相关信息。

因此,在Java中我们常常说到的内存管理就是针对这段空间进行管理(如何分配和回收内存空间)。

1、类加载器 ClassLoader
classloader把硬盘上的class文件加载到JVM中的运行时数据区域,但是它不负责这个类文件能否执行,而这个是执行引擎负责的。

2、执行引擎 executionengine
作用:执行字节码,或者执行本地方法。

3、运行时数据区域 Runtime DataArea
JVM在运行期间,在运行时数据区对JVM内存空间的划分和分配,划分为了以下几个区域来存储。

二. JVM内存结构案例

在这里插入图片描述

public class Math {
    public static final int initData = 666;

    public int compute(){
        int a = 1;
        int b =2;
        int c = (a+b)*10;
        return c;
    }

    public static void main(String[] args) {
        Math math = new Math();
        math.compute();
        System.out.println("math ok");
    }
}

javap -c Math.class > Math.txt文件后:
在这里插入图片描述

三、对象的创建

在语言层面,创建对象(例如:clone,反序列化)通常是一个 new 关键字,而在虚拟机中,对象创建的过程是如何呢?

在虚拟机遇到 new 指令时:
1、 类加载:确保常量池中存放的是已解释的类,且对象所属类型已经初始化过,如果没有,则先执行类加载

2、为新生对象分配内存:对象所需内存大小在类加载时可以确定,将确定大小的内存从Java堆中划分出来

  • 分配空闲内存方法:
    • 指针碰撞:假如堆是规整的,用过的内存和空闲的内存各一边,中间使用指针作为分界点,分配内存时则将指针移动对象大小的距离
    • 空闲列表:假如堆是不规整的,虚拟机需要维护哪些内存块是可用的列表,分配时候从列表中找出足够大的空闲内存划分,并更新列表记录
  • 对象创建在并发情况下保证线程安全:例如,正在给对象A分配内存,指针还没修改,对象B同时使用了原来的指针来分配内存
    • CAS配上失败重试
    • 本地线程分配缓冲TLAB(ThreadLocal Allocation Buffer):将内存分配动作按线程划分到不同空间中进行,即每个线程在Java堆中预先分配一小块内存

3、将分配的内存空间初始化为零值:保证对象的实例在Java代码中可以不赋值就可直接使用,能访问到这些字段的数据类型对应的零值(例如,int类型参数默认为0)

4、设置对象头:设置对象的类的元数据信息、哈希码、GC分代年龄等

5、执行方法初始化:将对象按照程序员的意愿初始化

四、堆内存组织机构以及与内存有关的调整参数

JVM的组成只是作为一个概念的存在,如果每一天都只是进行JVM结构研究对开发的作用很小,最关键的问题就是优化:堆内存空间。
堆内存之中需要考虑关于GC的问题,真正导致程序变慢的原因就在于堆内存的控制策略上。控制回收策略(JDK1.9之后的默认策略已经非常好了,因为其已经更换为了G1)

在这里插入图片描述

1、年轻代(Young Generation)

·伊甸园区(Eden Space):新生的小对象。每当使用关键字new的时候默认的时候都会在此空间进行对象的创建。
如果创建的对象过多,那么最终的结果也有可能造成伊甸园区的内存占满,所以此时就会发生晋级的操作(若干次Young GC执行还保留的对象,晋升到存活区)。

·存活区(Survivor Space):进行一些GC后保持的对象(程序计数器,会记录GC的执行次数),存活区准备两块空间:S0、S1,有一块空间永远都是空的,是向老年代晋升。

2、老年代(Old Generation)

那些又臭又硬的对象,这些对象都已经经历了无数次的GC之后依然被保留下来的对象,例如缓存对象。于是这些对象很难被清除。但是有可能也会被清除。
同时如果是一个很大的对象,那么默认的也会直接保存到老年代,如果现在老年代空间不足了,会出现Full GC,进行老年代的清理(这样的清理是非常消耗性能的),所以这也是为什么不去使用System.gc()方法。

3、如何进行堆结构的优化

(1)伸缩区
·每一块的空间实际上都会提供一个伸缩区;
·伸缩区的考虑是在某个空间不足时,会自动打开伸缩区继续扩大可用的内存,当发现当前的某个区域的空间内存可以满足要求时,就可以进行收缩。
如果不进行收缩的优点:可以提升堆内存的分配效率;
如果不进行收缩的缺点:空间太大了,那么如果没有选择好合适的GC算法,就会造成堆内存的性能下降

public class ShowSpaceDemo {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        System.out.println("MAX_MEMBER:"+runtime.maxMemory()/1024/1024);//最大可用内存
        System.out.println("TOTAL_MEMBER:"+runtime.totalMemory()/1024/1024);//默认的可用内存
        System.out.println("FREE_MEMBER:"+runtime.freeMemory()/1024/1024);//可用的内存中空闲的部分
    }
}

执行结果:

MAX_MEMBER:3641
TOTAL_MEMBER:245
FREE_MEMBER:241

maxMemory:默认大小为当前物理内存的“1/4”, 3641M
totalMemory:默认大小为当前物理内存的“1/64”,245M

伸缩区有这么大的处理范围,所以在进行堆内存分配的过程里面当用户访问量增加的时候就一定会导致不断的 判断空间是否充足,不断的进行内存空间的增长,不断的进行内存空间的收缩释放。

至关重要的两个参数:可以使用的单位(k、m、g)

-Xms:设置初始化的内存分配大学;
-Xmx:设置最大的可用的内存空间。

例如:-Xms16g -Xmx16G
可以减少堆内存的收缩处理操作。当堆内存空间很大情况下就需要考虑到GC的执行效率问题。

(2)年轻代
所以在这个环节里面就需要考虑两个技术名词:BTP、TLAB

·BTP:在伊甸园区采用栈的形式将最晚创建的对象保存在栈顶。
·TLAB:分开保存,适合于多线程的处理操作上。

-Xmn:设置年轻代的空间大小,默认采用的时物理内存的“1/64”。
-Xss:设置每一个线程所占用的栈的线程。
-XX:SurvivorRadio:设置伊甸园区与两个存活区之间的内存分配比,默认“8:1”。
不同的GC方式会按不同的方式来按此值划分Eden Space和Survivor Space,有些GC方式还会根据运行状况来动态调整Eden、From Space、To Space的大小。

(3)老年代
与年轻代比率:-XX:NewRatio
这主要有两种情况:一种为大对象,可以通过启动参数设置-XX:PretenureSizeThreshold=1024,表示超过多大时就不在年轻代分配,而是直接在老年代分配。此参数在年轻代采用Parallel Scavenge GC时无效,因为其会根据运行情况自己决定什么对象直接在老年代上分配内存;另一种为大的数组对象,且数组对象中无引用外部对象。
老年代所占用的内存大小为-Xmx对应的值减去-Xmn对应的值

【分水岭】JDK1.8之后取消了所谓的永久代,而变为了元空间(不在堆内存里面保存,而是直接利用物理内存保存。)

GC算法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值