jvm运行时内存

jvm运行时内存,第1张

jvm运行时内存

jdk7中的内存结构

jdk8中的内存结构

线程私有的:程序计数器,虚拟机栈,本地方法
线程共享的:堆,方法区,元空间,直接内存

java8之后方法区的变化:元空间替换永久代,永久代中的类原信息转移到了本地内存,字符串常量池和静态变量转移到了堆

程序计数器

程序计数器是当前线程字节码的行号提示器,在字节码解释器工作的时候改变程序计数器的值来选取下一条将要执行的字节码指令。分支、循环、跳转、异常、线程恢复等都靠这个程序计数器完成。

一个cpu同一时间只能执行一个cpu中的命令,线程未执行完的命令,会保存在线程的程序计数器中


程序计数器在java虚拟机规范中没有规定OutOfMemoryError,不会发生内存溢出

虚拟机栈

虚拟机栈是线程私有的,和线程的生命周期相同。Java虚拟机栈和线程同时创建,用于存储栈帧。每个方法在执行时都会创建一个栈帧(Stack frame),用于存储局部变量表、 *** 作数栈、动态链接、方法出口等信息。每一个方法从调用直到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

public class JvmStack {
    public static void main(String[] args) {
        System.out.println("main方法开始");
        a();
    }

    private static void a() {
        System.out.println("a方法开始");
        b();
    }

    private static void b() {
        System.out.println("b方法开始");
        c();
    }

    private static void c() {
        System.out.println("c方法开始");
    }
}

debug main方法


a方法执行


b方法执行


c方法执行

然后每一个方法执行完后栈帧依次从栈中d出

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构。栈帧存储了方法的局部变量表、 *** 作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。

局部变量表是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。包括8种基本数据类型、对象引用类型(reference)、和returnAddress类型
64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。

*** 作数栈是一个后人先出栈,随着方法的字节码指令的执行,会从局部变量表或者对象实例的字段中复制常量或变量写入到 *** 作数栈,或者将 *** 作数栈中的元素出栈到局部变量表或者返回给方法调用者。

动态连接:java虚拟机中每一个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,这个引用是为了支持方法调用过程中的动态连接将符号引用转换成直接引用

方法返回地址:用于存放调用该方法的程序计数器的值。无论一个方法是正常结束还是异常退出,都应该回到方法被调用的位置。方法正常退出的时候,从方法返回地址返回程序计数器。而异常退出的时候,返回地址通过异常表来确定。

-Xss 为jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5+中是1M

-Xss1m
-Xss1024k
-Xss1048576

idea中设置

本地方法栈

本地方法栈与虚拟机栈相似,只不过本地方法栈为本地方法服务。本地方法栈也是线程私有的,与线程的生命周期相同。

Java堆是虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建,用来存放对象的实例,大部分的对象实例都存放在堆里面,这里说大部分是因为会有逃逸分析将对象在栈上分配内存,堆是垃圾收集器管理的主要区域

堆中也包含了线程私有的线程缓冲区 Thread Local Allocation Buffer (TLAB)

由于现在垃圾收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;
新生代又可以分为:Eden 空间、From Survivor空间、To Survivor空间

堆的大小通过-Xms和-Xmx控制( -Xmx20m -Xms5m 最大内存20m,最小内存5m),方法结束后,堆中不使用的对象不会马上移除,在垃圾回收的时候时候才移除。

Java7 Hotspot虚拟机中将Java堆内存分为3个部分:新生代,老年代,永久代

java8以后永久代被移除,永久代中的类原信息转移到了本地内存,字符串常量池和静态变量转移到了堆

新生代:主要存放新创建的对象,内存大小相对较小,垃圾回收频繁。新生代包括Eden,survivor to ,survivor from。
老年代:老年代主要存放jvm认为生命周期较长的对象,即经过多次垃圾回收后依然存在的对象,内存相对较大,垃圾回收没有很频繁。

