源代码 .Java文件编译成 . Class 文件,然后进入到类加载器Class Loader,最后由类加载器加载到JVM

双亲委派机制:保证安全的机制

           1.类加载器收到类加载的请求

           2.将这个请求向上委托给父类加载器去完成,一直向上委托,知道启动类加载器

             3.启动加载器检查是否能够启动这个类,能加载就结束,使用当前的类加载器,否则抛出异常,通知子类加载器进行加载

             4.重复步骤   3

          中心思想就是 自身的类不进行加载,让自身类的父类进行加载,这样就会到顶层的类,最后都会到启动类加载器,如果启动类加载器没有这个类不能进行加载,然后再一步一步地向下加载

          这样一来,如果用户代码中自定义了一个全包名和自带的类一样,比如Java.lang.String 的类,就会加载Java本身的String类,而不是自定义的类。

          这样做是为了避免出现过多的重复字节码,避免原始类被覆盖的问题

 

如何打破双亲委派机制

        1.在自定义的类加载器中重写loadClass方法,因为ClassLoader中有一段双亲委派的核心代码,用来不断地找类加载器的父类加载器,直到父类加载器为空,如果重写这段代码就可打破机制

        2.使用线程上下文类加载器

          

Java中的类加载器

Bootstrap ClassLoader (启动类加载器)

Bootstrap ClassLoader,启动类加载,默认加载的是jdk\lib目录下jar中诸多类;

这个路径可以使用 -Xbootclasspath参数指定。

Extension ClassLoader (扩展类加载器)

Extension ClassLoader,扩展类加载器,默认加载jdk\lib\ext\目录下jar中诸多类;

这个路径可以使用 java.ext.dirs系统变量来更改。

Application ClassLoader (应用程序类加载器)

Application ClassLoader,应用程序类加载器,负责加载开发人员所编写的诸多类。

User ClassLoader (自定义类加载器)

自定义类加载器,当存在上述类加载器解决不了的特殊情况,或存在特殊要求时,可以自行实现类加载逻辑。

                              

  

 

Native关键字

      1.凡是带了native关键字的方法,都是Java的作用范围达不到了,要调用底层的C语言的库

        2.被native修饰的方法都会走本地方法栈

        3.调用本地方法接口JNI

        4.JNI的作用:扩展Java的使用,融合不同的编程语言为Java所用,Java诞生的时候C和C++横行,想要立足就必须要有C和C++的方法

        5.它在内存区域中专门开辟了一快标记区域:Native Method Area  本地方法栈,登记 Native 方法

        6.在最终执行时候,通过JNI加载本地方法库中的方法

 

方法区  Method Area

        1.静态变量,常量,类信息,(构造方法,接口定义)、运行时常量池存在方法区中,但是,实例变量存在堆内存中,与方法区无关

          Static   Final  Class模板  常量池

栈 Stack

        1.栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存也就释放,所以不存在垃圾回收

        2.栈 满了之后就会报  StackOverFlowError   栈内存溢出错误

        3.栈 +  堆  +  方法区 的交互关系   对象实例化的过程

                      

 

                                                      

 

 

堆 Heap

        Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的

        类加载器读取文件之后,一般会把什么东西放到堆中?类、方法、常量、变量,保存我们所有引用类型的真实对象

        堆内存还要细分为三个区域  新生区、养老区、永久区 , GC垃圾回收主要发生在伊甸园区和养老区

        假设堆内存满了,OOM,就会 发生堆内存溢出错误

        jdk 1.8 之后永久存储区改名为元空间,并且本质有一些不同

 

 

        新生区 :类诞生和成长的地方,甚至是死亡的地方

            其中分为两个部分,伊甸园区  所有的对象都是在伊甸园区new 出来的   和  幸存者区   又分为   0  和  1区(from  和 to)

 

 

 

 

      元空间:这个区域常驻内存的,方法区也就是位于这一部分,用于存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行的一些环境或者类信息,这个区域不存在垃圾回收!当JVM关闭的时候也就释放这个区域的内存。

        一个启动类,如果加载了大量的第三方jar包,或者Tomcat部署了太多的应用,带量动态生成反射类,不断地被加载,知道内存满,就会导致OOM

        元空间的发展历史,在JDK 1.6以及之前的时候叫做永久代,常量池在方法区

                 JDK 1.7   也叫做永久代,但是慢慢的退化,并且已经有了去永久代的想法,常量池在堆中

                 JDK 1.8    没有永久代,取而代之的是元空间,常量池也在元空间中

 

下图中所展示的结构,元空间逻辑上是存在的,也是堆的一部分,但是物理上是不存在的,

         

 

GC 垃圾回收

      JVM在进行GC的时候,并不是对这三个区域统一回收。大部分的回收都是新生代

 

       GC算法  :  标记清除算法、标记压缩法、复制算法、引用计数器 

 

             引用计数器方法,为每一个对象都创建一个引用计数器,每当对象被引用一次,计数器就加一次,最后根据引用次数,清除垃圾,因为每个计数器也有消耗,同时性能很差,所以几乎不用

                                                                         

      1.标记清除算法  : 回收时扫描这些对象,并对活着的对象进行标记,然后清除,对没有标记的对象进行清除

       优点:不需要额外的空间!

       缺点:两次扫描,严重浪费时间,会产生记忆碎片

                  

 

        2.标记压缩算法 : 是对标记清除算法的优化,在标记清除的基础上,为了防止产生记忆碎片,再次扫描,向一端移动存活的对象,多了一个移动成本

                

 

        3.复制算法 :  谁空谁是to   幸存0区和幸存1区 之间的from  和  to  的关系是随着多次GC的过程而不断交换的,简单理解为谁空谁是to

              1.每次GC 都会将Eden活的对象移到幸存区中,一旦Eden被GC后就会变成空的

              2.当一个对象经历了15次的GC都还没有死的情况下,就会进入到老年代(养老区)

               优点:没有内存碎片

               缺点:浪费了内存空间,有一般空间永远是空 的  to。假设对象100%存活,成本比较高

              最佳使用场景:对象存活率低,也就是新生代

         

 

 

 

       算法总结:内存效率  : 复制算法 > 标记清除算法  > 标记压缩算法

           内存整齐度  :   复制算法  =  标记压缩   >  标记清除

           内存利用率从   :  标记压缩  =  标记清除  >  复制算法

        没有最好的算法,只有最合适算法   

 

 

      

 

posted on 2022-04-09 20:09  迎新  阅读(36)  评论(0编辑  收藏  举报