面试直击:一文带你复习java--jvm篇

目录

系列文章目录

 一.JVM相关概念介绍

        1.jvm,jre,jdk之间的关系    

        2.什么是java虚拟机

        3.虚拟机关系

二.JVM内存结构

        1.Java虚拟机栈:

        2.本地⽅法栈:

       3. 程序计数器:

       4. 堆:

       5.方法区 :

       6.运行时常量池:

       7.直接内存:

三.关于java对象

      1.java创建对象的过程

      2.JVM给对象分配内存的策略:

      3.java对象内存分配是如何保证线程安全的:

      4.对象的内存布局:

      5.如何判断对象是否是垃圾:

四.堆内存结构与虚拟机参数

五.常见异常以及解决方案

1.堆内存溢出

2.栈内存溢出

六.JVM参数调优案例

1. ⽇均百万级订单交易系统如何设置JVM参数

七.垃圾回收

1.如何发现垃圾

1.1.引用计数算法:

1.2.根搜索算法(也叫可达性分析)

2.如何回收垃圾

     2.1标记-清除算法(mark and sweep)

    2.2标记-整理算法

3.垃圾收集器分类

简述Minor GC:

简述Full GC:

简述Serial垃圾收集器:

简述ParNew垃圾收集器 :

简述Parallel Scavenge垃圾收集器 :

简述CMS垃圾收集器 :

简述G1垃圾收集器 :

八。常见内存回收策略:

九.类加载

        类加载运⾏全过程 :

 其中loadClass的类加载过程有如下⼏步:

 类被加载到⽅法区中后主要包含:

类加载器:

类加载器初始化过程:

十.双亲委派机制

        简述双亲委派机制:

        双亲委派机制的优点:

        如何破坏双亲委派机制 :

        如何构建自定义类加载器:

十一.java引用

十二.finalize⽅法

        1、finalize()⽅法是什么?

        2、finallize()⽅法什么时候被调⽤?

        3、什么时候使⽤它?

         4、尽量避免使⽤这个函数。


系列文章目录

        一文带你复习java--java基础知识篇 

 一.JVM相关概念介绍

        1.jvm,jre,jdk之间的关系    

                JDK(Java Development Kit):是Java开发⼯具包,它是程序开发者⽤来编译、调试Java程 序,它也是Java程序,也需要JRE才能运⾏。 

                JRE(Java Runtime Environment):是Java运⾏环境,所有的Java程序都要在JRE下才能运 ⾏。

                JVM(Java Virual Machine):是Java虚拟机,它是JRE的⼀部分,⼀个虚构出来的计算机,它 ⽀持跨平台。

面试直击:一文带你复习java--jvm篇_第1张图片

        2.什么是java虚拟机

        Java Virtual Machine (JVM)虚拟机是指解释和执⾏Java字节码的程序,其中Java字节码由 Java编译器⽣成。 Java Virtual Machine(Java虚拟机),它是⼀个虚构出来的计算机,是通过在实际的计算机上 仿真模拟各种计算机功能来实现的。Java虚拟机有⾃⼰完善的硬件架构,如处理器、堆栈、寄存器 等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需⽣成在 Java虚拟机上运⾏的⽬标代码(字节码),就可以在多种平台上不加修改地运⾏。Java虚拟机在执⾏ 字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执⾏。

        面试直击:一文带你复习java--jvm篇_第2张图片

        3.虚拟机关系

            3.1、虚拟机始祖 Sun Classic/Exact VM 世界上第⼀款商⽤的虚拟机

            3.2、HotSpot VM 它是Sun/OracleJDK 和OpenJDK中的默认虚拟机,也是⽬前使⽤范围最⼴的虚拟机,但他不 是由sun公司开发,⽽是由⼀家名为“Longview Technologies”公司设计的,甚⾄这个虚拟机最初 并⾮是为java语⾔研发的,Hotspot既继承了sun公司前⾯两款虚拟机的优点(准确数内存管 理),也有了⾃⼰的新技术如:热点代码探测技术,JDK8 时移除掉了永久代,吸收了JRockit的 java Mission Control 监控⼯具等功能。

        3. 3、JRockit VM 三⼤商⽤虚拟机之⼀ JRockit是世界上最快的JVM 4、Dalvik VM Google开发的,应⽤于Android系统

