目录
JVM Memory
Java虚拟机在Java程序运行过程中会把它管理的内存,划分为若干个不同的数据区域。这些区域各自的用途,以及创建和销毁的时间,有的区域随虚拟机的启动而存在,有些区域依赖用户线程的启动和结束而建立和销毁。
运行时JVM数据区
-
程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。(每个线程都有自己的程序计数器,线程隔离)。
-
JAVA虚拟机栈
它描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame )用于存储局部变量表、操作栈、动态链接、方法出口等信息。线程私有(线程隔离)。
栈空间随着线程的开始而开始,线程的终结而终结。
线程之间不会有局部变量的数据安全问题。
-
本地方法栈(线程私有)
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
-
JAVA堆
此内存区域的唯一目的就是存放对象实例,一个JVM实例只存在一个堆,堆内存的大小是可以调节的。堆内存是线程共享的。
-
方法区
方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量等数据。
GC
内存管理方式
内存管理管理的方式通常有两种
-
显式的内存管理(C/C++)
内存管理(内存的申请和释放)是程序开发者的职责。
常见问题:-
内存泄漏:内存空间已经申请,使用完毕后未主动释放。
-
野指针:使用了一个指针,但是该指针指向的内存空间已经被free。
-
-
隐式的内存管理(Java/C#)
内存的管理是由垃圾回收器自动管理的(内存的回收)
- 优点:增加了程序的可靠性(和野指针),减小了memory leak。
- 缺点:无法控制GC的时间,耗费系统性能。
GC——Garbage Collection
在JVM中,GC的功能是由垃圾回收器来完成。
研究GC,就必须要回答下面3个问题:
- 如何确定“垃圾”
- 如何回收垃圾
- 何时触发GC
如何确定垃圾
一个对象何时会成为垃圾?——如果一个对象我们再也访问不到了,那么该对象就变成垃圾了。由于对于对象的访问,总是间接的,总是需要通过应用变量才能访问的,那就意味着,当没有任何一个(有效的)引用变量指向该对象的时候,该对象变成垃圾。
两种方法。
引用计数法
核心依据就看该对象是否还有引用变量引用它。
-
给对象添加一个引用计数器
引用计数器:记录当前jvm中有多少个引用变量指向该对象(是一个整数)。
-
每当一个地方引用它时,计数器加1
-
每当引用失效时,计数器减少1
-
当计数器的数值为0时,也就是对象无法被引用时,表明对象不可在使用
缺点:无法解决循环引用的问题。商用JVM一般不使用引用计数法。
根搜索算法
核心依据就看该对象能否被访问到。
-
这个算法的基本思想是将一系列称为“GC Roots”的对象作为起始点。
GC Roots是一个对象集合,包含:
- 虚拟机栈中引用的对象——通过栈引用一定可以访问到的对象。
- 方法区中的静态属性引用的对象。类名.静态变量。静态引用变量指向的对象,我们一定可以访问到。
简单来讲GC Roots就是一定可以访问到的对象的集合。
-
从这些节点(沿着引用链)开始向下搜索。
-
搜索所走的路径称为引用链
-
当一个对象到所有的GC root之间没有任何引用链相连,此时,就认为该对象变成了垃圾
如何回收垃圾
标记清除算法(Mark Sweep)
优点:实现简单。
缺点:容易产生内存碎片。
标记复制算法(Copy)
- 堆内存被平均分成两块。
- 每次做内存分配,只使用其中一块,另一块保留内存不用来分配。该保留内存在垃圾回收的时候使用。
- 内存回收的时候,复制算法会将用来分配的那一半内存的存活对象都一次复制到保留内存中,依次挨个存放。(在一次回收过后,没有内存碎片)。然后刚刚用来分配的那一块内存,一次整个回收掉。
- 于是在下一次内存回收之前,刚刚被回收掉的原本用来分配内存的存储区域就变成了新的保留内存,而刚刚的保留内存就用来分配内存。
优点:
- 一次内存回收过后,没有内存碎片;
- 当一次堆内存回收过程当中,如果存活对象很少,即需要复制的对象很少,而且又因为大片的连续存储空间的回收效率极高。
缺点:
- 内存利用率低,每次只用一般内存来分配;
- 如果每次存活的对象很多,复制就会比较多,效率就不是很高了。
标记整理算法(Mark Compact)
拿时间换空间,空间利用率百分百,但是需要花时间整理存活对象并且整理移动位置。
分代收集算法(Generational Collection 商用)
-
这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块
-
一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最合适的收集算法。
-
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
98%的对象,朝生夕死。
-
而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-整理”算法进行回收。
对象的年龄:一个对象,在新生代当中每经历一次垃圾回收都存活下来,该对象的年龄就增长一岁。如果一个对象在新生代中,连续经历15此垃圾回收仍然存活,就进入老年代。
GC相关概念
-
Shallow size
就是对象本身占用的内存大小,也就是对象头加成员变量占用内存大小的总和。
-
Retained size
是该对象自己的shallow size 加上仅可以从该对象访问(直接或者间接访问)的对象的shallow size之和。
Retained size是该对象被GC之后所能回收的内存的总和。
GC触发的时机
- 申请heap space失败后会触发GC回收
- 系统进入idle后一段时间会进行回收(系统空闲)
- 主动调用GC进行回收 System.gc()
内存相关问题
Out of Memory case
Heap OOM 堆溢出
Stack Overflow 栈溢出
内存泄漏(Memory Leak)
是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
内存泄露与内存溢出联系
内存泄露可能导致内存溢出,但不是必然导致内存溢出。