新生代和老年代配置比例通过-XX:NewRatio 配置
默认-XX:NewRatio = 2,新生代大小:老年代大小=1:2
Eden空间和另外两个Survivor空间占比为8:1:1,通过-XX:SurvivorRatio修改。-XX:SurvivorRatio = 8

新生代收集:eden空间不足,会触发minor GC。survivor满不会触发GC,Minor GC会引发STW(stop the world) ,暂停其他用户的线程,等垃圾回收接收,用户的线程才恢复

老年代收集:老年代空间不足会触发Major GC,在Major GC之前通常会伴随着Minor GC,如果Major GC后任然内存不足则OOM报错,Major GC速度比Minor GC慢10倍以上

混合收集(Mixed GC):收集整个新生代及老年代的垃圾收集(G1 GC会混合回收, region区域回收)

FullGC触发机制:
1调用system.gc(),系统会执行full gc,但不是立即执行
2老年代空间不足
3方法区空间不足
4通过Minor GC进入老年代平均大小大于老年代可用内存

元空间

jdk1.8开始,移除永久代,并用元空间代替方法区,它位于本地内存中,而不是虚拟机内存中。

永久代是堆的一部分,和新生代老年代的地址是连续的,而元空间在本地内存中。永久代用来存放类的元数据信息、静态变量以及常量等。jdk8之后类的元信息放在元空间中,静态变量和常量放入堆中。

为什么要废弃永久代,引入元空间?
永久代用来存放类的元数据信息、静态变量以及常量,它的大小不容易确定,永久代大小设置太小容易造成永久代内存溢出
移除永久代是为融合HotSpot VM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代
永久代会为gc带来难度,并且效率低

废除永久代的好处
元空间最大可分配空间就是系统的内存空间。不会像永久代那样内存溢出
运行时常量与类的元数据分开,提升类元数据独立性
元数据从永久代放到了元空间,提升元数据的管理效率和GC效率

元空间相关参数设置
初始空间大小:-XX:metaspaceSize 达到这个值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如 果释放了大量空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxmetaspaceSize时,适当提高大小

最大空间:-XX:MaxmetaspaceSize 默认是没有限制的,如果没有限制大小,则可可存在bug导致metaspace的空间在不停的扩展,会导致机器的内存不足;可能出现swap内存被耗尽;最终导致进程被系统kill掉。如果限制了大小,当metaspace剩余空间不足会抛出java.lang.OutOfMemoryError: metaspace space

-XX:MinmetaspaceFreeRatio,在GC之后,最小的metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxmetaspaceFreeRatio,在GC之后,最大的metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

方法区

方法区和java堆一样,是各个线程共享的内存区域,用来储存类信息,常量,静态变量,即时编译器编译后的代码缓存等数据

方法区在JVM启动的时候就会被创建,并且它实例的物理内存空间和Java堆一样都可以不连续,关闭jvm会释放这个区域的内存

方法区中的类信息包括:
1.类型信息:完整有效名(包名.包名.类名),直接父类完整有效名,类型的修饰符(abstract,final),直接接口的有序列表
2.域信息:成员变量相关信息及声明顺序,域名称域类型域修饰符(pυblic、private、protected、static、final、volatile、transient的某个子集)
3.方法信息:方法的名称,返回类型,参数数量和类型,方法的修饰符,方法的字节码, *** 作数栈,局部变量表及大小,异常表(每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引)

常量池和运行时常量池
常量池位于字节码文件中,存放编译期间生成的各种字面量与符号引用
运行时常量池位于方法区中

编译后的字节码文件中包含了类型信息、域信息、方法信息等。通过ClassLoader将字节码文件的常量池中的信息加载到内存中,存储在了方法区的运行时常量池中,虚拟机指令根据常量池找到要执行的类名、方法名、参数类型、字面量等类型

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("HellowWorld");
    }
}

编译后在target下找到它的class文件

右键

反编译字节码文件

javap -v HelloWorld > HelloWorld.txt


常量池信息

方法中引用常量池符号

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4871655.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-11
下一篇 2022-11-11

发表评论

登录后才能评论

评论列表(0条)

保存