二.JVM内存结构

        java官网     jvm指令

        线程私有的运行时数据区: 程序计数器、Java 虚拟机栈、本地方法栈。

        线程共享的运行时数据区:Java 堆、方法区

面试直击:一文带你复习java--jvm篇_第3张图片

        JVM内存结构⼀共分为5个区:Java虚拟机栈、本地⽅法栈、程序计数器、堆、⽅法区。

        1.Java虚拟机栈:

          Java 虚拟机栈用来描述 Java 方法执行的内存模型。线程创建时就会分配一个栈空间,线程结束后栈空 间被回收。 栈中元素用于支持虚拟机进行方法调用,每个方法在执行时都会创建一个栈帧存储方法的局部变量表、 操作数栈、动态链接和返回地址等信息

           虚拟机栈会产生两类异常:

                StackOverflowError:线程请求的栈深度大于虚拟机允许的深度抛出。                 OutOfMemoryError:如果 JVM 栈容量可以动态扩展,虚拟机栈占用内存超出抛出。

        2.本地⽅法栈:

        本地方法栈与虚拟机栈作用相似,不同的是虚拟机栈为虚拟机执行 Java 方法服务,本地方法栈为本地方法服务。可以将虚拟机栈看作普通的java函数对应的内存模型,本地方法栈看作由native关键词修饰 的函数对应的内存模型。⾥⾯并没有我们写的代码逻辑,而是存储c++的native⽅法运⾏时候的栈区。

        本地方法栈会产生两类异常:

                 StackOverflowError:线程请求的栈深度大于虚拟机允许的深度抛出。                 OutOfMemoryError:如果 JVM 栈容量可以动态扩展,虚拟机栈占用内存超出抛出。

       3. 程序计数器:

        (指向当前程序运⾏的位置)程序计数器表示当前线程所执行的字节码的行号指示器。它是⼀块很⼩的内存空间,主要⽤来记录各个线程执⾏ 的字节码的地址,例如,分⽀、循环、线程恢复等都依赖于计数器。 程序计数器不会产生StackOverflowError和OutOfMemoryError。

       4. 堆:

        堆主要作用是存放对象实例,Java 里几乎所有对象实例都在堆分配内存,因此被所有的线程共享堆也是内存管理中最大的一 块。Java的垃圾回收主要就是针对堆这一区域进行。 可通过 -Xms 和 -Xmx 设置堆的最小和最大容量。

        堆会抛出 OutOfMemoryError异常

       5.方法区 :

        方法区用于存储被虚拟机加载的类信息、常量、静态变量等数据。 JDK6之前使用永久代实现方法区,容易内存溢出。JDK7 把放在永久代的字符串常量池、静态变量等移 出,JDK8 中抛弃永久代,改用在本地内存中实现的元空间来实现方法区,把 JDK 7 中永久代内容移到 元空间。 方法区会抛出 OutOfMemoryError异常。

       6.运行时常量池:

        存放常量池表,用于存放编译器生成的各种字面量与符号引用。一般除了保存 Class 文件 中描述的符号引用外,还会把符号引用翻译的直接引用也存储在运行时常量池。除此之外,也会存放字 符串基本类型。 JDK8之前,放在方法区,大小受限于方法区。JDK8将运行时常量池存放堆中。

       7.直接内存:

         直接内存也称为堆外内存,就是把内存对象分配在JVM堆外的内存区域。这部分内存不是虚拟机管理, 而是由操作系统来管理。 Java通过通过DriectByteBuffer对其进行操作,避免了在 Java 堆和 Native堆来回复制数据。

三.关于java对象

