深入浅出JVM(jvm内存结构,类加载器图,双亲委托模式,堆内存,GC解析,GC算法)

版权声明:Please make the source marked https://blog.csdn.net/qq_31807385/article/details/84034106

目录

Java虚拟机的内存结构:

 类加载器图:

双亲委托模式:

堆内存:

GC解析图:

GC算法


Java虚拟机的内存结构:

 类加载器图:

双亲委托模式:

Java允许创建和JDK自带类库相同名称类
但是JVM不会加载我们自己定义的类,而是加载JDK提供的类
JVM的实现方式为:双亲委派机制
    1) 如果应用类加载器加载一个类,不是马上加载
    2)委托父加载器(扩展类加载器)加载
    3)扩展类加载器加载类的时候,也不是马上加载
    4) 委托父加载器(启动类加载器)加载
    5)启动类加载器开始加载这个类,如果加载成功,那么直接使用。
    6)如果加载失败,那么会返回到扩展类加载器,由扩展类加载器加载这个类
    7) 扩展类加载器开始加载这个类,如果加载成功,那么直接使用。
    8)如果加载失败,会抛出异常给子加载器(应用类加载器)
    9)应用类加载器捕捉异常后开始加载这个类,如果加载成功,那么直接使用。
    10)如果加载失败,会将异常抛出给JVM

JVM自身提供了3个类加载器,每一个类加载器会加载不同位置的类
 1)启动类加载器:加载JDK核心类库,由C++语言实现,
      加载位置: $JRE_HOME/lib
 2)扩展类加载器:是java类,可以加载JDK扩展类库
      加载位置: $JRE_HOME/lib/ext
 3)应用类加载器:是java类,可以加载环境变量classpath中的类
      加载位置: $classpath
    JAVA_HOME, PATH, CLASSPATH

堆内存:

 

GC解析图:

GC算法

 内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。 内存整齐度:复制算法=标记整理算法>标记清除算法。 
内存利用率:标记整理算法=标记清除算法>复制算法。 
 
可以看出,效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面所提到的三个指标,标记/整理算法相对来说更平滑一些,但效率上依然不尽如人意,它比复制算法多了一个标记的阶段,又比标记/清除多了一个整理内存的过程

年轻代(Young Gen)  ----->复制算法的
 
年轻代特点是区域相对老年代较小,对象存活率低。这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对像大小有关,因而很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
 
老年代(Tenure Gen) ----> 标记清除或者是标记清除与标记整理的混合实现
 
老年代的特点是区域较大,对象存活率高。这种情况,存在大量存活率高的对像,复制算法明显变得不合适。一般是由标记清除或者是标记清除与标记整理的混合实现。
 
Mark阶段(标记)的开销与存活对像的数量成正比,这点上说来,对于老年代,标记清除或者标记整理有一些不符,但可以通过多核/线程利用,对并发、并行的形式提标记效率。
 
Sweep阶段(清除)的开销与所管理区域的大小形正相关,但Sweep“就地处决”的特点,回收的过程没有对像的移动。使其相对其它有对像移动步骤的回收算法,仍然是效率最好的。但是需要解决内存碎片问题。
 
Compact阶段(整理)的开销与存活对像的数据成开比,如上一条所描述,对于大量对像的移动是很大开销的,做为老年代的第一选择并不合适。
 
以hotspot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,对于对像的回收效率很高,而对于碎片问题,CMS采用基于Mark-Compact算法的Serial Old回收器做为补偿措施:当内存回收不佳(碎片导致的Concurrent Mode Failure时),将采用Serial Old执行Full GC以达到对老年代内存的整理。 从JDK1.9后不再推荐使用CMS垃圾回收器, 推荐采用 Garbage-First(G1)垃圾回收器, Garbage-First(G1)是专门针对多核处理器的,虽然是整理,但是非常是多核完成的,因此较快。

一个case(说明引用计数法存在的问题):

package hello_java;
public class Test4 {
    public static void main(String[] args) {
        test();
        System.gc();// full gc
        System.out.println( "main finish" );
    }
    public static void test() {
        A a = new A();
        B b = new B();
        a.b = b;
        b.a = a;
    }
}
class B {
    public A a= null;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(  "B被回收了");
    }
}
class A {
    public B b = null;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(  "A被回收了");
    }

}

打印结果:

main finish
B被回收了
A被回收了

解释:

Test方法出栈,gc回收、

稍作修改:

package hello_java;
public class Test4 {
    public static void main(String[] args) {
        A a = test();
        System.gc();// full gc
        System.out.println( "main finish" );
    }
    public static A test() {
        A a = new A();
        B b = new B();
        a.b = b;
        b.a = a;
        return a;
    }
}
class B {
    public A a= null;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(  "B被回收了");
    }
}
class A {
    public B b = null;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(  "A被回收了");
    }

}

main finish

解释:

main方法中有指向对象A的,不会被回收。


 

猜你喜欢

转载自blog.csdn.net/qq_31807385/article/details/84034106