JVM面试题

news2024/10/5 23:28:23

Java内存区域

说一下 JVM 的主要组成部分及其作用?

image-20200807091040262

JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。

  • Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。
  • Execution engine(执行引擎):执行classes中的指令。
  • Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
  • Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。

作用 :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

下面是Java程序运行机制详细说明

Java程序运行机制步骤

  • 首先利用IDE集成开发工具编写Java源代码,源文件的后缀为.java;
  • 再利用编译器(javac命令)将源代码编译成字节码文件,字节码文件的后缀名为.class;
  • 运行字节码的工作是由解释器(java命令)来完成的。

image-20200807091620313

从上图可以看,java文件通过编译器变成了.class文件,接下来类加载器又将这些.class文件加载到JVM中。
其实可以一句话来解释:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。

说一下 JVM 运行时数据区

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域:

image-20200807091903962

不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范, Java 虚拟机规范规定的区域分为以下 5 个部分:

  • 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
  • Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
  • 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
  • Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
  • 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

深拷贝和浅拷贝

浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,

深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,

使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。

浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。

深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。

说一下堆栈的区别?

物理地址

堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩

栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。

内存分别

因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。

是连续的,所以分配的内存大小要在编译期就确认,大小是固定的

存放的内容

存放的是对象的实例和数组。因此该区更关注的是数据的存储

存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。

PS:

  1. 静态变量放在方法区
  2. 静态的对象还是放在堆。

程序的可见度

堆对于整个应用程序都是共享、可见的。

栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。

队列和栈是什么?有什么区别?

队列和栈都是被用来预存储数据的。

  • 操作的名称不同。队列的插入称为入队,队列的删除称为出队。栈的插入称为进栈,栈的删除称为出栈。
  • 可操作的方式不同。队列是在队尾入队,队头出队,即两边都可操作。而栈的进栈和出栈都是在栈顶进行的,无法对栈底直接进行操作。
  • 操作的方法不同。队列是先进先出(FIFO),即队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(不能从中间插入),每次离开的成员总是队列头上(不允许中途离队)。而栈为后进先出(LIFO),即每次删除(出栈)的总是当前栈中最新的元素,即最后插入(进栈)的元素,而最先插入的被放在栈的底部,要到最后才能删除。

HotSpot虚拟机对象探秘

对象的创建

说到对象的创建,首先让我们看看 Java 中提供的几种对象创建方式:

image-20200807092841238

image-20200807092926691

虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。类加载通过后,接下来分配内存。若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式。划分内存时还需要考虑一个问题-并发,也有两种方式: CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。然后内存空间初始化操作,接着是做一些必要的**对象设置(**元信息、哈希码…),最后执行<init>方法。

为对象分配内存

类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java堆是否规整,有两种方式:

  • 指针碰撞:如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。
  • 空闲列表:如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。

image-20200807093302307

处理并发安全问题

对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案:

  • 对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性);
  • 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB。

image-20200807093409650

对象的访问定位

Java程序需要通过 JVM 栈上的引用访问堆中的具体对象。对象的访问方式取决于 JVM 虚拟机的实现。目前主流的访问方式有 句柄直接指针 两种方式。

指针: 指向对象,代表一个对象在内存中的起始地址。

句柄: 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的真实内存地址。

句柄访问