面试直击:一文带你复习java--jvm篇_第4张图片

      1.java创建对象的过程

                1.1. 检查该指令的参数能否在常量池中定位到一个类的符号引用,并检查引用代表的类是否已被加载、 解析和初始化,如果没有就先执行类加载

                1.2. 通过检查通过后虚拟机将为新生对象分配内存。

                1.3. 完成内存分配后虚拟机将成员变量设为零值

                1.4. 设置对象头,包括哈希码、GC 信息、锁信息、对象所属类的类元信息等

                1.5. 执行 init 方法,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址 赋值给引用变量。

      2.JVM给对象分配内存的策略:

                2.1. 指针碰撞: 这种方式在内存中放一个指针作为分界指示器使用过的内存放在一边,空闲的放在另 一边,通过指针挪动完成分配

                2.2. 空闲列表: 对于 Java 堆内存不规整的情况,虚拟机必须维护一个列表记录哪些内存可用,在分配 时从列表中找到一块足够大的空间划分给对象并更新列表记录

      3.java对象内存分配是如何保证线程安全的:

                3.1. 对分配内存空间采用CAS机制配合失败重试的方式保证更新操作的原子性。该方式效率低

                3 2. 每个线程在Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块"私 有"内存中分配一般采用这种策略

      4.对象的内存布局:

         对象在堆内存的存储布局可分为对象头、实例数据和对齐填充

         对象头主要包含两部分数据: MarkWord、类型指针。MarkWord 用于存储哈希码(HashCode)、GC 分代年龄、锁状态标志位、线程持有的锁、偏向线程ID等信息。 类型指针即对象指向他的类元数据指针,如果对象是一个 Java 数组,会有一块用于记录数组长度的数 据,         实例数据存储代码中所定义的各种类型的字段信息。 、

        对齐填充起占位作用。HotSpot 虚拟机要求对象的起始地址必须是8的整数倍,因此需要对齐填充

      5.如何判断对象是否是垃圾:

         引用计数法:设置引用计数器,对象被引用计数器加 1,引用失效时计数器减 1,如果计数器为 0 则被 标记为垃圾。会存在对象间循环引用的问题,一般不使用这种方法

         可达性分析:通过 GC Roots 的根对象作为起始节点,从这些节点开始,根据引用关系向下搜索,如果 某个对象没有被搜到,则会被标记为垃圾可作为 GC Roots 的对象包括虚拟机栈和本地方法栈中引用 的对象、类静态属性引用的对象、常量引用的对象

四.堆内存结构与虚拟机参数

   面试直击:一文带你复习java--jvm篇_第5张图片堆内存结构:

      年轻代(Young Generation)

1.所有新生成的对象首先都是放在年轻代的。

2.新生代内存按照8:1:1的比例分为一个eden区和两个Survivor(survivor0,survivor1)区。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。

3.当survivor1区不足以存放eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。

4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)

        年老代(Old Generation)

1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

2.内存比新生代也大很多(大概是2倍),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率比较高。

        持久代(Permanent Generation)

用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,从JDK8以后已经废弃, 将存放静态文件,如Java类、方法等这些存储到了元数据区

总结:

         Young 年轻区(代): Eden+S0+S1, S0和S1大小相等, 新创建的对象都在年轻代

        Tenured 年老区: 经过年轻代多次垃圾回收存活下来的对象存在年老代中.

         Jdk1.7和Jdk1.8的区别在于, 1.8将永久代中的对象放到了元数据区, 不存永久代这一区域了.

 参数说明:

参数 描述
-Xms 设置堆的初始可用大小,默认为物理内存的1/64
-Xmx 设置堆的最大可用大小,默认为物理内存的1/4,一般不要大于物理内存的80%
-Xss 每个线程的栈大小
-Xmn 新生代大小
-XX:NewRatio 默认为2表示新生代占老年代的1/2,占整个堆内存的1/3
-XX:SurvivorRatio 默认8,表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存
-XX:MaxMetaspaceSize

设置元空间最大值默认为-1,即不限制,或者说只受限于本地内存大小。  

-XX:MetaspaceSize

       指定元空间触发Fullgc的初始阈值(元空间无固定初始大小),以字节为单位,默认为2M左右,达到该值就会触发full gc进行类型卸载同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值

        如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值,这个跟早期jdk版本的XX:PermSize参数意思不一样;-XX:PermSize代表永久代的初始容量。

        由于调整元空间的大小需要FullGC,这是非常昂贵的操作,如果应用在启动时候发生大量FullGC,通常是由永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM 参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存得机器来说,一般将这两个值设置为256M

五.常见异常以及解决方案

1.堆内存溢出

        java.lang.OutOfMemoryError:这种是java堆内存异常。

         1)⽼年代内存不⾜:java.lang.OutOfMemoryError:Java heap space

         2)永久代内存不足:java.lang.OutOfMemoryError:PermGenspace

         3)代码bug,占⽤内存⽆法及时回收。

        解决方案:

-Xms1024M
-Xmx1024M

代码bug

/**
 * 堆内存溢出
 */
