天天看点

小白从0到1看jvm(3、JVM内存结构)

作者:言沫东

一、概述

网上有许多介绍jvm的文章和视频,但大多枯燥、晦涩难懂,很难让小白坚持下去;如果你是一个小白,不用担心,在本文中,我们将从0到1,一步步地介绍JVM的基本概念和工作原理。无论你是想深入了解Java语言,还是准备参加Java开发的面试,这篇文章都将为你提供有用的知识和技能。让我们一起开始这段有趣的旅程吧!

小白从0到1看jvm(3、JVM内存结构)

二、正文

我们编写的.java文件,通过编译生成对应.class字节码文件,由类加载器加载,经历验证、准备、解析、初始化阶段,每个阶段前两篇文章我们已经介绍过了,那么接下来我们正式进入JVM内存区域这一主战场。

JVM 内存共分为:虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

小白从0到1看jvm(3、JVM内存结构)
名称 描述 用途 配置参数 异常
程序计数器 线程私有;生命周期与线程相同;占用内存小 字节码行号指示器(程序运行到哪个位置了)
虚拟机栈 线程私有;生命周期与线程相同;使用连续的内存空间 存储栈帧(一个方法对应一个栈帧),栈帧中存储:局部变量表、操作栈、动态链接、方法出口等信息

-xss

(256k、512k等)

StackOverflowError/OutOfMemoryError
线程共享,生命周期与虚拟机相同,可不使用连续的内存地址 保存对象实例,所有对象实例(new)、数组都在堆上分配

初始堆大小:-Xms

最大堆大小:-Xmx

年轻代的堆大小:-Xmn

OutOfMemoryError
方法区(jdk1.8之前)/元空间(jdk1.8之后) 线程共享,生命周期与虚拟机相同,可不使用连续的内存地址 存储已被虚拟机加载的类元信息、常量、静态变量、即时编译器编译后的代码等数据

初始永久代大小:-XX:PermSize

最大永久代大小:-XX:MaxPermSize

初始元空间大小:-XX:MetaspaceSize

最大元空间大小:-XX:MaxMetaspaceSize

OutOfMemoryError
本地方法栈 线程私有 为虚拟机使用到的Native方法服务 StackOverflowError/OutOfMemoryError

线程私有:程序计数器、虚拟机栈、本地方法栈

线程共享:堆、方法区/元空间

程序计数器

线程私有,当前线程所执行的字节码的行号指示器(程序运行到哪了),例如:当线程获取到CPU时间片,代码正常执行到一半,这时CPU切到另一个线程中去执行,当CPU再次切回来的时候如何能保证代码能正确执行而不会错乱?此时就需要靠程序计数器去完成。

虚拟机栈

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

1、栈帧中为什么不存在垃圾回收
栈帧(方法)每次执行结束自动出栈,所以不会涉及到垃圾的产生,也就不会对栈内存进行垃圾回收
2、什么情况下会导致栈内存溢出(Stack Overflow)
栈帧过多(方法过多),没有足够的栈内存空间来存储更多的栈帧,导致内存溢出, 将抛出StackOverflowError异常
比如:递归调用,不断产生新的栈帧,前面的栈帧不释放,很容易导致内存溢出
栈帧过大,无法分配足够的内存,也会导致内存溢出
3、栈内存分配越大越好吗?
不是的,假设分配的物理内存是100MB,每个线程栈大小是1MB,那么可以分配100个线程;
但是如果提升了线程栈大小,那可以分配的对应线程数就变少了。Linux系统上默认就是1MB,当然我们可以通过-Xss进行大小的更改
4、方法内的局部变量是否线程安全?
不一定;如果方法内局部变量没有逃离【方法的作用访问】,它是线程安全的;如果是局部变量引用了对象,并逃离【方法的作用范围】,需要考虑线程安全           

本地方法栈

线程私有,保存native方法进入区域的地址,他是用来调用第三方库所需要的一片栈空间,这里的第三方库指的是其他语言比如:c开发的库;与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

线程共享,通过 new 关键字创建对象、以及数组都会使用堆内存,堆是垃圾回器主要回收的区域。堆中对象都需要考虑线程安全的问题,默认情况下,初始堆内存大小(-Xms):电脑物理内存大小/64;最大堆内存大小(-Xmx):电脑物理内存大小/4;通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能

方法区

存储类的信息以及运行时常量池,在JDK8以后被元空间代替。方法区主要存放的是(class),而堆中主要存放的是(实例化的对象)

继续阅读