JVM笔记--内存区域

                               JVM笔记--内存区域

(推荐书籍------《深入理解Java虚拟机》)

1. GC

A. GCJAVA提供的可以自动检测对象是否超过其作用域从而自动的进行内存回收。

B. 垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。


2. 内存区域的划分

1. 行时数据区,可以细分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器。方法区和堆是所有线程共享的,所有线程都可以访问,而虚拟机栈、本地方法栈、程序计数器是线程隔离的,每个线程有自己独立的区域,线程之间是不共享的。

2. 程序计数器:

相当于一个程序执行过程中的行号指示器,类似于操作系统中的ip,指向当前执行的虚拟机字节码地址。如果执行的是Java方法,计数器就记录者正在执行的虚拟机字节码指令的地址。如果是native 方法,计数器为空


3. 虚拟机栈:虚拟机栈就是java方法的内存模型,每一个线程在执行时会有自己的一个虚拟机栈,在运行过程中把所调用方法封装为一个栈帧,然后将栈帧存放在栈里面。栈帧包含了一个方法执行时的相关信息,包括方法用到的局部变量,操作数,动态链接等。

StackOverflowError(方法调用层次太深,内存不够新建栈帧)

OutOfMemoryError(线程太多,内存不够新建线程)


4. 本地栈: 类似于虚拟机栈,只不过他存放的是Native方法。

5. Native方法指的是引用非java语言的接口或者库的方法


5 堆:堆是相对来说占内存最大的一块,用来存放所有线程创建的类的对象实例。方法调用中如果创建了对象,会把这个对象实例存放在堆,然后将对于这个对象的引用存放在栈中,这样就可以方法对象了。对于内存的回收,也就是对堆内存的回收了。

 

(注意:java7中常量池移到堆中)


6方法区:存放虚拟机加载的类的信息和一些常量、静态变量等,这些内容一般是不可变的。

 

JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。但永久代仍存在于JDK1.7中,并没完全移除。 
JDK 8.HotSpot JVM使用本地化的内存存放类的元数据,这个空间叫做元空间(Metaspace)。官方定义:”In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace”。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:-XX:MetaspaceSize-XX:MaxMetaspaceSize 


3. 内存溢出异常

OOMStackOverFlow就是在运行时数据区出现的。前面说了,虚拟机栈会把每次调用的方法作封装为一个栈帧存起来。这些栈帧肯定是要占内存的,而栈的内存也是有限的。如果栈帧很多一直没有释放,这时候又来了一个栈帧,这个栈帧已经没有空间可以容纳了,有两种情况。如果这种虚拟机栈不支持动态扩展,那么将会抛出StackOverFlow异常。如果支持动态扩展,那么这个栈会请求再扩展部分空间。当然内存不是无穷的,如果频繁的扩展内存,以至于无法再继续扩展了,这时候会抛出OutOfMemory异常。

 

除此之外,堆得空间也是有限的。由于创建的对象都是要在堆中分配内存,那么如果堆中空间不足,没有足够的内存空间用来给新的对象分配内存,这时候也会抛出OutOfMemory异常。


4. 对象的创建

.检测

JVM(本文特指HotSpot)遇到new指令时,先检查指令参数是否能在常量池中定位到一个类的符号引用:

(A)、如果能定位到,检查这个符号引用代表的类是否已被加载、解析和初始化过;

(B)、如果不能定位到,或没有检查到,就先执行相应的类加载过程;

 

 

二. 分配内存

1. 对象所需内存的大小在类加载完成后便完全确定(JVM可以通过普通Java对象的类元数据信息确定对象大小);  

2. 分配方式

I)、指针碰撞

 如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器;

分配内存只需向空闲那边移动指针,这种分配方式称为"指针碰撞"Bump the Pointer);

II)、空闲列表

如果Java堆不是规整的:用过的和空闲的内存相互交错;

需要维护一个列表,记录哪些内存可用;

分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为"空闲列表"Free List);

Java堆是否规整由JVM采用的垃圾收集器是否带有压缩功能决定的;

所以,使用SerialParNew等带Compact过程的收集器时,JVM采用指针碰撞方式分配内存;而使用CMS这种基于标记-清除(Mark-Sweep)算法的收集器时,采用空闲列表方式;

 

.分配完内存,之后会将这个对象的实例字段初始化为零值。最后,会对对象进行一些设置,比如设置哈希码,分代年龄信息,这个对象属于哪个类之类的。

 


3. 回收的对象的选择

1. 虚拟机回收对象的方法--------常见的有引用计数法和可达性分析算法。

2. (大多数用可达性分析算法)----将一些特定的对象作为GC Roots,然后从这个节点向下寻找对其他对象的引用。如果一个对象到GC Roots没有引用链,那么就可以被回收了。

GC Roots的对象有:

· 虚拟机栈中引用的对象

· 方法区中 静态属性引用的对象

· 方法区中 常量引用的对象

· JNI引用的对象

我们日常开发过程中遇到的内存泄漏,很大一部分原因就是本该被回收的对象无意之中被GC Roots引用到了,比如写的static这样的静态字段引用的对象,这样他就不会被回收了

3. 引用计数法----思路就是为每一个对象设一个值,用来计算被引用的次数。只要有一个对于对象的引用存在,就让这个数字加一。这样如果一个对象没有任何引用,那么引用计数为零,这个对象就会被标记为“可回收”。但是这样有一个很严重的bug,那就是如果我有两个对象,已经不再使用,但是他们互相引用,那么他们的引用计数就永远不会为零,那么就不会被回收。

 

4. 对象回收的算法

1. 垃圾回收算法常见的有标记-清除法,标记-整理法,复制算法,分代收集等。现在的虚拟机基本上都是采用以分代收集为基础,搭配其他算法一起合作完成的。

2. 具体:根据对象的生存周期对内存划分为新生代 老生代,在新生代中因为每次都会有大量对象被回收,比较频繁,因此采用了复制算法。而老生代相对来说回收的对象少,没那么频繁,而且对象普遍比较大,因此采用了标记-清楚或标记-整理算法。编程思想p91


5. 对象的分代

6.

1对象回收的过程----finalize的生命周期

1. 当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象复活

2.变迁说明:

1. 新建对象首先处于[reachable, unfinalized]状态(A)

2. 随着程序的运行,一些引用关系会消失,导致状态变迁,从reachable状态变迁到f-reachable(B, C, D)unreachable(E, F)状态

3. JVM检测到处于unfinalized状态的对象变成f-reachableunreachableJVM会将其标记为finalizable状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为f-reachable(H)

4. 在某个时刻,JVM取出某个finalizable对象,将其标记为finalized并在某个线程中执行其finalize方法。由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(KJ)。该动作将影响某些其他对象从f-reachable状态重新回到reachable状态(L, M, N)

5. 处于finalizable状态的对象不能同时是unreahable的,由第4点可知,将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。这也是图中只有八个状态点的原因

6. 程序员手动调用finalize方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象复活也是如此。程序员手动调用多少次不影响JVM的行为

7. JVM检测到finalized状态的对象变成unreachable,回收其内存(I)

8. 若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O

9. 注:System.runFinalizersOnExit()等方法可以使对象即使处于reachable状态,JVM仍对其执行finalize方法

 

2. 对象周期图

 


发布了14 篇原创文章 · 获赞 3 · 访问量 1496

猜你喜欢

转载自blog.csdn.net/qq_41008202/article/details/79931552
今日推荐