public class OutOfMemoryTest {
 public static void main(String[] args){
  List list = new ArrayList();
    while(true){
     StringBuffer sb = new StringBuffer();
       for(int i = 0 ; i < 5000 ; i++){
        sb.append(i);
       }
     list.add(sb);
   }
  }
}

2.栈内存溢出

        StackOverflowError示例:x先将-Xss设置为128k(默认为1M)

/**
 * 栈内存溢出
 */
public class StackOverflowTest {
 static int count = 0;
 static void redo() {
 count++;
 redo();
 }
 public static void main(String[] args) {
 try {
 redo();
 } catch (Throwable t) {
 t.printStackTrace();
 System.out.println(count);
 }
 }
}

结论: -Xss设置越⼩count值越⼩,说明⼀个线程栈⾥能分配的栈帧就越少,但是对JVM整体来说能开启 的线程数会更多

六.JVM参数调优案例

提问:JVM内存参数⼤⼩该如何设置?

 JVM参数⼤⼩设置并没有固定标准,需要根据实际项⽬情况分析

1. ⽇均百万级订单交易系统如何设置JVM参数

面试直击:一文带你复习java--jvm篇_第6张图片

 面试直击:一文带你复习java--jvm篇_第7张图片

 原始分配:j​​​​​​​

java -Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize
=512M -jar restclient.jar

调优之后:让fullgc几乎不发生

java -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMet
aspaceSize=256M -jar restclient.jar

总结:JVM优化就是尽可能让对象都在新⽣代⾥分配和回收,尽量别让太多对象频繁进⼊⽼年代,避免 频繁对⽼年代进⾏垃圾回收,同时给系统充⾜的内存⼤⼩避免新⽣代频繁的进⾏垃圾回收

七.垃圾回收

JVM的垃圾回收动作可以大致分为两大步,首先是「如何发现垃圾」,然后是「如何回收垃圾」。说明一点, 线程私有的不存在垃圾回收, 只有线程共享的才会存在垃圾回收, 所以堆中存在垃圾回收

1.如何发现垃圾

Java语言规范并没有明确的说明JVM使用哪种垃圾回收算法,但是常见的用于「发现垃圾」的算法有两种,引用计数算法和根搜索算法

1.1.引用计数算法:

  • 该算法很古老(了解即可)。核心思想是,堆中的对象每被引用一次,则计数器加1,每减少一个引用就减1当对象的引用计数器为0时可以被当作垃圾收集
  • 优点:快。
  • 缺点:无法检测出循环引用。如两个对象互相引用时,他们的引用计数永远不可能为0。

1.2.根搜索算法(也叫可达性分析)

根搜索算法是把所有的引用关系看作一张图,从一个节点GC ROOT开始寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即可以当作垃圾

Java中可作为GC Root的对象有

  1.虚拟机栈中引用的对象

  2.本地方法栈引用的对象

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

  3.方法区中常量引用的对象

2.如何回收垃圾

Java中用于「回收垃圾」的常见算法有4种:

     2.1标记-清除算法(mark and sweep)

分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象

缺点:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片。

    2.2标记-整理算法

是在标记-清除算法基础上做了改进,标记阶段是相同的,但标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动在移动过程中清理掉可回收的对象这个过程叫做整理

优点:内存被整理后不会产生大量不连续内存碎片

     2.3复制算法(copying)

将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。
       缺点:可使用的内存只有原来一半。

    2.4分代收集算法(generation)

