JVM基础(内存结构)


一、内存结构

可称为java的自动内存管理机制
在这里插入图片描述
三大部分

  • 堆内存
  • 方法区
  • 栈内存

现在jdk默认使用的是 Hot-Spot 虚拟机

在这里插入图片描述

1.1、JAVA堆

堆是虚拟机中最大的一块内存,被所有线程共享的内存区域
唯一作用就是: 放New出来的对象,几乎所有对象都在这里面分配内存1(以后说不定)

堆可以实现固定的大小也可以扩展(通过参数-Xmx和-Xms设定)

扩展失败就会爆OOM异常

堆分两个区老年代和新生代 默认比例 young 1:2 oid 通过:–XX:NewRatio

新生代又分为Eden区和2个Survivor区(From Survivor 和 To Survivor)
默认的,Eden : from : to = 8 : 1 : 1
通过 –XX:SurvivorRatio来设定

深入理解java虚拟机原文 61页:

Java 堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”(Garbage Collected Heap,幸好国内没翻译成“垃圾堆”)。从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现“新生代”“老年代”“永久代”“Eden空间”“From Survivor空间”“To Survivor空间”等名词,这些概念在本书后续章节中还会反复登场亮相,在这里笔者想先说明的是这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个Java虚拟机具体实现的固有内存布局,更不是《Java虚拟机规范》里对Java堆的进一步细致划分。不少资料上经常写着类似于“Java虚拟机的堆内存分为新生代、老年代、永久代、Eden、Survivor……”这样的内容。在十年之前(以G1收集器的出现为分界),作为业界绝对主流的HotSpot虚拟机,它内部的垃圾收集器全部都基于“经典分代” 2来设计,需要新生代、老年代收集器搭配才能工作,在这种背景下,上述说法还算是不会产生太大歧义。

但是到了今天,垃圾收集器技术与十年前已不可同日而语,HotSpot里面也出现了不采 用分代设计的新垃圾收集器,再按照上面的提法就有很多需要商榷的地方了。

如果从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分 配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。不过无 论从什么角度,无论如何划分,都不会改变Java堆中存储内容的共性,无论是哪个区 域,存储的都只能是对象的实例,将Java 堆细分的目的只是为了更好地回收内存,或 者更快地分配内存。在本章中,我们仅仅针对内存区域的作用进行讨论,Java堆中的上 述各个区域的分配、回收等细节将会是下一章的主题。

1.2、方法区 (Method Area)

jdk8之前又称永久代,同堆一样是一个线程共享的内存区域, 存储虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码缓存等数据。

深入理解java虚拟机 原文 62页:

虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫
作“非堆”(Non-Heap),目的是与Java堆区分开来。

JDK8之前的HotSpotJVM,存放这些”永久的”的区域叫做“永久代(permanent
generation)”。永久代是一片连续的堆空间,在JVM启动之前通过在命令行设
置参数-XX:MaxPermSize 来设定永久代最大可分配的内存空间,默认大小是64M
(64位JVM默认是85M)。

jdk8之后叫Metaspace(元空间)其实和之前的概念是一样的

从 Hotspot JVM 中删除永久代,因此需要调整永久代的大小。
官网说明

Class metadata, interned Strings and class static variables will be moved from the permanent generation to either the Java heap or native memory.

The code for the permanent generation in the Hotspot JVM will be removed.
Application startup and footprint will not regress more than 1% as measured by a yet-to-be-chosen set of benchmarks.

1.2.1、运行时常量池(Runtime Constant Pool)

方法区的一部分
用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
-会把由符号引用翻译出来的直接引用也存储在运行时常量池中。

运行时常量池没有格式上的细节规范,没一个虚拟机都可以自己实现这一个内存区域,相对于Class文件的常量池另一个特征是动态性

Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的 intern()方法。

也会报oom异常

1.3、虚拟机栈 (Java Virtual Machine Stack)

常说的栈 内存就是这个玩意
线程私有

生命周期和线程一样

创建线程的时候也会创建虚拟机栈

java程序执行时,每一个方法都会 创建 一个栈帧 放到虚拟机栈中 通过压栈出栈的方式进行方法调用

栈帧又分为一下几个区域:局部变量表、操作数栈、动态连接、方法出口等

我们所说的变量存在栈中,这句话说的不太严谨,应该说局部变量存放在java 虚拟机栈的局部变量表中。
java 的8中基本类型的局部变量的值存放在虚拟机栈的局部变量表中,如果是引用型的变量,则只存储对象的引用地址。

局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、
char、short、int、 float、long、double)、对象引用(reference类型,它并不等同于对象
本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或
者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。

这些数据类型在存储空间以局部变量槽(Slot)来表示,64位长度的long和double占两个Slot,
其余占一个

局部变量的内存空间在 编译期间产生并分配

经常有人把Java内存区域笼统地划分为堆内存(Heap)和栈内存(Stack),这种划
分方式直接继承自传统的C、C++程序的内存布局结构,在Java语言里就显得有些粗糙
了,实际的内存区域划分要比这更复杂。

可能出现的异常就是OutOfMemoryError ,如果可以动态扩展,但是还是不够就会报OutOfMemoryError

1.4、本地方法摘栈(Native Method Stacks)

与虚拟机栈的作用是非常相似的
,区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务
,而本地方法栈则是为虚拟机使用到的Native方法服务。

《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError 异常。

1.5、程序计数器(Program Counter Register)

在虚拟机的概念模型中计数器: 记录当前线程执行程序的位置,改变计数器的值来确定执行的下一条指令,
比如循环、分支、方法跳转、异常处理,线程恢复都是依赖程序计数器来完成。

Java 虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。

为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的。(在任何一个确定的时刻,处理器的一个内核中只会执行一个线程的指令,)

如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是Native方法,这个计数器值则为空(Undefined)。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError(内存溢出)情况的区域。

1.6、直接内存(Direct Memory)

这个不是虚拟机的内存,但是也会检查使用而且还会报oom异常

在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的 DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

这个内存不受java堆的限制,但受物理主机限制
这个动态控制需求大于你实际物理主机的情况下肯定会报oom了,

显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯
定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器
寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx
等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括
物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

二 JVM参数设置

  • -Xms设置堆的最小空间大小。
  • -Xmx设置堆的最大空间大小。
  • -Xmn:设置年轻代大小
  • -XX:NewSize 设置新生代最小空间大小。
  • -XX:MaxNewSize 设置新生代最大空间大小。
  • -XX:PermSize 设置永久代最小空间大小。
  • -XX:MaxPermSize 设置永久代最大空间大小。
  • -Xss设置每个线程的堆栈大小
  • -XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
  • -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

Xmx3550m:设置 JVM 最大可用内存为3550M。
-Xms3550m:设置 JVM 促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小+年老代大小+持久代大小。
持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值
对系统性能影响较大,官方推荐配置为整个堆的3/8。-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前
每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统
对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000
左右。

参考文章书

个人笔记,不同意见,望有交流
有的直接可以点击跳转连接

作者 : 爪哇教育

深入理解Java虚拟机: 作者:周志明


  1. 《Java 虚拟机规范》中的原文:The heap is the runtime data area from which memory for all class instances and arrays is allocated。 ↩︎

  2. 指新生代(其中又包含一个Eden和两个Survivor)、老年代这种划分,源自UC Berkeley在20世纪80年代中期开发的Berkeley Smalltalk。历史上有多款虚拟机采用了这种设计,包括HotSpot和它的前身Self 和Strongtalk虚拟机(见第1章),原始论文是:https://dl.acm.org/citation.cfm?id=808261↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值