Java`堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据对象类型数据各自的具体地址信息,具体构造如下图所示:

image-20200807093808140

优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中实例数据指针,而引用本身不需要修改。

直接指针

如果使用直接指针访问,引用 中存储的直接就是对象地址,那么Java堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息。

image-20200807093925611

优势:速度更,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。

内存溢出异常

Java会存在内存泄漏吗?请简单描述

内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。

但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。

垃圾收集器

简述Java垃圾回收机制

在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

GC是什么?为什么要GC

GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存

回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动

回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。

垃圾回收的优点和原理。并考虑2种回收机制

java语言最显著的特点就是引入了垃圾回收机制,它使java程序员在编写程序时不再考虑内存管理的问题。

由于有这个垃圾回收机制,java中的对象不再有“作用域”的概念,只有引用的对象才有“作用域”。

垃圾回收机制有效的防止了内存泄露,可以有效的使用可使用的内存。

垃圾回收器通常作为一个单独的低级别的线程运行,在不可预知的情况下对内存堆中已经死亡的或很长时间没有用过的对象进行清除和回收。

程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。

垃圾回收有分代复制垃圾回收、标记垃圾回收、增量垃圾回收。

垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。

通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。

可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

Java 中都有哪些引用类型?

  • 强引用:发生 gc 的时候不会被回收。
  • 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
  • 弱引用:有用但不是必须的对象,在下一次GC时会被回收。
  • 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

怎么判断对象是否可以被回收?

垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需要被回收。

一般有两种方法来判断:

  • 引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
  • 可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

引用计数算法:

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值就减1;计数器为0,说明对象没有被使用

但是主流的java虚拟机里面没有选用引用计数法来管理内存,其中最重要的原因是它很难解决对象之间互相循环引用的问题(objA和objB都有字段instance,赋值令objA.instance=ObjB和objB.instance=objA,除此之外,两者再无任何引用,但是因为它们互相引用着对方,导致它们的引用计数都不为0,于是引用计数算法无法通知GC收集器回收它们)

可达性分析算法:

通过一系列的称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的

在Java中,对象什么时候可以被垃圾回收

当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。

JVM中的永久代中会发生垃圾回收吗

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区
(译者注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)

说一下 JVM 有哪些垃圾回收算法?

  • 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
  • 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
  • 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
  • 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

标记-清除算法

标记无用对象,然后进行清除回收。

标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段:

  • 标记阶段:标记出可以回收的对象。
  • 清除阶段:回收被标记的对象所占用的空间。

标记-清除算法之所以是基础的,是因为后面讲到的垃圾收集算法都是在此算法的基础上进行改进的。

优点:实现简单,不需要对象进行移动。

缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。

标记-清除算法的执行的过程如下图所示

image-20200807095357308

复制算法

为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。

优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。

缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。

复制算法的执行过程如下图所示

image-20200807095431852

标记-整理算法

新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-清除算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。

优点:解决了标记-清理算法存在的内存碎片问题。

缺点:仍需要进行局部对象移动,一定程度上降低了效率。

标记-整理算法的执行过程如下图所示

image-20200807095622650

分代收集算法

当前商业虚拟机都采用分代收集的垃圾收集算法。分代收集算法,顾名思义是根据对象的存活周期将内存划分为几块。一般包括年轻代老年代永久代,如图所示:

image-20200807095705990

说一下 JVM 有哪些垃圾回收器?

如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括SerialPraNewParallel Scavenge,回收老年代的收集器包括Serial OldParallel OldCMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。

image-20200807095856359

  • Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
  • ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
  • Parallel Scavenge收集器 (复制算法): **新生代并行收集器,**追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
  • Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
  • Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
  • CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间
  • G1(Garbage First)收集器 (0): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

简述分代垃圾回收器是怎么工作的?

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

  • 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
  • 清空 Eden 和 From Survivor 分区;
  • From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。

老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

内存分配策略

简述java内存分配与回收策率以及Minor GC和Major GC

所谓自动内存管理,最终要解决的也就是内存分配和内存回收两个问题。前面我们介绍了内存回收,这里我们再来聊聊内存分配。

对象的内存分配通常是在 Java 堆上分配(随着虚拟机优化技术的诞生,某些场景下也会在栈上分配,后面会详细介绍),对象主要分配在新生代的 Eden 区,如果启动了本地线程缓冲,将按照线程优先在 TLAB 上分配。少数情况下也会直接在老年代上分配。总的来说分配规则不是百分百固定的,其细节取决于哪一种垃圾收集器组合以及虚拟机相关参数有关,但是虚拟机对于内存的分配还是会遵循以下几种「普世」规则:

对象优先在 Eden 区分配

多数情况,对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间进行分配时,虚拟机将会发起一次 Minor GC。如果本次 GC 后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。

这里我们提到 Minor GC,如果你仔细观察过 GC 日常,通常我们还能从日志中发现 Major GC/Full GC。

  • Minor GC 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快;
  • Major GC/Full GC 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。

大对象直接进入老年代

所谓大对象是指需要大量连续内存空间的对象,频繁出现大对象是致命的,会导致在内存还有不少空间的情况下提前触发 GC 以获取足够的连续空间来安置新对象。

前面我们介绍过新生代使用的是标记-清除算法来处理垃圾回收的,如果大对象直接在新生代分配就会导致 Eden 区和两个 Survivor 区之间发生大量的内存复制。因此对于大对象都会直接在老年代进行分配。

长期存活对象将进入老年代

虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在 Eden 区出生,并且能够被 Survivor 容纳,将被移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到一定程度**(默认 15**) 就会被晋升到老年代。

虚拟机类加载机制

简述java类加载机制?

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型。

描述一下JVM加载Class文件的原理机制

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

类装载方式,有两种 :

1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,

2.显式装载, 通过class.forname()等方法,显式加载需要的类

Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

什么是类加载器,类加载器有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

主要有一下四种类加载器:

  1. 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
  2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  3. 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
  4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。

全盘委托机制

当一个类运行时,可能有其他的类,这时由应用类加载器委托给扩展类加载器是否加载这些类,扩展类加载器再次向上委托引导类加载器是否加载这些类,引导类加载器判断后将有的类进行加载向内存中返回class对象后,再由扩展类加载器中有的类进行加载返回class对象,剩下全部有应用类加载器进行加载.

说一下类装载的执行过程?

类装载分为以下 5 个步骤:

  • 加载:根据查找路径找到相应的 class 文件然后导入;
  • 验证:检查加载的 class 文件的正确性;
  • 准备:给类中的静态变量分配内存空间;
  • 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
  • 初始化:对静态变量和静态代码块执行初始化工作。

类加载过程

*第一步:加载*

查找并加载类的二进制数据。

加载是类加载过程的第一个阶段,虚拟机在这一阶段需要完成以下三件事情:

通过类的全限定名来获取其定义的二进制字节流
将字节流所代表的静态存储结构转化为方法区的运行时数据结构
在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口

*第二步:验证*

确保被加载的类的正确性。

这一阶段是确保 Class 文件的字节流中包含的信息符合当前虚拟机的规范,并且不会损害虚拟机自身的安全。包含了四个验证动作:文件格式验证,元数据验证,字节码验证,符号引用验证。

*第三步:准备*

为类的静态变量分配内存,并将其初始化为默认值。

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。

*第四步:解析*

把类中的符号引用转换为直接引用。

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。

*第五步:初始化*

类变量进行初始化

为类的静态变量赋予正确的初始值,JVM 负责对类进行初始化,主要对类变量进行初始化。

什么是双亲委派模型?

在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。

image-20200807102259323

类加载器分类:

  • 启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;

  • 其他类加载器:

  • 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;

  • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

    双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。

    当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。

    当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。

    采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。

JVM调优

说一下 JVM 调优的工具?

JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。

  • jconsole:用于对 JVM 中的内存、线程和类等进行监控;
  • jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。

常用的 JVM 调优的参数都有哪些?

  • -Xms2g:初始化推大小为 2g;
  • -Xmx2g:堆最大内存为 2g;
  • -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
  • -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
  • –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
  • -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
  • -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
  • -XX:+PrintGC:开启打印 gc 信息;
  • -XX:+PrintGCDetails:打印 gc 详细信息。

如何解决内存溢出

首先,如果代码没有什么问题的情况下,可以适当调整-Xms和-Xmx两个jvm参数,使用压力测试来调整这两个参数达到最优值。

其次,尽量避免大的对象的申请,像文件上传,大批量从数据库中获取,这是需要避免的,尽量分块或者分批处理,有助于系统的正常稳定的执行。

最后,尽量提高一次请求的执行速度,垃圾回收越早越好,否则,大量的并发来了的时候,再来新的请求就无法分配内存了,就容易造成系统的雪崩。

https://blog.csdn.net/qq_32671287/article/details/86063396

Java垃圾回收的时间点

垃圾回收系统主要分为三部分

1.判别那些对象是垃圾

2.垃圾怎么进行回收

3.什么时候回收垃圾

什么时候GC

首先来说什么时候发生GC(垃圾回收),一下想到的肯定是:内存不够用了就清理一下呗。的确大体上是这样的,我们从虚拟机的角度来理解这个问题,当进行垃圾回收的时候肯定会先进行可达性算法的遍历,这时肯定是要所有的线程的停下来等待可达性算法线程标记对象的,这个过程被称为“Stop The World”,名字很帅啊。

究竟是什么类型的数据

继续往下说之前,先说一下Java中数据的两大分类吧,分别是基本类型和引用类型,这两个类型的数据虽然都是放在堆上的,但是在调用时的行为截然不同啊,于是Java虚拟机利用一个称为OopMap的数据类型去记录哪些变量是基本类型,哪些变量是引用类型,而且也会在特定的位置记录这些信息。

由于垃圾回收时对两种数据类型的操作是不同的,所以Java垃圾回收机肯定要知道现在回收的这个对象究竟是引用还是基本类型,上文说Java虚拟机会把区分对象类型的信息记录在特定的位置,所以在垃圾回收时必须要在这个特定位置进行(这样才能得到对象的类型信息)。这个特定位置被称为“安全点”。

安全点在哪里,有多少

说到这里脑中会产生一个想法:

Java垃圾回收进行的还是比较频繁的(尤其是Minor GC),所以“安全点”应该基本是处处存在的啊,这样才能在发生GC时提供“服务”。

错!如果处处都有安全点去记录信息,那么内存的开销会变得非常大,但是如果安全点过于少的话也不行啊,GC都发生很长时间了,但是就因为无法得到数据类型信息一直无法继续前行。所以“安全点”的位置和出现次数是非常讲究的,**“安全点”的选定是根据“是否具有让程序长时间执行的特征”来决定的。**Java虚拟机是通过执行一条条指令来运作的(安全点也是记录到某一条的指令上),指令执行都是非常快的(不然我们也不会有那么流畅的体验啊!),所以长时间让程序执行的指令一般是那些方法调用,循环跳转,异常跳转等,这些操作会花一些相对较长的时间而且基本不会产生新的对象,也就是这段时间整个线程的对象总数基本是确定的,所以在这个时间点进行GC是比较理想的。

image-20200911143335988

GC信号与安全点

继续我们的GC流程,内存不够了,Java虚拟机开始发生GC。这时Java虚拟机会发出一个GC,告诉每个线程“发生GC了,赶紧在最近的安全点停下来去回收垃圾”这个信息。然后各个线程都注意到了这个信息,于是在各自的安全点回收当前线程的垃圾对象。问题又来啦!

A:线程是怎么发现GC信号的呢?

B:当然是在执行过程中去实时检测GC信号的啊

A:那要是总检测GC信号,也太浪费资源了吧!

B:当然是在特定的位置检测了

没错,检测的位置就是就是“安全点”,每次Java虚拟机执行到标记为安全点的指令时都会进行一次GC信号检测,如果没有就继续跑指令了,如果有就直接进行GC,反正现在的位置就是安全点,执行GC没毛病(真是佩服那些设计者们,想出这么巧妙的方法)。

在GC信号发出时,各个线程运行到“安全点”就会发现信号,然后停止线程进行GC操作,当所有的线程都回收完毕时,这次GC就结束了。

image-20200911143515735

线程正在执行各自的任务,这时发生了GC,Java虚拟机发出GC信号。

image-20200911143528897

各个线程到了“安全点”,检测到了GC信号,开始进行GC。

image-20200911143543138

每个线程都完成垃圾回收,整个GC结束,各个线程恢复执行。

阻塞呢,无法检测“安全点”

还有一个问题,当程序在等待io操作,或者在sleep的状态怎么办呢?这时程序无法继续向下执行,不能实时检测GC信号,这次GC是不是就错过了?

当然不会啊,有一个特殊的“安全点”可以解决这个问题。

安全区域
如果说安全点是一个“点”,那么“安全区域”就是把这个点进行一个拉长操作,变成一条线,在这条线包含那一片区域就是“安全区域”。

等待io等线程阻塞非常适合放到“安全区域”中,因为在这段时间中,线程就是处于一个等待的状态,基本不会产生新的对象,适合去进行GC。当线程进入“安全区域”时,会把给自己贴上一个安全标签,告诉虚拟机自己现在在“安全区域”,随时可以进行GC。这时发生GC,虚拟机看到这个线程有安全标签,根本就不会管他,直接就是一顿GC全家桶伺候。这时的GC可以说这个线程是不知情的(因为线程在阻塞啊,处于专注的等待状态或者睡得正香),所以当这个线程重新跑以来以后,在它走出安全区时会检测一下虚拟机是不是在GC的过程中,如果是就等待GC完成,如果不是就直接继续运行(管你GC完成了还是压根没GC,反正我已经给过你机会了)。

“安全区”和“安全区域”基本上可以解决所以的问题,所以在GC什么时候进行这一块也就结束了。

image-20200911143645166

线程在安全区域外正常执行

image-20200911143655625

线程执行到安全区域,标记当前线程为安全状态,然后进入了io阻塞

image-20200911143708305

这时发生GC,Java虚拟机查询线程的安全标志,发现此线程处于安全区域,于是直接开始GC。

image-20200911143720877

GC完成,线程从阻塞态变为运行态重新开始运行,走出安全区域时,检测是否有GC在执行中,并没有发现,于是走出安全区域继续执行下去。

使用类加载器加载资源文件

我们在项目之中,加载资源文件最为痛苦的就是资源文件的路径.

这里我们提出一个类加载器的概念.

类加载器加载的是项目的根路径,我们使用类加载器定位的位置就能定位位置.

例子:

InputStream in  = TestField.class.getClassLoader().getResourceAsStream("11.txt");

现在我们就可以使用类加载器配合相对目录就能定位资源的位置.

自定义类加载器

如果想要编写自己的类加载器,只需要两步:

  • 继承ClassLoader类
  • 覆盖findClass(String className)方法

ClassLoader超类的loadClass方法用于将类的加载操作委托给其父类加载器去进行,只有当该类尚未加载并且父类加载器也无法加载该类时,才调用findClass方法。
如果要实现该方法,必须做到以下几点:

1.为来自本地文件系统或者其他来源的类加载其字节码。
2.调用ClassLoader超类的defineClass方法,向虚拟机提供字节码。

堆内存中的年轻代分配内存过少或过多分别有什么影响?复制算***导致Stop-the-World吗?

可以明显看出上述 GC 日志包含两次 Minor GC。 注意到第二次 Minor GC 的情况, 日志打出 “Desired survivor size 53673984 bytes”, 但是却存活了 “- age 1: 107256200 bytes, 107256200 total” 这么多。 可以看出明显的新生代的 Survivor 空间不足。正因为 Survivor 空间不足, 那么从 Eden 存活下来的和原来在 Survivor 空间中不够老的对象占满 Survivor 后, 就会提升到老年代, 可以看到这一轮 Minor GC 后老年代由原来的 0K 占用变成了 105782K 占用, 这属于一个典型的 JVM 内存问题, 称为 “premature promotion”(过早提升)。

常用垃圾回收器

image-20200913154817982

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1

新生代 And 老生代

目前常用的商用垃圾收集器都使用的是分代垃圾回收方式。

分代垃圾回收器把内存分为:新生代(Young Generation)和老生代(Tenured Generation),如下图所示:

image-20200913154951800

默认情况下,新生代和老生代的内存比例是 1:2,该值可以通过 -XX:NewRatio 来设定。

新生代(Young Generation)

程序中的大部分对象都符合“朝生夕死”的特性,所以绝大数新创建的对象都会存放在新生代,除非是大对象会直接进入老生代。新生代采用的是复制算法,这样可以更高效的回收内存空间。

新生代有细分为:Eden、Form Survivor、To Survivor 三个区域,默认的比例是 8:1:1,可以通过 -XX:SurvivorRatio 来设定。

新生代垃圾回收的执行过程:

1、Eden 区 + From Survivor 区存活着的对象复制到 To Survivor 区;

2、清空 Eden 和 From Survivor 分区;

3、From Survivor 和 To Survivor 分区交换(From 变 To,To 变 From)。

老生代(Tenured Generation)

老生代垃圾回收的频率比新生代低,存放的主要对象是:

1、新生代对象经过 N 次 GC 晋升到老年代。

可以通过设置 -XX:MaxTenuringThreshold=5 来设置,默认值是 15 次。

2、大对象直接存储到老生代。

所谓的“大对象”指的是需要连续存储空间的对象,比如:数组。

当大对象在新生代存储不下的时候,就需要分配担保机制,把当前新生代的所有对象复制到老年代中,因为分配担保机制需要涉及大量的复制,会导致性能问题,所有最好的方案是直接把大对象存储到老生代中。

通过参数 -xx:PretrnureSizeThreshold 来设定大对象的值。

注意:该参数只有 Serial 和 ParNew 垃圾回收器有效。

Serial

Serial 最早的垃圾回收器,JDK 1.3.1 之前新生代唯一的垃圾回收器,使用的是单线程串行回收方式,在单 CPU 环境下性能较好,因为单线程执行不存在线程切换。

线程类型: 单线程

使用算法: 复制算法

指定收集器: -XX:+UseSerialGC

Serial Old

**Serial 收集器的老年代版本,同样也是单线程的。**它有一个实用的用途作为CMS收集器的备选预案,后面介绍CMS的时候会详细介绍。

线程类型: 单线程

使用算法: 标记-整理

指定收集器: -XX:+UseSerialGC

ParNew

ParNew 其实就是 Serial 的多线程版本,可以和 Serial 共用很多控制参数,比如:-XX:SurvivorRatio , ParNew 可以和 CMS 配合使用。

image-20200913155035099

线程类型: 多线程

使用算法: 复制

指定收集器: -XX:+UseParNewGC

Parallel Scavenge

Parallel 和 ParNew 收集器类似,也是多线程的,但 Parallel 是吞吐量优先的收集器,GC停顿时间的缩短是以吞吐量为代价的,比如收集 100MB 的内存,需要 10S 的时间,CMS 则会缩短为 7S 收集 50 MB 的内存,这样停顿的时间确实缩少了,但收集的频率变大了,吞吐量就变小了。

线程类型: 多线程

使用算法: 复制

指定收集器: -XX:+UseParallelGC

Parallel Old

Parallel Old 是 Parallel 的老生代版本,同样是吞吐量优先的收集器。

线程类型: 多线程

使用算法: 标记-整理

指定收集器: -XX:+UseParallelOldGC

CMS

CMS(Concurrent Mark Sweep)一种以获得**最短停顿时间为目标的收集器**,非常适用B/S系统。

使用 Serial Old 整理内存。

CMS 运行过程:

image-20200913160005497

1、初始标记

标记 GC Roots 直接关联的对象,需要 Stop The World 。

2、并发标记

从 GC Roots 开始对堆进行可达性分析,找出活对象。

3、重新标记

重新标记阶段为了修正并发期间由于用户进行运作导致的标记变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,也需要 Stop The World 。

4、并发清除

除垃圾对象。

CMS 缺点:

1、对 CPU 资源要求敏感。

CMS 回收器过分依赖于多线程环境,默认情况下,开启的线程数为(CPU 的数量 + 3)/ 4,当 CPU 数量少于 4 个时,CMS 对用户本身的操作的影响将会很大,因为要分出一半的运算能力去执行回收器线程。

2、CMS无法清除浮动垃圾。

浮动垃圾指的是CMS清除垃圾的时候,还有用户线程产生新的垃圾,这部分未被标记的垃圾叫做“浮动垃圾”,只能在下次 GC 的时候进行清除。

3、CMS 垃圾回收会产生大量空间碎片。

CMS 使用的是标记-清除算法,所有在垃圾回收的时候回产生大量的空间碎片。

注意:CMS 收集器中,当老生代中的内存使用超过一定的比例时,系统将会进行垃圾回收;当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时采用 Serial Old 算法进行清除,此时的性能将会降低。

线程类型: 多线程

使用算法: 标记-清除

指定收集器: -XX:+UseConcMarkSweepGC

G1

G1 GC 这是一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。G1 可以直观的设定停顿时间的目标,相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。

G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候,G1 的优势更加明显。

image-20200913160121040

G1 吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时 CMS 已经在 JDK 9 中被标记为废弃(deprecated),所以 G1 GC 值得深入掌握。

G1 运行过程:

1、初始标记

标记 GC Roots 直接关联的对象,需要 Stop The World 。

2、并发标记

从 GC Roots 开始对堆进行可达性分析,找出活对象。

3、重新标记

重新标记阶段为了修正并发期间由于用户进行运作导致的标记变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,也需要 Stop The World 。

4、筛选回收

首先对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。这个阶段可以与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的。

线程类型: 多线程

使用算法: 复制、标记-整理

指定收集器: -XX:+UseG1GC(JDK 7u4 版本后可用)

减少系统的停顿时间(STW)的增量收集算法 和分区算法

增量收集算法

1、上述现有的算法,在垃圾回收过程中,应用软件将处于一种stop the world的状态。在Stop the World状态下,应用程序所有的线程都会挂起,暂停一切正常的工作,等待垃圾回收的完成。如果垃圾回收时间过长,应用程序会被挂起很久,将严重影响用户体验或者系统的稳定性。为了解决这个问题,即对实时垃圾收集算法的研究直接导致了增量收集( Incremental Collecting) 算法的诞生。.

2、基本思想
如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。总的来说,增量收集算法的基础仍是传统的标记-清除和复制算法。增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作。

3、缺点
使用这种方式,由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。
因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

分区算法

1、一般来说,在相同条件下,堆空间越大,一次GC时所需要的时间就长,有关GC产生的停顿也越长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。

2、分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间。

3、每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间,很灵活。

总结:增量收集算法是将总的收集量一部分一部分的去执行,而分区算法是将总的内存空间分为小分区,一次可控的去收集多少个小区间。

并发和并行的区别

并发(concurrency)和并行(parallellism)是:

  1. 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
  2. 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
  3. 解释三:并行是在多台处理器上同时处理多个任务。如 hadoop 分布式集群,并发是在一台处理器上“同时”处理多个任务。

所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。

并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

image-20200913163544449

并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

image-20200913163604849

并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。


当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一个以上的线程,它只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发(Concurrent)。

当系统有一个以上 CPU 时,则线程的操作有可能非并发。当一个 CPU 执行一个线程时,另一个 CPU 可以执行另一个线程,两个线程互不抢占 CPU 资源,可以同时进行,这种方式我们称之为并行(Parallel)。

类加载

https://baijiahao.baidu.com/s?id=1636309817155065432&wfr=spider&for=pc

https://juejin.cn/post/6865572557329072141#heading-12

方法区和永久代及元空间的区别

方法区(线程共享):类的所有字段和方法字节码,以及一些特殊方法如构造方法,接口代码也再此定义。也就是静态变量+常量+类信息(构造方法/接口定义)+运行时常量池都存在该方法区中。也就是**类的模板。**
永久代:永久代在jdk1.7之后就被元空间给取代了,永久代逻辑结构上属于堆,但是物理上不属于堆,会出现OOM异常。
元空间:元数据区取代了永久代,本质和**永久代类似逻辑结构上属于堆,区别在于元数据区并不在虚拟机中,而是使用本地物理内存,**永久代在虚拟机中,元数据区也有可能发生OutOfMemory异常。

永久代和元空间都是对方法区的一个实现,这样做的原因是可以不用在单独为方法区去做一个内存管理了。

深入理解Java中的逃逸分析

https://blog.csdn.net/hollis_chuang/article/details/80922794

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。

对象和数组并不是都在堆上分配内存的。

深入理解JVM——第六章 JVM调优和深入了解性能优化笔记

https://blog.csdn.net/m0_37661458/article/details/91576694

MVC框架优缺点

https://blog.csdn.net/weixin_44589334/article/details/99674383

JAVA中,哪些对象可以作为GC Roots?

https://blog.csdn.net/mengqingming1/article/details/103526191

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中的类静态属性引用的对象
  3. 方法区中的常量引用的对象
  4. 本地方法栈中JNI(native方法)的引用的对象

CMS 和G1 的区别

https://www.jianshu.com/p/ab54489f5d71

GC两个关键难点:跨代引用与并发标记

https://blog.csdn.net/yunxing323/article/details/109231330

JVM为什么要分代回收

https://blog.csdn.net/weixin_34597791/article/details/114809782

基于两个共识

  1. 绝大多数对象都是朝生夕死
  2. 熬过越多次垃圾收集过程的对象九月难以消亡

JVM使用jstat命令查看jvmGC的情况

https://www.cnblogs.com/qmfsun/p/5601734.html

命令查看多线程所有线程ID

方法一:PS

在ps命令中,“-T”选项可以开启线程查看。下面的命令列出了由进程号为的进程创建的所有线程。 ps -T -p

方法二: Top

top命令可以实时显示各个线程情况。要在top输出中开启线程查看,请调用top命令的“-H”选项,该选项会列出所有Linux线程。在top运行时,你也可以通过按“H”键将线程查看模式切换为开或关。

触发JVM进行Full GC的情况及应对策略

https://blog.csdn.net/chenleixing/article/details/46706039

  1. System.gc()方法的调用**
  2. 老年代空间不足
  3. 永久代空间不足
  4. CMS GC时出现promotion failed和concurrent mode failure
  5. 统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间

Top-K

https://blog.csdn.net/aninstein/article/details/112458257?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/165541.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Es6的Promise

Promise是异步编程的一种解决方案。简单来说Promise就是一个用来存储数据的对象&#xff0c;它有着一套特殊的存取数据的方式。可以解决异步回调函数/回调地狱问题。创建Promise1.创建Promise时需要一个 回调函数 作为参数这个回调函数会在Promise时 自动调用2.调用回调函数时&…

基于matlab的指纹图像处理、脊线增强、脊线分割、脊线细化、细节点检测和细节点验证

需求分析对于指纹的特征提取包含几个步骤&#xff0c;脊线增强、脊线分割、脊线细化、细节点检测和细节点验证&#xff0c;本次大作业需要针对已经增强的指纹图片进行后续几个步骤&#xff0c;通过多种形态学算法进行分割、细化、细化后处理&#xff0c;找到其中的端点和分叉点…

elasticsearch基础2——es配置文件、jvm配置文件详解

文章目录一、配置文件详解1.1 elasticsearch.yml文件1.1. 1 基础参数1.1.1.1 自定义数据/日志目录1.1.1.2 锁定物理内存1.1.1.3 跨域设置1.1.1.4 其他参数1.1.2 集群类1.1.3 分片类1.1.4 IP绑定类1.1.5 端口类1.1.6 交互类1.1.5 Xpcak安全认证1.1.5.1 xpack内置用户1.1.5.2 xpa…

LabVIEW使用VI脚本向VI添加对象

LabVIEW使用VI脚本向VI添加对象可使用VI脚本向前面板和程序框图添加对象。该教程以向程序框图添加对象为例。按照下列步骤&#xff0c;通过VI脚本向VI添加对象。创建VI前&#xff0c;需先了解VI脚本的基本内容。必须启用VI脚本&#xff0c;才能显示VI脚本选板&#xff0c;使用相…

aws beanstalk 理解和使用eb工作线程环境

参考资料 beanstalk 工作线程环境beanstalk 工作线程环境管理https://catalog.us-east-1.prod.workshops.aws/workshops/b317e4f5-cb38-4587-afb1-2f75be25b2c0/en-US/03-provisionresources 理解 beanstalk 工作线程环境 https://docs.amazonaws.cn/elasticbeanstalk/latest…

【Java基础】-【线程】

文章目录创建线程的方式Thread类的常用方法run()和start()有什么区别&#xff1f;线程是否可以重复启动&#xff0c;有什么后果&#xff1f;线程的生命周期实现线程同步Java多线程之间的通信方式sleep()和wait()的区别notify()、notifyAll()的区别如何实现子线程先执行&#xf…

GaussDB(DWS)数据库的数据迁移实操【玩转PB级数仓GaussDB(DWS)】

GaussDB(DWS)数据库的数据迁移实操【玩转PB级数仓GaussDB(DWS)】 1.1先了解一下Gauss数据库 Gauss数据库并非完全自主开发。它可以看作是基于PostgreSQL 9.2的一次神奇的修改。正如Redhat和Android都源于LINUX的研发&#xff0c;IBM AIX和IOS都源于UNIX的研发一样&#xff0c;…

16、ThingsBoard-配置OAuth2

1、概述 如果你的系统想要接入第三方认证来登录,就像国内很多网站都支持微信、QQ等授权登录,其实thingsboard也提供了OAuth2.0来支持,ThingsBoard 是支持授权码授权类型来交换访问令牌的授权码,同时它自己也提供了几种方式 Google、GitHub、Facebook、Apple 同时也支持自定…

使用numpy进行深度学习代码实战

使用方法定义网络from net import ConvNet net ConvNet() if not net.load(MODEL_PATH): net.addConvLayout([3,3,1,4],bias True,paddingVAILD,init_typeinit_type,st_funcLEAKY_RELU_0.01)net.addConvLayout([3,3,4,8],bias True,paddingVAILD,init_typeinit_type,st…

weblogic反序列化之T3协议

前言 weblogic 的反序列化漏洞分为两种 &#xff0c;一种是基于T3 协议的反序列化漏洞&#xff0c;一个是基于XML的反序列化漏洞&#xff0c;这篇来分析一下基于T3 协议的反序列化漏洞。 环境搭建&#xff1a; [JAVA安全]weblogic反序列化介绍及环境搭建_snowlyzz的博客-CSDN…

Virtualbox设置固定IP

Virtualbox桥接实现静态固定IP内外网访问 super_kancy 2018-10-20 11:55:28 6024 收藏 7 展开 桥接实现静态固定IP内外网访问 第一步、安装好一个虚拟机linux01 第二步、配置网卡&#xff0c;选择桥接网卡模式&#xff0c;并且指定桥接的具体的网卡 第三步、正常启动虚拟机lin…

【Java集合】Collection 体系集合详解(ArrayList,LinkedList,HashSet,TreeSet...)

文章目录1. 概念2. 集合和数组的区别3. 集合的体系结构4. Collection父接口5. List 子接口6. List 实现类6.1 ArrayList 类6.2 Vector 类6.3 LinkedList 类6.4 ArrayList和LinkedList的区别7. Set 子接口8. Set 实现类8.1 HashSet 类8.2 TreeSet 类9. Collections 工具类Java编…

【链表经典题目】总结篇

【链表经典题目】总结篇1 虚拟头结点2 链表的基本操作3 反转链表4 删除倒数第N个节点5 链表相交6 环形链表总结【链表】关于链表&#xff0c;你该了解这些&#xff01; 1 虚拟头结点 在链表&#xff1a;听说用虚拟头节点会方便很多&#xff1f; 中&#xff0c;我们讲解了链表…

简单了解OSI网络模型

本文为学习笔记&#xff0c;根据了解需求摘抄自下篇文章 参考&#xff1a;原文地址 作者&#xff1a;sunsky303 目录 OSI模型 TCP/IP分层模型 OSI模型 OSI 模型(Open System Interconnection model)&#xff08;七层网络模型&#xff09;是一个由国际标准化组织提出的概念模…

职责链模式

职责链模式 1.职责链模式基本介绍 职责链模式&#xff08;Chain of Responsibility Pattern&#xff09;, 又叫 责任链模式&#xff0c;为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。 职责链模式通常每个接收者都包含对另一个接收者…

谷歌搜索引擎排名规则(谷歌 seo 外链重要还是内容重要)

谷歌外链仍然是Google排名前三的因素之一&#xff0c;这意味着你根本不能忽视外链带来的排名。如果不建立高质量的链接&#xff0c;现实情况是&#xff0c;你的竞争性关键字和搜索词不会有高排名的。 并非所有外链都是平等的。事实上&#xff0c;错误类型的链接可能会损害您的…

一次线上事故,我顿悟了MongoDB的精髓

目录MongoDB拒绝连接&#xff1f;显然是MongoDB服务又挂了。mongodb启动异常&#xff1a;about to fork child process, waiting until server is ready for connection一、什么是MongoDB分片&#xff1f;二、MongoDB如何分片?三、何时分片&#xff1f;四、搭建MongoDB分片服务…

算法:链表(力扣+牛客经典题)

链表 力扣 203. 移除链表元素 思路&#xff1a;使用while循环每找到指定的值&#xff0c;就把下一个节点指向下下个节点的位置 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int…

【指针笔试题下】你知道大厂面试题的指针题是什么样的吗?快来通过这些面试题目检测一下自己吧!

目录 前言 笔试题1&#xff1a; 笔试题2&#xff1a; 笔试题3&#xff1a; 笔试题4&#xff1a; 笔试题5&#xff1a; 笔试题6&#xff1a; 笔试题7&#xff1a; 笔试题8&#xff1a; 总结&#xff1a; 博客主页&#xff1a;张栩睿的博客主页 欢迎关注&#xff1a;点赞收藏留…

JVM--Garbage First(G1) 垃圾收集器

G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。G1垃圾回收器是在Java7 update 4之后引入的一个新的垃圾回收器&#xff0c;在 JDK9 中更被指定为官方GC收集器一、G1…