当前主流JVM都采用分代收集(Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为年轻代、年老代、永久代,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。

3.垃圾收集器分类

简述Minor GC:

        Minor GC指发生在新生代的垃圾收集,因为 Java 对象大多存活时间短,所以 Minor GC 非常频繁,一 般回收速度也比较快。

简述Full GC:

        Full GC 是清理整个堆空间—包括年轻代和永久代。调用System.gc(),老年代空间不足,空间分配担保失 败,永生代空间不足会产生full gc。

简述Serial垃圾收集器:

单线程串行收集器。垃圾回收的时候,必须暂停其他所有线程。新生代使用标记复制算法,老年代使用 标记整理算法。简单高效。

简述ParNew垃圾收集器 :

可以看作Serial垃圾收集器的多线程版本,新生代使用标记复制算法,老年代使用标记整理算法。

简述Parallel Scavenge垃圾收集器 :

注重吞吐量,即cpu运行代码时间/cpu耗时总时间(cpu运行代码时间+ 垃圾回收时间)。新生代使用标 记复制算法,老年代使用标记整理算法。

简述CMS垃圾收集器 :

注重最短时间停顿。CMS垃圾收集器为最早提出的并发收集器,垃圾收集线程与用户线程同时工作。采 用标记清除算法。该收集器分为初始标记、并发标记、并发预清理、并发清除、并发重置这么几个步 骤。 初始标记:暂停其他线程(stop the world),标记与GC roots直接关联的对象。并发标记:可达性分析过 程(程序不会停顿)。 并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象,重新标记,暂停虚拟机(stop the world)扫描CMS堆中剩余对象。 并发清除:清理垃圾对象,(程序不会停顿)。 并发重置,重置CMS收集器的数据结构。

简述G1垃圾收集器 :

和之前收集器不同,该垃圾收集器把堆划分成多个大小相等的独立区域(Region),新生代和老年代不 再物理隔离。通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个 小空间可以单独进行垃圾回收。 初始标记:标记与GC roots直接关联的对象。 并发标记:可达性分析。 最终标记,对并发标记过程中,用户线程修改的对象再次标记一下。 筛选回收:对各个Region的回收价值和成本进行排序,然后根据用户所期望的GC停顿时间制定回收计 划并回收。

八。常见内存回收策略:

         大多数情况下对象在新生代 Eden 区分配,当 Eden 没有足够空间时将发起一次 Minor GC

        大对象需要大量连续内存空间直接进入老年代区分配

         如果经历过第一次 Minor GC 仍然存活且能被 Survivor 容纳,该对象就会被移动到 Survivor 中并将年龄 设置为 1,并且每熬过一次 Minor GC 年龄就加 1 ,当增加到一定程度(默认15)就会被晋升到老年 代

        如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 的一半,年龄不小于该年龄的对象就可 以直接进入老年代

        空间分配担保。MinorGC 前虚拟机必须检查老年代最大可用连续空间是否大于新生代对象总空间,如果 满足则说明这次 Minor GC 确定安全。如果不,JVM会查看HandlePromotionFailure 参数是否允许担保 失败,如果允许会继续检查老年代最大可用连续空间是否大于历次晋升老年代对象的平均大小,如果满 足将Minor GC,否则改成一次 FullGC

九.类加载

        类加载运⾏全过程 :

        当我们⽤java命令运⾏某个类的main函数启动程序时,⾸先需要通过类加载器把主类加载到 JVM。

例如:

public class Demo {
 public static final int initData = 888;
 public static Person p = new Person();
 //⼀个⽅法对应⼀块栈帧内存区域
 public int exec() { 
 int a = 1;
 int b = 2;
 int c = (a + b) * 10;
 return c;
 }
 public static void main(String[] args) {
 Demo demo = new Demo();
 demo.exec();
 }
}

通过Java命令执⾏代码的⼤体流程如下:

面试直击:一文带你复习java--jvm篇_第8张图片

 其中loadClass的类加载过程有如下⼏步:

         加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使⽤ >> 卸载

         加载:在硬盘上查找并通过IO读⼊字节码⽂件使⽤到类时才会加载,例如调⽤类的main()⽅ 法,new对象等等,在加载阶段会在内存中⽣成⼀个代表这个类的java.lang.Class对象,作为 ⽅法区这个类的各种数据的访问⼊⼝

        验证:校验字节码⽂件的正确性

        准备:给类的静态变量分配内存,并赋予默认值,⽐如:int=0; bool ret = false;

        解析:将符号引⽤替换为直接引⽤,该阶段会把⼀些静态⽅法(符号引⽤,⽐如main()⽅法)替 换为指向数据所存内存的指针或句柄等(直接引⽤),这是所谓的静态链接过程(类加载期间完 成),动态链接是在程序运⾏期间完成的将符号引⽤替换为直接引⽤。

       初始化:对类的静态变量初始化为指定的值,执⾏静态代码块

面试直击:一文带你复习java--jvm篇_第9张图片

 类被加载到⽅法区中后主要包含:

         运⾏时常量池、类型信息、字段信息、⽅法信息、类加载器的引 ⽤、对应class实例的引⽤等信息。

        类加载器的引⽤:这个类到类加载器实例的引⽤

        对应class实例的引⽤:类加载器在加载类信息放到⽅法区中后,会创建⼀个对应的Class 类型的 对象实例放到堆(Heap)中, 作为开发⼈员访问⽅法区中类定义的⼊⼝和切⼊点。

        注意,主类在运⾏过程中如果使⽤到其它类,会逐步加载这些类。 jar包或war包⾥的类不是⼀次性全部加载的,是使⽤到时才加载

类加载器:

        引导类加载器:负责加载⽀撑JVM运⾏的位于JRE的lib⽬录下的核⼼类库,⽐如rt.jar、 charsets.jar等

        扩展类加载器:负责加载⽀撑JVM运⾏的位于JRE的lib⽬录下的ext扩展⽬录中的JAR类包

        应⽤程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你⾃⼰写的那些类

        ⾃定义加载器:负责加载⽤户⾃定义路径下的类包

看⼀个类加载器示例:

public class TestJDKClassLoader {
 public static void main(String[] args) {
 System.out.println(String.class.getClassLoader());
 System.out.println(com.sun.crypto.provider.DESKeyFactory.class.get
ClassLoader().getClass().getName());
 System.out.println(TestJDKClassLoader.class.getClassLoader().getCl
ass().getName());
 System.out.println();
 ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
 ClassLoader extClassloader = appClassLoader.getParent();
 ClassLoader bootstrapLoader = extClassloader.getParent();
 System.out.println("the bootstrapLoader : " + bootstrapLoader);
 System.out.println("the extClassloader : " + extClassloader);
 System.out.println("the appClassLoader : " + appClassLoader);
 System.out.println();
 System.out.println("bootstrapLoader加载以下⽂件:");
 URL[] urls = Launcher.getBootstrapClassPath().getURLs();
 for (int i = 0; i < urls.length; i++) {
 System.out.println(urls[i]);
 }
 System.out.println();
 System.out.println("extClassloader加载以下⽂件:");
 System.out.println(System.getProperty("java.ext.dirs"));
 System.out.println();
 System.out.println("appClassLoader加载以下⽂件:");
 System.out.println(System.getProperty("java.class.path"));
 }
}

运行结果

null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@3cda1055
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
bootstrapLoader加载以下⽂件:
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_301/jre/classes
extClassloader加载以下⽂件:
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
appClassLoader加载以下⽂件:
C:\Program Files\Java\jdk1.8.0_301\jre\lib\charsets.jar;C:\Program Files\J
ava\jdk1.8.0_301\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_301\jre
\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\e
xt\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jaccess.jar;C:\Program File
s\Java\jdk1.8.0_301\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_3
01\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\e
xt\nashorn.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunjce_provider.jar;C:\Prog
ram Files\Java\jdk1.8.0_301\jre\lib\ext\sunmscapi.jar;C:\Program Files\Jav
a\jdk1.8.0_301\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_30
1\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\javaws.
jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jce.jar;C:\Program Files\Ja
va\jdk1.8.0_301\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib
\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jsse.jar;C:\Progra
m Files\Java\jdk1.8.0_301\jre\lib\management-agent.jar;C:\Program Files\Ja
va\jdk1.8.0_301\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_301\jre
\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\rt.jar;D:\课
件\⾯试专区\3、JVM讲解\jvm-demo\target\classes;C:\Program Files\ideaIU_2021.
2.1_Portable\lib\idea_rt.jar;C:\Users\Administrator\AppData\Local\Temp\cap
tureAgent3086jars\debugger-agent.jar

类加载器初始化过程:

        参⻅类运⾏加载全过程图可知其中会创建JVM启动器实例sun.misc.Launcher。 在Launcher构造⽅法内部,其创建了两个类加载器,分别是 sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应⽤ 类加载器)。 JVM默认使⽤Launcher的getClassLoader()⽅法返回的类加载器AppClassLoader的实例加载我们 的应⽤程序

