Java 虚拟机 垃圾回收机制,堆内存分代存储,类的加载与卸载

==============================================

                                    1:内存模型

==============================================

线程共享区域:堆(heap)和 方法区(Method Area)

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

堆(heap):new 出来的对象存放位置(存放过大,会导致内存溢出)

方法区:存放静态属性和常量那些(存放过大,会导致内存溢出)

程序计数器:可以理解为记录当前线程 执行到哪一行或哪一个指令。以便线程切换回来时,可以从当前指令继续执行。

java虚拟机盏:存放栈帧,可以理解每调用一个方法,就有一个栈帧放进虚拟机盏中,压栈太多会导致栈溢出(也就是方法调用的层级太深)。

本地方法栈:和虚拟机盏类似,但这个栈是用来存放 Native方法时的栈帧。

==============================================

                                    2:内存模块划分

==============================================

1:回收算法----  分代收集算法

        【可达性分析算法】标记对象是否需要被回收的算法

        对象引用都是以引用链为路径互相引用。如果一个对象的引用链没有连接上任何 根节点(GC Roots ),那就说明这个对象是没用的,是需要被回收的。所谓的根节点引用,就是:静态属性,常量属性,栈帧引用。 还有 Native方法引用的变量(千万不要说成员变量)的引用。如果没有这四个的引用,即代表这个对象是没用的。

       

         把对象的存活周期分为:新生代老年代

-------新生代 采用 复制算法。(针对对象存活率低使用的算法)

-------------------- 复制算法 :将内存一分为二,分为两个同样大小的区域。每次只用一个区域,等这个区域接近使用完了,会把这个区域的存活对象(可达性分析算法)全部复制到另一个区域,然后把这半个区域全部清空。每次都是对整个半区域进行回收。

                                            缺点:每次只能用半个内存区域,内存空间使用率太低了

                                            优点:效率高,每次只回收半个区域。不用考虑内存碎片的问题,因为复制过后会重新排列,剩余内

                                                       存空间都是完整连续排列的

-------老年代 采用   标记-清除算法   或  标记-整理 算法  (针对对象存活率高使用的算法)

--------------------标记-清除 算法:通过可达性分析算法标记需要回收的对象,然后标记后的对象会被GC线程去回收。

                                           缺点1> 效率问题,标记和效率的清楚都不高

                                           缺点2> 空间问题,清除后会产生大量不连续的内存碎片。如果后续要给一个大的对象分配内存空间,这些内存碎片都不足以有足够的空间时,会再次出发GC进行回收。                                      

--------------------标记-整理 算法:通过可达性分析算法标记需要回收的对象,然后标记后的对象会被GC线程去回收。是《标记清除算法》一个加强版。不直接对可回收对象进行清理,而是让存活的对象往一边移动,让所有的存活对象在内存空间里连续排序。然后直接清理掉边界外的无用对象。

                                           优点1> 解决了内存碎片的问题

                                         

---------------------【标记-清除 算法】 示例图

---------------------【标记-整理 算法】 示例图

 

2:垃圾回收器--回收机制

垃圾回收器扫到了需要被回收的对象,且这个对象的finalize() 从来没被调用过,会调用对象的 finalize()。如果在finalize()对象重新和根节点连接上(比如:把对象赋值给一个静态变量),那这个时候不会回收这个对象。但如果,对象的finalize() 方法已经被调用过,就不会再调用finalize(),直接就回收了这个对象。

3:内存分配和回收策略

1:分区

新生对象内存分配分为:Eden > Survivor >  老年区    优先级依次递减

新生区:Eden 和 Survivor    (复制回收算法)

老年区:老年区                    (标记-整理回收算法)

注意:并不是所有对象都会先在新生区存储的,比如一个对象特别大,就会直接进入 老年区 

所以,避免大对象的创建,会提前触发垃圾回收器去回收垃圾,以提供足够连续的空间去安置大对象。

(java虚拟机可以通过参数设定 大对象的  界限)。超过直接放进老年区。

2:对象在内存区域的移动

<规则1>    年龄计数器,每被垃圾回收器扫过一次,年龄就+1。Eden被扫过一次,存活下来就会移到Survivor区域,Survivor区域被扫到15次还存活就会移动老年区。当然,这些界限的参数是可以控制的。

<规则2>    年龄的判断规则不是唯一的。如果Survivor区域所有同龄对象所占内存之和  大于 Survivor空间的一半,那么大于这个年龄的对象都会被移到老年区域。

<规则3>

1>   对象刚开始只会存在于Eden区和Survivor 的 From区,Survivor的“To”是空的。

2>   紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到年龄阈值的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。

3>   Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

==============================================

                                    2:类加载

==============================================

类的加载分为五大步:

1:加载

1:获取此类的二进制流(分别可以从:zip,网络(applet),运行时才生成(动态代理))

2:将这个流转成方法去运行时的数据结构

3:生成这个类的Class对象,作为此类在方法区的数据访问接口

2:连接

              细分:验证,准备,解析

验证: 验证编码是否符合UTF-8,常量类型是否支持等等这些

准备: 分配内存,初始化变量

解析: 虚拟机将常量池符号引用替换为直接引用

3:初始化】   

类加载最后一步

4:使用

使用就是使用对象的属性

5:卸载

卸载,可以理解为引用进程被杀死

类与类加载器的关系

比较两个类是否相等,只有在这两个类都在同一个类加载器加载的前提下才有意义。否则,即使这两个类来源于同一个class文件,在同一个虚拟机加载,只要加载它们的加载器不一样,那么这两个类必然不相等。

Java判断一个类是否相等(equals()方法,isAssignableFrom()方法,isInstance()方法返回结果)

从Java虚拟机角度看,只存在2种类加载器:

1:启动 类加载器

2:其他 类加载器

而Java设计者推荐给开发者的类加载机制是:双亲委派模型         如下图:


上图展示的就是双亲委派模型  除了顶端的 启动类加载器外,其他的加载器都有自己的父类加载器。这里的父子关系一般不是集成的关系,而是通过使用组合关系来服用父加载器的代码。

双亲委派模型的工作过程

        如果一个类加载器接收到加载请求,它会先把这个请求抛给父类去完成,每一层级的加载器都是如此,因此所有的请求最终都会传递到顶端的 启动类加载器去,只有在父类加载器反馈自己无法完成加载任务时,子类加载器才会尝试自己去加载。

好处

         这种加载机制具备了一种优先级的层级关系。当去尝试加载一个类时,最终都会传递到顶端的启动类加载器去,这就保证类在程序中的唯一性了。举个例子:比如要加载java.lang.String,在存放在rt.jar之中,最终都是会委派到顶端的启动类加载器去完成加载任务,因此String类在程序里面各个类加载器环境中都是同一个类。相反,如果没有双亲委派模型,每个类加载器各自去加载一个类,就拿java.lang.String来说,每个类加载器去加载String类,那么程序中会出现多个不同的String类,就会非常混乱,java程序中的最基本的行为就无法保证了。

猜你喜欢

转载自blog.csdn.net/Leo_Liang_jie/article/details/90379533