目录
什么是JVM?
主流虚拟机
JVM与操作系统关系
JVM、JRE、JDK的关系
Java程序的执行过程
JVM翻译字节码有三种执行方式
Java虚拟机的内存管理
JVM整体架构图
JVM运行时内存
Java7和Java8内存结构的不同主要体现在方法区的实现
对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?
方法区Java8之后的变化
Java8为什么要将永久代替换成Metaspace?
PC程序计数器
PC寄存器的特点
虚拟机栈
什么是虚拟机栈?
什么是栈帧?
局部变量表
操作数栈
动态链接
方法返回地址
本地方法栈
特点
Java堆
什么是堆?
堆的特点
设置堆空间大小(内存大小-Xmx/-Xms )
堆的分类
年轻代和老年代
对象分配过程
堆GC
元空间
永久代与元空间的区别
为什么要废弃永久代,引入元空间?
废除永久代的好处
方法区
方法区的理解
方法区的特点
方法区结构
方法区设置
运行时常量池
直接内存
什么是JVM?
相信大家已经很不陌生了,只要接触编程以及Java的小伙伴们都肯定知道。大概介绍一下。
JVM是Java Virtual Macine(java虚拟机)的缩写,JVM是一种用于计算设别的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
主流虚拟机
虚拟机名称 | 介绍 |
---|---|
HotSpot | Oracle/Sun JDK和OpenJDK都使用HotSPot VM的相同核心 |
J9 | J9是IBM开发的高度模块化的JVM |
JRockit | JRockit与HotSpot同属于Oracle,目前为止Oracle一直在推进HotSpot与JRockit两款各有优势的虚拟机进行融合互补 |
Zing | 由Azul Systems根据HostPot为基础改进的高性能低延迟的JVM |
Dalvik | Android上的Dalvik虽然名字不叫JVM,但骨子里就是不折不扣的JVM |
JVM与操作系统关系
从图中可以看到,有了JVM这个抽象层之后,Java就可以实现跨平台了。JVM只需要保证能够正确执行.class文件,就可以运行在诸如Linux、Windows、MacOS等平台上。
而Java跨平台的意义在于一次编译,处处运行,能够做到这一点JVM功不可没。比如我们再Maven仓库下载同一个版本的jar包就可以到处运行,不需要再每个平台上再编译一次。
现在的一些JVM的扩展语言,比如Clojure、JRuby、Groovy等,编译到最后都是.class文件,Java语言的维护者,只需要控制好JVM这个解析器,就可以将这些扩展语言无缝的运行再JVM之上了。
JVM与操作系统之间的关系:JVM上承开发语言,下接操作系统,它的中间接口就是字节码。
JVM、JRE、JDK的关系
JVM是Java程序能够运行的核心。但是需要注意,JVM自己什么也干不了,需要给它提供生产原料(.class文件)。
而且是需要借助于基本的类库,所谓的 JRE(Java Runtime Environment)Java的运行时环境,仅靠JVM是无法完成一次编译的。
Java程序的执行过程
这里的Java程序是文本格式的。以下代码它遵循的就是Java语言规范。
其中,我们调用了System.out等模块,也就是JRE里提供的类库。
public class HelloWorld{
public static void main(){
System.out.println("Hello World");
}
}
使用JDK的工具javac进行编译后,会产生HelloWorld的字节码。
Java字节码是沟通JVM与Java程序的桥梁,以下是字节码示例。
0 getstatic #2 <java/lang/System.out> //getstatic 获取静态字段的值
3 ldc #3 <Hello World> //ldc 常量池中的常量值入栈
5 invokevirtual #4 <java/io/PrintStream.println> //invokevirtual 运行时方法绑定调用方法
8 return //void 函数返回
JVM虚拟机采用基于栈的结构,其指令由操作码和操作数组成。这些字节码指令,就叫做opcode。其中,getstatic、ldc、invokevirtual、return等,就是opcode。
JVM就是靠解析这些opcode和操作数来完成程序的执行的。当我们使用java命令运行.class文件的时候,实际上就相当于启动了一个JVM进程。
JVM翻译字节码有三种执行方式
(1)解释执行,将opcode+操作数翻译成机器码;要开启此模式,使用-Xint参数;
java -Xint -version
java version "1.8.0_71"
Java(TM) SE Runtime Environment (build 1.8.0_71-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, interpreted mode)
缺点: 这种模式会降低运行速度,通常低10倍或者更多。
(2)JIT(即时编译),它会在一定条件下(不管是否热点代码,对所有的函数)将字节码编译成机器码之后再执行;要开启此模式,使用-Xcomp参数;
java -Xcomp -version
java version "1.8.0_71"
Java(TM) SE Runtime Environment (build 1.8.0_71-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, compiled mode)
缺点:JVM在第一次使用时就会把所有的字节码编译为本地代码, 从而优化执行速度,绕开缓慢的解释器。但是这种模式没有让JVM启用JIT编译器的全部功能。
(3)混合执行(默认),JVM默认的执行模式,部分函数会解释执行,部分会编译执行。如果函数调用频率高,被反复使用,就会认为是热点代码,该函数就会编译执行。
Java虚拟机的内存管理
JVM整体架构图
JVM五大模块分为:类装载器子系统、运行时数据区、执行引擎、本地方法接口、垃圾手机模块。
JVM内存共分为:虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
JVM运行时内存
Java7和Java8内存结构的不同主要体现在方法区的实现
JDK7内存结构
JDK8的内存结构
针对JDK8虚拟机内存详解
JDK7和JDK8变化小结
线程私有的:
对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?
当然不是,方法区只是一个规范,只不过它的实现变了。
在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在本地内存(Native memory)。
方法区Java8之后的变化
- 移除了永久代(PermGen),替换为元空间(Metaspace)。
- 永久代中的class metadata(类元信息)转移到了natice memory(本地内存,而不是虚拟机)。
- 永久代中的interned Strings(字符串常量池)和class static variables(类静态变量)转移到了Java heap。
- 永久代参数(PermSize MaxPermSize)--->元空间参数(MetaspaceSize MaxMetaspaceSize)
Java8为什么要将永久代替换成Metaspace?
- 字符串存在永久代中,容易出现性能问题和内存溢出。
- 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
- 永久代会为GC带来不必要的复杂度,并且回收效率偏低。
- Oracle可能会将HotSpot与JRockit合二为一,JRockit没有所谓的永久代。
PC程序计数器
PC寄存器的特点
- 区别于计算机硬件的pc寄存器,两者不略有不同。计算机用pc寄存器来存放“伪指令”或者地址,而相对于虚拟机,pc寄存器它表现为一块内存,虚拟机的pc寄存器的功能也是存放伪指令,更确切的说存放的是将要执行指令的地址。
- 当虚拟机正在执行的方法是一个本地(native)方法的时候,jvm的pc寄存器存储的值是undefined。
- 程序计数器是线程私有的,它的生命周期与线程相同,每个线程都有一个。
- 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处 理器只会执行一条线程中的指令。
虚拟机栈
什么是虚拟机栈?
package com.lagou.unit;
public class StackDemo {
public static void main(String[] args) {
StackDemo sd = new StackDemo();
sd.A();
}
public void A(){
int a = 10;
System.out.println(" method A start");
System.out.println(a);
B();
System.out.println("method A end");
}
public void B(){
int b = 20;
System.out.println(" method B start");
C();
System.out.println("method B end");
}
private void C() {
int c = 30;
System.out.println(" method C start");
System.out.println("method C end");
}
}
什么是栈帧?
设置虚拟机栈的大小
- Linux/x64 (64-bit): 1024 KB
- macOS (64-bit): 1024 KB
- Oracle Solaris/x64 (64-bit): 1024 KB
- Windows: The default value depends on virtual memory
-Xss1m-Xss1024k-Xss1048576
局部变量表
操作数栈
动态链接
Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态链接(Dynamic Linking)。
动态链接的作用: 将符号引用转换成直接引用。
方法返回地址
方法返回地址存放调用该方法的PC寄存器的值。一个方法的结束,有两种方式:正常地执行完成,出现未处理的异常非正常的退出。无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的PC计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。
无论方法是否正常完成,都需要返回到方法被调用的位置,程序才能继续进行。
本地方法栈
特点
(1)本地方法栈加载native的方法,native类方法存在的意义当然是填补java代码不方便实现的缺陷而提出的。
(2)虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到都得Native方法服务。
(3)是线程私有的,它的生命周期与线程相同,每个线程都有一个。
在Java虚拟机规范中,对本地方法栈这块区域,与Java虚拟机栈一样,规定了两种类型的异常:
(1)StackOverFlowError:线程请求的栈深度>所允许的深度。
(2)OutOfMemoryError:本地方法栈扩展时无法申请到足够的内存。
Java堆
什么是堆?
堆的特点
序号 | 特点 |
---|---|
1 | shiJava虚拟机所管理的内存中最大的一块。 |
2 | 堆是jvm所有线程共享的。 堆中也包含私有的线程缓冲区:Thread Local Allocation Buffer(TLAB) |
3 | 在虚拟机启动时创建。 |
4 | 唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。 |
5 |
Java
堆是垃圾收集器管理的主要区域。
|
6 |
因此很多时候
java
堆也被称为
“GC
堆
”
(
Garbage Collected Heap
)。从内存回收的角度来看,由于现在收集器
基本都采用分代收集算法,所以
Java
堆还可以细分为:新生代和老年代;新生代又可以分为:
Eden
空间、
From
Survivor
空间、
To Survivor
空间。
|
7 |
java
堆是计算机物理存储上不连续的、逻辑上是连续的,也是大小可调节的(通过
-Xms
和
-Xmx
控制)。
|
8 |
方法结束后
,
堆中对象不会马上移出仅仅在垃圾回收的时候时候才移除。
|
9 |
如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出
OutOfMemoryError
异常
|
设置堆空间大小(内存大小-Xmx/-Xms )
public class TestVm {
public static void main(String[] args) {
//补充
//byte[] b=new byte[4*1024*1024];
//System.out.println("分配了4M空间给数组");
System.out.print("Xmx=");
System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
System.out.print("free mem=");
System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");
System.out.print("total mem=");
System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
}
}
Xmx=18.0M // 设置的Xmx20m 但实际是18m,这里没太懂
free mem=4.789039611816406M
total mem=9.0M
byte[] b=new byte[4*1024*1024];
System.out.println("分配了4M空间给数组");
Xmx=18.0M
free mem=3.751373291015625M
total mem=11.5M
堆的分类
- 青年代Young Generation
- 老年代Old Generation
- 永久代Permanent Generation
在Java8以后,由于方法区的内存不在分配在Java堆上,而是存储于本地内存元空间Metaspace中,所以永久代就不存在了
年轻代和老年代
1)年轻代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分 、成1个Eden Space和2个Suvivor Space(from 和to)。
2)年老代(Tenured Gen):年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁。
2.配置新生代和老年代堆结构占比:
从图中可以看出:
对象分配过程
JVM设计者不仅需要考虑到内存如何分配,在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,因此还需要考虑GC执行完内存回收后是否存在空间中间产生内存碎片。
具体步骤:(仔细看完)
- new的对象先放在Eden区。该区域有大小限制。
- 当Eden区填满时,程序又需要创建对象, JVM的垃圾回收器将对Eden进行垃圾回收(Minor GC),将Eden区域中不再被其他对象引用的对象进行销毁,再加载新的对象放到Eden
- 然后将Eden区中的剩余对象移动到from survivor区
- 如果再次触发垃圾回收,此时上次幸存下来的放在from survivor区的,如果没有回收,就会放到 to survivor区
- 如果再次经历垃圾回收,此时会重新返回from survivor区,接着再去to survivor区
- 如果累计次数到达默认的15次,这会进入Old Memory区
- Old Memory区内存不足是会再次触发GC:Major GC进行Old Memory区的内存清理
- 如果Old Memory区执行了Major GC后仍然没有办法进行对象的保存,就会报OOM异常
堆GC
Java中的堆是GC收集垃圾的主要区域。GC分为两种:一种是部分收集器(Partial GC)另一类是整堆收集器(Full GC)。
部分收集器:(不是完整收集java堆的收集器)
- 新生代收集器(Minor GC/Yound GC):只是新生代的垃圾收集
- 老年代收集(Major GC/Old GC):只是老年代的垃圾收集(CMS GC单独回收老年代)
- 混合收集(Mixed GC):收集整个新生代及老年代的垃圾收集(G1 GC会混合回收,region区域回收)
整堆收集(Full GC):收集整个java堆喝方法区的垃圾收集器
年轻代GC触发条件:
- 年轻代空间不足,就会触发MinorGC,这里年轻代指的是Eden代满,Survivor不满不会引发GC
- Minor GC会引发STW(stop the world),暂停其他用户的线程,等垃圾回收接收,用户的线程才恢复.
老年代GC(Major GC)触发机制:
- 老年代空间不足时,会尝试触发MinorGC,如果空间还是不足,则触发Major GC
- 如果Major GC,内存仍然不足,则报错OOM
- Major GC的速度比Minor GC慢10倍以上
- 慢的原因有两个:
- 1) major gc使用的算法复杂度比minor gc高
- 2) major gc使用的算法复杂度比minor gc高
- 慢的原因有两个:
Full GC触发机制:
- 调用System.gc(),系统会执行Full GC,不是立即执行
- 老年代空间不足
- 方法区空间不足
- 通过Minor GC进入老年代平均大小 大于老年代可用内存
元空间
在JDK1.7之前,HotSpot虚拟机把方法区当成永久代来进行垃圾回收。从JDK1.8开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中,HotSpots取消了永久代,方法区是一个规范,规范没变,只不过取代永久代的是元空间(Metaspace)。
永久代与元空间的区别
区别 | 永久代 | 元空间 |
---|---|---|
存储位置不同 | 永久代在物理上是堆的一部分,和新生代、老年代的地址是连续的 | 元空间属于本地内存 |
存储内容不同 | 永久代用来存放类的元数据信息、静态变量以及常量池等 | 类的元信息存储在元空间; 静态变量和常量池等并入堆中; 相当于原来的永久代中的数据,被原空间和堆内存给瓜分了。 |
为什么要废弃永久代,引入元空间?
- 在原来的永久代划分中,永久代需要存放类的元数据、静态变量和常量等。它的大小不容易确定,因为这其中又很多影响因素,比如类的总数,常量池的大小和方法数量等,-XX:MaxPermSize指定太小很容易造成永久代内存溢出。
- 移除永久代是为融合HotSpot VM与JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
- 永久代会为GC带来不必要的复杂度,并且回收效率偏低。
废除永久代的好处
- 由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。不会遇到永久代存在时的内存溢出错误。
- 将运行时常量池从PermGen(永久代)分离出来,与类的元数据分开,提升类元数据的独立性。
- 将元数据从PermGen(永久代)剥离出来到Metaspace(元空间),可以提升对元数据的管理同时提升GC效率。
Metaspace相关参数
- -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整;如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
- -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。如果没有使用该参数来设置类的元数据的大小,其最大可利用空间是整个系统内存的可用空间。JVM也可以增加本地内存空间来满足类元数据信息的存储。
但是如果没有设置最大值,则可能存在bug导致Metaspace的空间在不停的扩展,会导致机器的内存不足;进而可能出现swap(交换分区)内存被耗尽;最终导致进程直接被系统kill掉。 - -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少分配空间所导致的垃圾收集
- -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
方法区
方法区的理解
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器后的代码缓存等数据。
《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但是一些简单的实现可能不会选择去进行垃圾收集或者进行压缩”。对HotSpot而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
元空间、永久代是方法区具体的落地实现。方法区看作是一块独立于Java堆的内存空间,它主要是用来存储所加载的类信息的。
创建对象各数据区域的声明:
方法区的特点
- 方法区与堆一样是各个线程共享的内存区域
- 方法区在JVM启动的时候就会被创建并且它实例的物理内存空间和Java堆一样都可以不连续
- 方法区的大小跟堆空间一样 可以选择固定大小或者动态变化
- 方法区的对象决定了系统可以保存多少个类,如果系统定义了太多的类导致方法区溢出虚拟机同样会抛出(OOM)异常(Java7之前是PermGen Space(永久代),Java8之后是MetaSpace(元空间))
- 关闭JVM就会释放这个区域的内存
方法区结构
方法区的内部结构
类加载器将Class文件加载到内存之后,将类的信息存储到方法区中。
方法区中存储的内容:
- 类型信息(域信息、方法信息)
- 运行时常量池
类型信息
对每个加载的类型(类Class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
① 这个类型的完整有效名称(全名 = 包名.类名)
② 这个类型直接父类的完整有效名(对于 interface或是java.lang. Object,都没有父类)
③ 这个类型的修饰符(public, abstract,final的某个子集)
域信息
域信息,即为类的属性,成员变量
JVM必须在方法区中保存类所有的成员变量相关信息及声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(public、private、protected、static、final、volatile、transient的某个子集)
方法信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
- 方法名称方法的返回类型(或void)
- 方法参数的数量和类型(按顺序)
- 方法的修饰符public、private、protected、static、final、synchronized、native、abstract的一个子集
- 方法的字节码bytecodes、操作数栈、局部变量表及大小(abstract和native方法除外)
- 异常表(abstract和native方法除外)。每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
方法区设置
方法区的大小不必是固定的,JVM可以根据应用的需要动态调整。
JDK7及以前:
- 通过-xx:Permsize来设置永久代初始分配空间。默认值是20.75M
- -XX:MaxPermsize来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M
-
当 JVM 加载的类信息容量超过了这个值,会报异常 OutofMemoryError:PermGen space 。
- jps #是java提供的一个显示当前所有java进程pid的命令
- jinfo -flag PermSize 进程号 #查看进程的PermSize初始化空间大小
- jinfo -flag MaxPermSize 进程号 #查看PermSize最大空间
JDK8以后:
元数据区大小可以使用参数 -XX:MetaspaceSize和 -XX:MaxMetaspaceSize指定。
默认值依赖于平台。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,即没有限制。
与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。如果元数据区发生溢出,虚拟机一样会抛出异常OutOfMemoryError:Metaspace。
-XX:MetaspaceSize:设置初始的元空间大小。对于一个64位的服务器JVM来说,其默认的 -xx:MetaspaceSize值为21MB。这就是初始的高水位线,一旦触及这个水位线,FullGC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活)然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值。
如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到FullGC多次调用。为了避免频繁地GC,建议将-XX:MetaspaceSize设置一个相对较高的值。
jps #查看进程号
jinfo -flag MetaspaceSize 进程号 #查看Metaspace 最大分配内存空间
jinfo -flag MaxMetaspaceSize 进程号 #查看Metaspace 最大空间
运行时常量池
常量池和运行时常量池区别
存放位置不同 | 概念不同 | |
---|---|---|
常量池 | 字节码文件中 | 存放编译期间生成的各种字面量和符号引用 |
运行时常量池 | 方法区中 | 常量池表在运行时的表现形式 |
编译后的字节码文件中包含了类型信息、域信息、方法信息等。通过ClassLoader将字节码文件的常量池中的信息加载到内存中,存储在了方法区的运行时常量池中。
理解为字节码中的常量池Constant pool只是文件信息,它想要执行就必须加载到内存中。而Java程序是靠JVM,更具体的来说是JVM的执行引擎来解释执行的。执行引擎在运行时常量池中取数据,被加载的字节码常量池中的信息时放到了方法区的运行时常量池中。
它们不是一个概念,存放的位置是不同的。一个在字节码文件中,一个在方法区中。
以下是对字节码文件反编译之后,查看常量池相关信息:
直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分。
在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能, 因为避免了在Java堆和Navtice堆中来回复制数据。
NIO的Buffer提供一个可以直接访问系统物理内存的类——DirectBuffer。DirectBuffer类继承ByteBuffer,但和普通的ByteBuffer不同。普通的ByteBuffer仍在JVM堆上分配内存,其最大内存受到最大堆内存的限制。而DirectBuffer直接分配在物理内存中,并不占用堆空间。在访问普通的ByteBuffer时,系统总是会使用一个“内核缓冲区”进行操作。而DirectBuffer所处的位置,就相当于这个“内核缓冲区”。因此,使用DirectBuffer是一种更加接近内存底层的方法,所以它的速度比普通的ByteBuffer更快。
通过使用堆外内存,可以带来以下好处:
- 改善堆过大时垃圾回收效率,减少停顿。Full GC时会扫描堆内存,回收效率和堆大小成正比。Native的内存,由OS负责管理和回收。
- 减少内存在Native堆和JVM堆拷贝过程,避免拷贝损耗,降低内存使用。
- 可突破JVM内存大小限制。
作者:筱白爱学习!!
欢迎关注转发评论点赞沟通,您的支持是筱白的动力!