//Launcher的构造⽅法
public Launcher() {
 Launcher.ExtClassLoader var1;
 try {
 //构造扩展类加载器,在构造的过程中将其⽗加载器设置为null
 var1 = Launcher.ExtClassLoader.getExtClassLoader();
 } catch (IOException var10) {
 throw new InternalError("Could not create extension class loader",
var10);
 }
 try {
 //构造应⽤类加载器,在构造的过程中将其⽗加载器设置为ExtClassLoader,
 //Launcher的loader属性值是AppClassLoader,我们⼀般都是⽤这个类加载器来加
载我们⾃⼰写的应⽤程序
 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
 } catch (IOException var9) {
 throw new InternalError("Could not create application class loade
r", var9);
 }
 Thread.currentThread().setContextClassLoader(this.loader);
 String var2 = System.getProperty("java.security.manager");
 //......
}

十.双亲委派机制

        简述双亲委派机制:

         一个类加载器收到类加载请求之后,首先判断当前类是否被加载过。已经被加载的类会直接返回,如果 没有被加载,首先将类加载请求转发给父类加载器一直转发到启动类加载器,只有当父类加载器无法 完成时才尝试自己加载

         加载类顺序:BootstrapClassLoader->ExtensionClassLoader->AppClassLoader->CustomClassLoader

        检查类是否加载顺序: CustomClassLoader->AppClassLoader->ExtensionClassLoader->BootstrapClassLoader

        双亲委派机制的优点:

         1. 避免类的重复加载。相同的类被不同的类加载器加载会产生不同的类,双亲委派保证了java程序的 稳定运行。 2. 保证核心API不被修改。

        如何破坏双亲委派机制 :

        重载loadClass()方法,即自定义类加载器。

        如何构建自定义类加载器:

         1. 新建自定义类继承自java.lang.ClassLoader 2. 重写findClass、loadClass、defineClass方法

十一.java引用

  1. 强引用
    1. Object obj=new Object()

      强引⽤是使⽤最普遍的引⽤。如果⼀个对象具有强引⽤,那垃圾回收器绝不会回收它。当内存空间 不⾜,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终⽌,也不会靠随意回收具有强 引⽤的对象来解决内存不⾜的问题。

  2. 软引用
    1. String str=new String("abc"); // 强引⽤
      SoftReference softRef=new SoftReference(str); // 软引

      如果⼀个对象只具有软引⽤,若内存空间⾜够,垃圾回收器就不会回收它;如果内存空间不⾜ 了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使⽤。软引⽤可 ⽤来实现内存敏感的⾼速缓存。

  3. 弱引用
    1. Object obj = new Object();
      WeakReference wf = new WeakReference(obj);
      obj = null; 

      弱引⽤与软引⽤的区别在于:只具有弱引⽤的对象拥有更短暂的⽣命周期。在垃圾回收器线程 扫描它所管辖的内存区域的过程中,⼀旦发现了只具有弱引⽤的对象,不管当前内存空间⾜够与 否,都会回收它的内存。不过,由于垃圾回收器是⼀个优先级很低的线程,因此不⼀定会很快发现 那些只具有弱引⽤的对象。

    2. 虚引用
      1. Object obj = new Object();
        PhantomReference pf = new PhantomReference(obj);
         

        虚引⽤在任何时候都可能被垃圾回收器回收,主要⽤来跟踪对象被垃圾回收器回收的活动,被 回收时会收到⼏个系统通知。虚引⽤与软引⽤和弱引⽤的⼀个区别在于:虚引⽤必须和引⽤队列 (ReferenceQueue)联合使⽤。当垃圾回收器准备回收⼀个对象时,如果发现它还有虚引⽤,就 会在回收对象的内存之前,把这个虚引⽤加⼊到与之关联的引⽤队列中。

        十二.finalize⽅法

                1、finalize()⽅法是什么?

                 finalize()⽅法是Object类提供的⽅法,在GC(垃圾回收器)准备释放对象所占⽤的内存空间之 前,它将⾸先调⽤finalize()⽅法。其在Object中定义如下:

                 protected void finalize() throws Throwable { }

                2、finallize()⽅法什么时候被调⽤?

                在java中,由于垃圾回收器(GC)的机制是⾃动回收,所以垃圾回收的时机具有不确定性, finallize()也可能⾃始⾃终都不被调⽤

                3、什么时候使⽤它?

        这个函数⼀般⽤于关闭⾮java资源(如⽂件,数据库连接池等),也⽤于关闭java语⾔调⽤其他语 ⾔(如c语⾔等)⽽产⽣的内存空间。

                 4、尽量避免使⽤这个函数。

        垃圾回收具有不确定性,也⼤有可能在资源耗尽前⽆法执⾏这个函数,因⽽最好⼿动调⽤的显示的 close()⽅法。 System.gc()或Runtime.getRuntime()的调⽤,会显示的触发FullGC也不⼀定成功。

        你可能感兴趣的:(jvm,java,面试)