JVM架构
JVM = 类加载器(classloader) + 执行引擎(execution engine) + 运行时数据区(runtime data area)
jvm架构.jpg 运行时数据区.jpg
内存分类
按空间划分为两类:堆内存(Heap) 、非堆内存(NonHeap)
堆内存(Heap)
堆内存.jpg- 堆是Java 虚拟机所管理的内存中最大的一块,默认为物理内存1/4大小
- 堆是被所有线程共享的区域,在虚拟机启动时创建
- 堆里面存放的都是对象的实例(new 出来的对象都存在堆中)
- 我们平常所说的垃圾回收,主要回收的就是堆区
- 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2
- 默认的,Eden : From : To = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 )
Thread Local Allocation Buffer (TLAB,线程本地分配缓冲区)
- 占用 Eden 区(缺省 Eden 的1%),默认开启,线程私有
- 优化多线程堆空间分配对象指针碰撞问题
非堆内存(NonHeap)
非堆内存.jpgMetaspace
- JAVA8以后使用Metaspace替代了PermGen(永久代),将元数据移动到了非堆内存中。默认为物理内存的1/64大小
- 当一个类被加载时,它的类加载器会负责在 Metaspace 中分配空间用于存放这个类的元数据
- 只有当这个类加载器加载的所有类都没有存活的对象,并且没有到达这些类和类加载器的引用时,相应的 Metaspace 空间才会被 GC 释放
- 释放 Metaspace 的空间,并不意味着将这部分空间还给系统内存,这部分空间通常会被 JVM 保留下来
- Metaspace 可能在两种情况下触发 GC:Metaspace需要扩容时、达到MaxMetaspaceSize时,所以通常把-XX:MetaspaceSize和-XX:MaxMetaspaceSize设置为相同的值(Metaspace默认大小只有21MB)
Compressed Class Space
- 压缩指针,指的是在 64 位的机器上,使用 32 位的指针来访问数据(堆中的对象或 Metaspace 中的元数据)的一种方式
- 它需要被分配一个连续的地址空间,因此在GC后释放的空间会被JVM保留,一定不会还给物理内存
- 默认大小是1G,但如果设置了-XX:MaxMetaspaceSize,则它的大小不会超过MaxMetaspaceSize
Direct Memory
- NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作
- 可以帮助JAVA实现零拷贝
- 默认大小为堆内存大小,可通过-XX:MaxDirectMemorySize设置
- 不会被GC,使用时要注意释放
线程共享与私有
线程共享与私有.jpg堆外内存使用指标
used
、capacity
、committed
、reserved
-
committed、reserved
committed
和reserved
并不纯粹是JVM的概念,它和操作系统相关。
reserved
是指,操作系统已经为该进程“保留”的。所谓的保留,更加接近一种记账的概念,就是操作系统承诺说一大块连续的内存已经是你这个进程的了。那么实际上这一大块内存有没有真实对应的物理内存,这时是不知道的,等进程committed
的时候,进程真的要用这个连续地址空间的时候,操作系统才会分配真正的内存。所以,这也就是意味着,这个过程会失败。 -
used、capacity
比如创建了一个可以存放20个元素的ArrayList
,但是我实际上只放了10个元素,那么capacity
就是20,而size就是10,这里的size和used
就是一个概念。
Netty直接内存溢出
- 如果有手工分配了DataBuffer但是没有释放的时候,Netty会报警
ByteBuf.release()
没有调用。 - 如果直接内存被占满无法继续分配到内存空间,会报出
OutOfDirectMemoryError
的异常,大量的请求无法获取内存会导致应用挂掉,无法访问任何接口。
常见调优参数
参数名称 | 含义 | 默认值 | 说明 |
---|---|---|---|
-Xms | 初始堆大小 | 物理内存的1/64 | 默认空余堆内存小于40%时(MinHeapFreeRatio),堆增大到-Xmx的值 |
-Xmx | 最大堆大小 | 物理内存的1/4 | 默认空余堆内存大于70%时(MaxHeapFreeRatio),堆减小到-Xms的值 |
-Xmn | 年轻代大小 | 堆内存的1/3 | 此处的大小是eden+ 2 survivor space,增大年轻代后,将会减小年老代大小 |
-Xss | 每个线程的堆栈大小 | 1M | |
-XX:NewRatio | 年轻代与年老代的比值 | 2 | -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5,当Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置 |
-XX:MetaspaceSize | 元空间初始大小 | 21M | 元空间的大小达到这个值时,会触发Full GC并会卸载没有用的类 |
-XX:MaxMetaspaceSize | 元空间最大可分配大小 | 物理内存的1/64 | |
-XX:MaxDirectMemorySize | 直接内存最大可分配大小 | 与堆内存相同 |
- 应用可分配的最大内存
[-Xmx] + 线程数量*[-Xss] + [-XX:MaxMetaspaceSize] + [-XX:MaxDirectMemorySize]
垃圾回收(GC)
有不少人把这项技术当作Java语言的伴生产物。事实上,垃圾收集的历史远远比Java久远,在1960年诞生于麻省理工学院的Lisp是第一门开始使用内存动态分配和垃圾收集技术的语言。
GC重点
- 程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,因此这几个区域的内存分配和回收都具备确定性,不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。
- 而Java堆和方法区(元数据区)这两个区域则有着很显著的不确定性,只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理。
可达性分析法
可达性分析法.jpg当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。
在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器
- 所有被同步锁(synchronized关键字)持有的对象
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
分代收集理论
当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”(Generational Collection)[1]的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:
- 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的
- 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡
这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:
- 收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储
在一次次只局限于新生代区域内的收集中,新生代中的对象是完全有可能被老年代所引用的,因此对分代收集理论添加第三条经验法则:
- 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数
不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构记忆集(Remembered Set),把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。
各阶段GC名词:
- 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集
- 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。Major GC在不同的资料上常有不同所指,需要按照上下文区分到底是指老年代收集还是整堆收集
- 整堆收集(Full GC):收集整个Java堆和方法区(元数据区)的垃圾收集。
网友评论