java虚拟机 订阅
虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。 展开全文
虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
信息
外文名
Java Virtual Machine
简    称
JVM
特    点
与平台的无关性
中文名
java虚拟机
实    质
是一种抽象化的计算机
途    径
在实际计算机上模拟各种功能
java虚拟机定义
Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java语言的运行环境,它是Java 最具吸引力的特性之一。
收起全文
精华内容
参与话题
问答
  • Java虚拟机(JVM)你只要看这一篇就够了!

    万次阅读 多人点赞 2018-08-14 12:55:02
    根据《Java 虚拟机规范(Java SE 7 版)》规定,Java 虚拟机所管理的内存如下图所示。 1.1.1 程序计数器 内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节...

    本文是学习了《深入理解Java虚拟机》之后的总结,主要内容都来自于书中,也有作者的一些理解。一是为了梳理知识点,归纳总结,二是为了分享交流,如有错误之处还望指出。

    用XMind画了一张导图(源文件对部分节点有详细备注和参考资料,需要的朋友可以关注我的微信公众号:Java团长,然后回复“JVM”获取):

    1. Java 内存区域与内存溢出异常

    1.1 运行时数据区域

    根据《Java 虚拟机规范(Java SE 7 版)》规定,Java 虚拟机所管理的内存如下图所示。

     

    1.1.1 程序计数器

    内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成

    如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

    1.1.2 Java 虚拟机栈

    线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

    局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)

    StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
    OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。

    1.1.3 本地方法栈

    区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。

    1.1.4 Java 堆

    对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。

    OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。

    1.1.5 方法区

    属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    现在用一张图来介绍每个区域存储的内容。

     

    1.1.6 运行时常量池

    属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。

    1.1.7 直接内存

    非虚拟机运行时数据区的部分

    在 JDK 1.4 中新加入 NIO (New Input/Output) 类,引入了一种基于通道(Channel)和缓存(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。可以避免在 Java 堆和 Native 堆中来回的数据耗时操作。
    OutOfMemoryError:会受到本机内存限制,如果内存区域总和大于物理内存限制从而导致动态扩展时出现该异常。

    1.2 HotSpot 虚拟机对象探秘

    主要介绍数据是如何创建、如何布局以及如何访问的。

    1.2.1 对象的创建

    创建过程比较复杂,建议看书了解,这里提供个人的总结。

    遇到 new 指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,执行相应的类加载。

    类加载检查通过之后,为新对象分配内存(内存大小在类加载完成后便可确认)。在堆的空闲内存中划分一块区域(‘指针碰撞-内存规整’或‘空闲列表-内存交错’的分配方式)。

    前面讲的每个线程在堆中都会有私有的分配缓冲区(TLAB),这样可以很大程度避免在并发情况下频繁创建对象造成的线程不安全。

    内存空间分配完成后会初始化为 0(不包括对象头),接下来就是填充对象头,把对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存入对象头。

    执行 new 指令后执行 init 方法后才算一份真正可用的对象创建完成。

    1.2.2 对象的内存布局

    在 HotSpot 虚拟机中,分为 3 块区域:对象头(Header)实例数据(Instance Data)对齐填充(Padding)

    对象头(Header):包含两部分,第一部分用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32 位虚拟机占 32 bit,64 位虚拟机占 64 bit。官方称为 ‘Mark Word’。第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例。另外,如果是 Java 数组,对象头中还必须有一块用于记录数组长度的数据,因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以。

    实例数据(Instance Data):程序代码中所定义的各种类型的字段内容(包含父类继承下来的和子类中定义的)。

    对齐填充(Padding):不是必然需要,主要是占位,保证对象大小是某个字节的整数倍。

    1.2.3 对象的访问定位

    使用对象时,通过栈上的 reference 数据来操作堆上的具体对象。

    通过句柄访问

    Java 堆中会分配一块内存作为句柄池。reference 存储的是句柄地址。详情见图。

     

    使用直接指针访问

    reference 中直接存储对象地址

     

    比较:使用句柄的最大好处是 reference 中存储的是稳定的句柄地址,在对象移动(GC)是只改变实例数据指针地址,reference 自身不需要修改。直接指针访问的最大好处是速度快,节省了一次指针定位的时间开销。如果是对象频繁 GC 那么句柄方法好,如果是对象频繁访问则直接指针访问好。

    1.3 实战

    // 待填

    2. 垃圾回收器与内存分配策略

    2.1 概述

    程序计数器、虚拟机栈、本地方法栈 3 个区域随线程生灭(因为是线程私有),栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。而 Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期才知道那些对象会创建,这部分内存的分配和回收都是动态的,垃圾回收期所关注的就是这部分内存。

    2.2 对象已死吗?

    在进行内存回收之前要做的事情就是判断那些对象是‘死’的,哪些是‘活’的。

    2.2.1 引用计数法

    给对象添加一个引用计数器。但是难以解决循环引用问题。

     

    从图中可以看出,如果不下小心直接把 Obj1-reference 和 Obj2-reference 置 null。则在 Java 堆当中的两块内存依然保持着互相引用无法回收。

    2.2.2 可达性分析法

    通过一系列的 ‘GC Roots’ 的对象作为起始点,从这些节点出发所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连的时候说明对象不可用。

     

    可作为 GC Roots 的对象:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中 JNI(即一般说的 Native 方法) 引用的对象

    2.2.3 再谈引用

    前面的两种方式判断存活时都与‘引用’有关。但是 JDK 1.2 之后,引用概念进行了扩充,下面具体介绍。

    下面四种引用强度一次逐渐减弱

    强引用

    类似于 Object obj = new Object(); 创建的,只要强引用在就不回收。

    软引用

    SoftReference 类实现软引用。在系统要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。

    弱引用

    WeakReference 类实现弱引用。对象只能生存到下一次垃圾收集之前。在垃圾收集器工作时,无论内存是否足够都会回收掉只被弱引用关联的对象。

    虚引用

    PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

    2.2.4 生存还是死亡

    即使在可达性分析算法中不可达的对象,也并非是“facebook”的,这时候它们暂时出于“缓刑”阶段,一个对象的真正死亡至少要经历两次标记过程:如果对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

    如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象竟会放置在一个叫做 F-Queue 的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运行结束。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象简历关联即可。

    finalize() 方法只会被系统自动调用一次。

    2.2.5 回收方法区

    在堆中,尤其是在新生代中,一次垃圾回收一般可以回收 70% ~ 95% 的空间,而永久代的垃圾收集效率远低于此。

    永久代垃圾回收主要两部分内容:废弃的常量和无用的类。

    判断废弃常量:一般是判断没有该常量的引用。

    判断无用的类:要以下三个条件都满足

    • 该类所有的实例都已经回收,也就是 Java 堆中不存在该类的任何实例
    • 加载该类的 ClassLoader 已经被回收
    • 该类对应的 java.lang.Class 对象没有任何地方呗引用,无法在任何地方通过反射访问该类的方法

    2.3 垃圾回收算法

    仅提供思路

    2.3.1 标记 —— 清除算法

    直接标记清除就可。

    两个不足:

    • 效率不高
    • 空间会产生大量碎片

    2.3.2 复制算法

    把空间分成两块,每次只对其中一块进行 GC。当这块内存使用完时,就将还存活的对象复制到另一块上面。

    解决前一种方法的不足,但是会造成空间利用率低下。因为大多数新生代对象都不会熬过第一次 GC。所以没必要 1 : 1 划分空间。可以分一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 上,最后清理 Eden 和 Survivor 空间。大小比例一般是 8 : 1 : 1,每次浪费 10% 的 Survivor 空间。但是这里有一个问题就是如果存活的大于 10% 怎么办?这里采用一种分配担保策略:多出来的对象直接进入老年代。

    2.3.3 标记-整理算法

    不同于针对新生代的复制算法,针对老年代的特点,创建该算法。主要是把存活对象移到内存的一端。

    2.3.4 分代回收

    根据存活对象划分几块内存区,一般是分为新生代和老年代。然后根据各个年代的特点制定相应的回收算法。

    新生代

    每次垃圾回收都有大量对象死去,只有少量存活,选用复制算法比较合理。

    老年代

    老年代中对象存活率较高、没有额外的空间分配对它进行担保。所以必须使用 标记 —— 清除 或者 标记 —— 整理 算法回收。

    2.4 HotSpot 的算法实现

    // 待填

    2.5 垃圾回收器

    收集算法是内存回收的理论,而垃圾回收器是内存回收的实践。

     

    说明:如果两个收集器之间存在连线说明他们之间可以搭配使用。

    2.5.1 Serial 收集器

    这是一个单线程收集器。意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束。

     

    2.5.2 ParNew 收集器

    可以认为是 Serial 收集器的多线程版本。

     

    并行:Parallel

    指多条垃圾收集线程并行工作,此时用户线程处于等待状态

    并发:Concurrent

    指用户线程和垃圾回收线程同时执行(不一定是并行,有可能是交叉执行),用户进程在运行,而垃圾回收线程在另一个 CPU 上运行。

    2.5.3 Parallel Scavenge 收集器

    这是一个新生代收集器,也是使用复制算法实现,同时也是并行的多线程收集器。

    CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程所停顿的时间,而 Parallel Scavenge 收集器的目的是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。

    作为一个吞吐量优先的收集器,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整停顿时间。这就是 GC 的自适应调整策略(GC Ergonomics)。

    2.5.4 Serial Old 收集器

    收集器的老年代版本,单线程,使用 标记 —— 整理

     

    2.5.5 Parallel Old 收集器

    Parallel Old 是 Parallel Scavenge 收集器的老年代版本。多线程,使用 标记 —— 整理

     

    2.5.6 CMS 收集器

    CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。基于 标记 —— 清除 算法实现。

    运作步骤:

    1. 初始标记(CMS initial mark):标记 GC Roots 能直接关联到的对象
    2. 并发标记(CMS concurrent mark):进行 GC Roots Tracing
    3. 重新标记(CMS remark):修正并发标记期间的变动部分
    4. 并发清除(CMS concurrent sweep)

     

    缺点:对 CPU 资源敏感、无法收集浮动垃圾、标记 —— 清除 算法带来的空间碎片

    2.5.7 G1 收集器

    面向服务端的垃圾回收器。

    优点:并行与并发、分代收集、空间整合、可预测停顿。

    运作步骤:

    1. 初始标记(Initial Marking)
    2. 并发标记(Concurrent Marking)
    3. 最终标记(Final Marking)
    4. 筛选回收(Live Data Counting and Evacuation)

     

    2.6 内存分配与回收策略

    2.6.1 对象优先在 Eden 分配

    对象主要分配在新生代的 Eden 区上,如果启动了本地线程分配缓冲区,将线程优先在 (TLAB) 上分配。少数情况会直接分配在老年代中。

    一般来说 Java 堆的内存模型如下图所示:
     

     

    新生代 GC (Minor GC)

    发生在新生代的垃圾回收动作,频繁,速度快。

    老年代 GC (Major GC / Full GC)

    发生在老年代的垃圾回收动作,出现了 Major GC 经常会伴随至少一次 Minor GC(非绝对)。Major GC 的速度一般会比 Minor GC 慢十倍以上。

    2.6.2 大对象直接进入老年代

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

    2.6.4 动态对象年龄判定

    2.6.5 空间分配担保

    3. Java 内存模型与线程

     

    3.1 Java 内存模型

    屏蔽掉各种硬件和操作系统的内存访问差异。

     

    3.1.1 主内存和工作内存之间的交互

    操作 作用对象 解释
    lock 主内存 把一个变量标识为一条线程独占的状态
    unlock 主内存 把一个处于锁定状态的变量释放出来,释放后才可被其他线程锁定
    read 主内存 把一个变量的值从主内存传输到线程工作内存中,以便 load 操作使用
    load 工作内存 把 read 操作从主内存中得到的变量值放入工作内存中
    use 工作内存 把工作内存中一个变量的值传递给执行引擎,
    每当虚拟机遇到一个需要使用到变量值的字节码指令时将会执行这个操作
    assign 工作内存 把一个从执行引擎接收到的值赋接收到的值赋给工作内存的变量,
    每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
    store 工作内存 把工作内存中的一个变量的值传送到主内存中,以便 write 操作
    write 工作内存 把 store 操作从工作内存中得到的变量的值放入主内存的变量中

    3.1.2 对于 volatile 型变量的特殊规则

    关键字 volatile 是 Java 虚拟机提供的最轻量级的同步机制。

    一个变量被定义为 volatile 的特性:

    1. 保证此变量对所有线程的可见性。但是操作并非原子操作,并发情况下不安全。

    如果不符合 运算结果并不依赖变量当前值,或者能够确保只有单一的线程修改变量的值变量不需要与其他的状态变量共同参与不变约束 就要通过加锁(使用 synchronize 或 java.util.concurrent 中的原子类)来保证原子性。

    1. 禁止指令重排序优化。

    通过插入内存屏障保证一致性。

    3.1.3 对于 long 和 double 型变量的特殊规则

    Java 要求对于主内存和工作内存之间的八个操作都是原子性的,但是对于 64 位的数据类型,有一条宽松的规定:允许虚拟机将没有被 volatile 修饰的 64 位数据的读写操作划分为两次 32 位的操作来进行,即允许虚拟机实现选择可以不保证 64 位数据类型的 load、store、read 和 write 这 4 个操作的原子性。这就是 long 和 double 的非原子性协定。

    3.1.4 原子性、可见性与有序性

    回顾下并发下应该注意操作的那些特性是什么,同时加深理解。

    • 原子性(Atomicity)

    由 Java 内存模型来直接保证的原子性变量操作包括 read、load、assign、use、store 和 write。大致可以认为基本数据类型的操作是原子性的。同时 lock 和 unlock 可以保证更大范围操作的原子性。而 synchronize 同步块操作的原子性是用更高层次的字节码指令 monitorenter 和 monitorexit 来隐式操作的。

    • 可见性(Visibility)

    是指当一个线程修改了共享变量的值,其他线程也能够立即得知这个通知。主要操作细节就是修改值后将值同步至主内存(volatile 值使用前都会从主内存刷新),除了 volatile 还有 synchronize 和 final 可以保证可见性。同步块的可见性是由“对一个变量执行 unlock 操作之前,必须先把此变量同步会主内存中( store、write 操作)”这条规则获得。而 final 可见性是指:被 final 修饰的字段在构造器中一旦完成,并且构造器没有把 “this” 的引用传递出去( this 引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那在其他线程中就能看见 final 字段的值。

    • 有序性(Ordering)

    如果在被线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句指“线程内表现为串行的语义”,后半句是指“指令重排”现象和“工作内存与主内存同步延迟”现象。Java 语言通过 volatile 和 synchronize 两个关键字来保证线程之间操作的有序性。volatile 自身就禁止指令重排,而 synchronize 则是由“一个变量在同一时刻指允许一条线程对其进行 lock 操作”这条规则获得,这条规则决定了持有同一个锁的两个同步块只能串行的进入。

    3.1.5 先行发生原则

    也就是 happens-before 原则。这个原则是判断数据是否存在竞争、线程是否安全的主要依据。先行发生是 Java 内存模型中定义的两项操作之间的偏序关系。

    天然的先行发生关系

    规则 解释
    程序次序规则 在一个线程内,代码按照书写的控制流顺序执行
    管程锁定规则 一个 unlock 操作先行发生于后面对同一个锁的 lock 操作
    volatile 变量规则 volatile 变量的写操作先行发生于后面对这个变量的读操作
    线程启动规则 Thread 对象的 start() 方法先行发生于此线程的每一个动作
    线程终止规则 线程中所有的操作都先行发生于对此线程的终止检测
    (通过 Thread.join() 方法结束、 Thread.isAlive() 的返回值检测)
    线程中断规则 对线程 interrupt() 方法调用优先发生于被中断线程的代码检测到中断事件的发生
    (通过 Thread.interrupted() 方法检测)
    对象终结规则 一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始
    传递性 如果操作 A 先于 操作 B 发生,操作 B 先于 操作 C 发生,那么操作 A 先于 操作 C

    3.2 Java 与线程

    3.2.1 线程的实现

    使用内核线程实现

    直接由操作系统内核支持的线程,这种线程由内核完成切换。程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口 —— 轻量级进程(LWP),轻量级进程就是我们通常意义上所讲的线程,每个轻量级进程都有一个内核级线程支持。

     

    使用用户线程实现

    广义上来说,只要不是内核线程就可以认为是用户线程,因此可以认为轻量级进程也属于用户线程。狭义上说是完全建立在用户空间的线程库上的并且内核系统不可感知的。

     

    使用用户线程夹加轻量级进程混合实现

    直接看图

     

    Java 线程实现

    平台不同实现方式不同,可以认为是一条 Java 线程映射到一条轻量级进程。

    3.2.2 Java 线程调度

    协同式线程调度

    线程执行时间由线程自身控制,实现简单,切换线程自己可知,所以基本没有线程同步问题。坏处是执行时间不可控,容易阻塞。

    抢占式线程调度

    每个线程由系统来分配执行时间。

    3.2.3 状态转换

    五种状态:

    • 新建(new)

    创建后尚未启动的线程。

    • 运行(Runable)

    Runable 包括了操作系统线程状态中的 Running 和 Ready,也就是出于此状态的线程有可能正在执行,也有可能正在等待 CPU 为他分配时间。

    • 无限期等待(Waiting)

    出于这种状态的线程不会被 CPU 分配时间,它们要等其他线程显示的唤醒。

    以下方法会然线程进入无限期等待状态:
    1.没有设置 Timeout 参数的 Object.wait() 方法。
    2.没有设置 Timeout 参数的 Thread.join() 方法。
    3.LookSupport.park() 方法。

    • 限期等待(Timed Waiting)

    处于这种状态的线程也不会分配时间,不过无需等待配其他线程显示地唤醒,在一定时间后他们会由系统自动唤醒。

    以下方法会让线程进入限期等待状态:
    1.Thread.sleep() 方法。
    2.设置了 Timeout 参数的 Object.wait() 方法。
    3.设置了 Timeout 参数的 Thread.join() 方法。
    4.LockSupport.parkNanos() 方法。
    5.LockSupport.parkUntil() 方法。

    • 阻塞(Blocked)

    线程被阻塞了,“阻塞状态”和“等待状态”的区别是:“阻塞状态”在等待着获取一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。

    • 结束(Terminated)

    已终止线程的线程状态。

     

    4. 线程安全与锁优化

    // 待填

    5. 类文件结构

    // 待填

    有点懒了。。。先贴几个网址吧。

    1. Official:The class File Format
    2.亦山: 《Java虚拟机原理图解》 1.1、class文件基本组织结构

    6. 虚拟机类加载机制

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

    在 Java 语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的。

    6.1 类加载时机

    类的生命周期( 7 个阶段)

     

    其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的。解析阶段可以在初始化之后再开始(运行时绑定或动态绑定或晚期绑定)。

    以下五种情况必须对类进行初始化(而加载、验证、准备自然需要在此之前完成):

    1. 遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时没初始化触发初始化。使用场景:使用 new 关键字实例化对象、读取一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)、调用一个类的静态方法。
    2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候。
    3. 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需先触发其父类的初始化。
    4. 当虚拟机启动时,用户需指定一个要加载的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类。
    5. 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需先触发其初始化。

    前面的五种方式是对一个类的主动引用,除此之外,所有引用类的方法都不会触发初始化,佳作被动引用。举几个例子~

    public class SuperClass {
        static {
            System.out.println("SuperClass init!");
        }
        public static int value = 1127;
    }
    
    public class SubClass extends SuperClass {
        static {
            System.out.println("SubClass init!");
        }
    }
    
    public class ConstClass {
        static {
            System.out.println("ConstClass init!");
        }
        public static final String HELLOWORLD = "hello world!"
    }
    
    public class NotInitialization {
        public static void main(String[] args) {
            System.out.println(SubClass.value);
            /**
             *  output : SuperClass init!
             * 
             * 通过子类引用父类的静态对象不会导致子类的初始化
             * 只有直接定义这个字段的类才会被初始化
             */
    
            SuperClass[] sca = new SuperClass[10];
            /**
             *  output : 
             * 
             * 通过数组定义来引用类不会触发此类的初始化
             * 虚拟机在运行时动态创建了一个数组类
             */
    
            System.out.println(ConstClass.HELLOWORLD);
            /**
             *  output : 
             * 
             * 常量在编译阶段会存入调用类的常量池当中,本质上并没有直接引用到定义类常量的类,
             * 因此不会触发定义常量的类的初始化。
             * “hello world” 在编译期常量传播优化时已经存储到 NotInitialization 常量池中了。
             */
        }
    }

    6.2 类的加载过程

    6.2.1 加载

    1. 通过一个类的全限定名来获取定义次类的二进制流(ZIP 包、网络、运算生成、JSP 生成、数据库读取)。
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法去这个类的各种数据的访问入口。

    数组类的特殊性:数组类本身不通过类加载器创建,它是由 Java 虚拟机直接创建的。但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型最终是要靠类加载器去创建的,数组创建过程如下:

    1. 如果数组的组件类型是引用类型,那就递归采用类加载加载。
    2. 如果数组的组件类型不是引用类型,Java 虚拟机会把数组标记为引导类加载器关联。
    3. 数组类的可见性与他的组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性将默认为 public。

    内存中实例的 java.lang.Class 对象存在方法区中。作为程序访问方法区中这些类型数据的外部接口。
    加载阶段与连接阶段的部分内容是交叉进行的,但是开始时间保持先后顺序。

    6.2.2 验证

    是连接的第一步,确保 Class 文件的字节流中包含的信息符合当前虚拟机要求。

    文件格式验证

    1. 是否以魔数 0xCAFEBABE 开头
    2. 主、次版本号是否在当前虚拟机处理范围之内
    3. 常量池的常量是否有不被支持常量的类型(检查常量 tag 标志)
    4. 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
    5. CONSTANT_Utf8_info 型的常量中是否有不符合 UTF8 编码的数据
    6. Class 文件中各个部分集文件本身是否有被删除的附加的其他信息
    7. ……

    只有通过这个阶段的验证后,字节流才会进入内存的方法区进行存储,所以后面 3 个验证阶段全部是基于方法区的存储结构进行的,不再直接操作字节流。

    元数据验证

    1. 这个类是否有父类(除 java.lang.Object 之外)
    2. 这个类的父类是否继承了不允许被继承的类(final 修饰的类)
    3. 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
    4. 类中的字段、方法是否与父类产生矛盾(覆盖父类 final 字段、出现不符合规范的重载)

    这一阶段主要是对类的元数据信息进行语义校验,保证不存在不符合 Java 语言规范的元数据信息。

    字节码验证

    1. 保证任意时刻操作数栈的数据类型与指令代码序列都鞥配合工作(不会出现按照 long 类型读一个 int 型数据)
    2. 保证跳转指令不会跳转到方法体以外的字节码指令上
    3. 保证方法体中的类型转换是有效的(子类对象赋值给父类数据类型是安全的,反过来不合法的)
    4. ……

    这是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段对类的方法体进行校验分析,保证校验类的方法在运行时不会做出危害虚拟机安全的事件。

    符号引用验证

    1. 符号引用中通过字符创描述的全限定名是否能找到对应的类
    2. 在指定类中是否存在符方法的字段描述符以及简单名称所描述的方法和字段
    3. 符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问
    4. ……

    最后一个阶段的校验发生在迅疾将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,还有以上提及的内容。
    符号引用的目的是确保解析动作能正常执行,如果无法通过符号引用验证将抛出一个 java.lang.IncompatibleClass.ChangeError 异常的子类。如 java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError 等。

    6.2.3 准备

    这个阶段正式为类分配内存并设置类变量初始值,内存在方法去中分配(含 static 修饰的变量不含实例变量)。

    public static int value = 1127;
    这句代码在初始值设置之后为 0,因为这时候尚未开始执行任何 Java 方法。而把 value 赋值为 1127 的 putstatic 指令是程序被编译后,存放于 clinit() 方法中,所以初始化阶段才会对 value 进行赋值。

    基本数据类型的零值

    数据类型 零值 数据类型 零值
    int 0 boolean false
    long 0L float 0.0f
    short (short) 0 double 0.0d
    char '\u0000' reference null
    byte (byte) 0  

    特殊情况:如果类字段的字段属性表中存在 ConstantValue 属性,在准备阶段虚拟机就会根据 ConstantValue 的设置将 value 赋值为 1127。

    6.2.4 解析

    这个阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

    1. 符号引用
      符号引用以一组符号来描述所引用的目标,符号可以使任何形式的字面量。
    2. 直接引用
      直接引用可以使直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用和迅疾的内存布局实现有关

    解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行,分别对应于常量池的 7 中常量类型。

    6.2.5 初始化

    前面过程都是以虚拟机主导,而初始化阶段开始执行类中的 Java 代码。

    6.3 类加载器

    通过一个类的全限定名来获取描述此类的二进制字节流。

    6.3.1 双亲委派模型

    从 Java 虚拟机角度讲,只存在两种类加载器:一种是启动类加载器(C++ 实现,是虚拟机的一部分);另一种是其他所有类的加载器(Java 实现,独立于虚拟机外部且全继承自 java.lang.ClassLoader)

    1. 启动类加载器
      加载 lib 下或被 -Xbootclasspath 路径下的类

    2. 扩展类加载器
      加载 lib/ext 或者被 java.ext.dirs 系统变量所指定的路径下的类

    3. 引用程序类加载器
      ClassLoader负责,加载用户路径上所指定的类库。

     

    除顶层启动类加载器之外,其他都有自己的父类加载器。
    工作过程:如果一个类加载器收到一个类加载的请求,它首先不会自己加载,而是把这个请求委派给父类加载器。只有父类无法完成时子类才会尝试加载。

    6.3.2 破坏双亲委派模型

    keyword:线程上下文加载器(Thread Context ClassLoader)

    最后

    前面两次粗略的阅读,能理解内容,但是很难记住细节。每每碰到不会的知识点就上网查,所以知识点太碎片脑子里没有体系不仅更不容易记住,而且更加容易混乱。但是通过这种方式记录发现自己清晰了很多,就算以后忘记,知识再次捡起的成本也低了很多。

    这次还有一些章节虽然阅读了,但是还未完成记录。等自己理解深刻有空闲了就再次记录下来,这里的内容均出自周志明老师的《深入理解 Java 虚拟机》,有兴趣的可以入手纸质版。

    展开全文
  • Java虚拟机

    2019-11-25 19:08:19
    一、Java虚拟机 1.1 Java程序执行流程 Java程序的执行依赖于编译环境和运行环境。源码代码转变成可执行的机器代码,由下面的流程完成: Java技术的核心就是Java虚拟机,因为所有的Java程序都在虚拟机上运行。...

    一、Java虚拟机

    1.1 Java程序执行流程

    Java程序的执行依赖于编译环境和运行环境。源码代码转变成可执行的机器代码,由下面的流程完成:
    在这里插入图片描述
    Java技术的核心就是Java虚拟机,因为所有的Java程序都在虚拟机上运行。Java程序的运行需要Java虚拟机、Java API和Java Class文件的配合。Java虚拟机实例负责运行一个Java程序。当启动一个Java程序时,一个虚拟机实例就诞生了。当程序结束,这个虚拟机实例也就消亡。
    在这里插入图片描述

    Java的跨平台特性,因为它有针对不同平台的虚拟机。

    1.2 Java虚拟机

    Java虚拟机的主要任务是装载class文件并且执行其中的字节码。由下图可以看出,Java虚拟机包含一个类装载器(class loader),它可以从程序和API中装载class文件,Java API中只有程序执行时需要的类才会被装载,字节码由执行引擎来执行。
    在这里插入图片描述
    当Java虚拟机由主机操作系统上的软件实现时,Java程序通过调用本地方法和主机进行交互。Java方法由Java语言编写,编译成字节码,存储在class文件中。本地方法由C/C++/汇编语言编写,编译成和处理器相关的机器代码,存储在动态链接库中,格式是各个平台专有。所以本地方法是联系Java程序和底层主机操作系统的连接方式。

    由于Java虚拟机并不知道某个class文件是如何被创建的,是否被篡改一无所知,所以它实现了一个class文件检测器,确保class文件中定义的类型可以安全地使用。class文件检验器通过四趟独立的扫描来保证程序的健壮性:

    • class文件的结构检查
    • 类型数据的语义检查
    • 字节码验证
    • 符号引用验证

    Java虚拟机在执行字节码时还进行其它的一些内置的安全机制的操作,他们作为Java编程语言保证Java程序健壮性的特性,同时也是Java虚拟机的特性:

    • 类型安全的引用转换
    • 结构化的内存访问
    • 自动垃圾收集
    • 数组边界检查
    • 空引用检查

    1.3 Java虚拟机数据类型

    Java虚拟机通过某些数据类型来执行计算。数据类型可以分为两种:基本类型和引用类型,如下图:
    在这里插入图片描述

    二、体系结构

    Java对象的生命周期

         在Java中,对象的生命周期包括以下几个阶段:
    
    1.  创建阶段(Created)
      
    2.  应用阶段(In Use)
      
    3.  不可见阶段(Invisible)
      
    4.  不可达阶段(Unreachable)
      
    5.  收集阶段(Collected)
      
    6.  终结阶段(Finalized)
      
    7.  对象空间重分配阶段(De-allocated)
      

    1.创建阶段(Created)

    在创建阶段系统通过下面的几个步骤来完成对象的创建过程

    l 为对象分配存储空间
    l 开始构造对象
    l 从超类到子类对static成员进行初始化
    l 超类成员变量按顺序初始化,递归调用超类的构造方法
    l 子类成员变量按顺序初始化,子类构造方法调用
    一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段

    2.应用阶段(In Use)

    对象至少被一个强引用持有着。

    3.不可见阶段(Invisible)

    当一个对象处于不可见阶段时,说明程序本身不再持有该对象的任何强引用,虽然该这些引用仍然是存在着的。

    简单说就是程序的执行已经超出了该对象的作用域了。

    举例如下图:本地变量count在25行时已经超出了其作用域,则在此时称之为count处于不可视阶段。当然这种情况编译器在编译的过程中会直接报错了。

    在这里插入图片描述

    4.不可达阶段(Unreachable)

    对象处于不可达阶段是指该对象不再被任何强引用所持有。
    与“不可见阶段”相比,“不可见阶段”是指程序不再持有该对象的任何强引用,这种情况下,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GC root会导致对象的内存泄露情况,无法被回收。

    5.收集阶段(Collected)

    当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了finalize()方法,则会去执行该方法的终端操作。

    这里要特别说明一下:不要重载finazlie()方法!原因有两点:

    1、 会影响JVM的对象分配与回收速度
    在分配该对象时,JVM需要在垃圾回收器上注册该对象,以便在回收时能够执行该重载方法;在该方法的执行时需要消耗CPU时间且在执行完该方法后才会重新执行回收操作,即至少需要垃圾回收器对该对象执行两次GC。
    2 、可能造成该对象的再次“复活”
    在finalize()方法中,如果有其它的强引用再次持有该对象,则会导致对象的状态由“收集阶段”又重新变为“应用阶段”。这个已经破坏了Java对象的生命周期进程,且“复活”的对象不利用后续的代码管理。

    6.终结阶段

    当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。

    7.对象空间重新分配阶段

    垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段”。

    垃圾回收的时间

    当一个对象处于可复活状态时,垃圾回收线程执行它的finalize()方法,任何使它转到不可触及状态,任何回收它占用的内存,这对于程序来说都是透明的。程序只能决定一个对象任何不再被任何引用变量引用,使得它成为可以被回收的垃圾。

    类比:居民把无用物品放在指定的地方,清洁工人会把它收拾走。但垃圾被收走的时间,居民是不知道的,也无需了解。

    垃圾回收器作为低优先级线程独立运行。在任何时候,程序都无法迫使垃圾回收器立即执行垃圾回收操作。
    

    程序中可调用System.gc()或Runtime.gc()方法提示垃圾回收器尽快执行垃圾回收操作,但是不能保证调用后垃圾回收器会立即执行垃圾回收。

    类比:小区垃圾成堆时,居民打电话给环保局,催促清洁工尽快来处理垃圾。但是清洁工不一定立即就来了,也有可能很长时间后再来。
    

    对象的finalize()方法简介

    finalize()定义在Object类中:
    protected void finalize() throws Throwable
    因为该方法为protected,所以任何Java类都可以覆盖finalize()方法,该方法中进行释放对象所占的相关资源的操作。

    注意:

    JVM的垃圾回收操作对程序来说都是透明的。因此程序无法预料某个无用对象的finalize()方法何时被调用。

    finalize()方法的特点:

    垃圾回收器是否会执行该方法及何时执行该方法,都是不确定的。
    finalize()方法有可能使对象复活,使它恢复到可触及状态。
    垃圾回收器在执行finalize()方法时,如果出现异常,垃圾回收器不会报告异常,程序继续正常运行。

    希望这个知识点的分享可以帮助到大家!

    展开全文
  • JAVA虚拟机

    千次阅读 2018-05-10 11:29:59
    1、Java虚拟机是什么? A、抽象规范; B、一个具体实现; C、一个运行的虚拟机实例;2、虚拟机的生命周期 A、Java虚拟机的职责就是负责运行一个Java程序; B、当启动一个Java程序时,Java虚拟机就开始了,当...

    1、Java虚拟机是什么?

        A、抽象规范;

        B、一个具体实现;

        C、一个运行的虚拟机实例;

    2、虚拟机的生命周期

        A、Java虚拟机的职责就是负责运行一个Java程序;

        B、当启动一个Java程序时,Java虚拟机就开始了,当停止一个Java程序是,Java虚拟机就消失了;

        C、如果同时启动多个程序,则同时存在多个虚拟机;

     3、虚拟机的体系结构


        A、类装载器子系统负责加载编写的Class文件或者API里面的Class文件;

        B、执行引起负责执行被类加载器加载后的字节码文件;

        C、方法区和堆被所有线程共享;

            C1、方法区存放字节码文件的内容;

            C2、对存放程序运行时生成的对象;

        D、Java栈+程序计数器(PC寄存器)+本地方法栈属于线程私有的,其他线程不可访问,只有该线程能访问;

            D1、Java栈中存放程序运行时方法调用的状态:局部变量、返回值、传入的参数、运算的中间结果;

            D2、在程序运行时程序计数器的值总是指向下一条将被执行的命令;

            D3、本地方法栈:本地方法的状态以某种依赖于具体实现的方式存放在本地方法栈中;也可能是在寄存器或某些与特定实现相关的内存中;

        E、Java栈有多个帧组成,每个帧存放方法调用的状态;

    展开全文
  • Java虚拟机(JVM)面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:26:32
    HotSpot虚拟机对象探秘对象的创建为对象分配内存处理并发安全问题对象的访问定位句柄访问直接指针内存溢出异常Java会存在内存泄漏吗?请简单描述垃圾收集器简述Java垃圾回收机制GC是什么?为什么要GC垃圾回收的优点...

    大家好,我是CSDN的博主ThinkWon,“2020博客之星年度总评选"开始啦,希望大家帮我投票,每天都可以投多票哦,点击下方链接,然后点击"最大”,再点击"投TA一票"就可以啦!
    投票链接:https://bss.csdn.net/m/topic/blog_star2020/detail?username=thinkwon
    在技术的世界里,ThinkWon将一路与你相伴!创作出更多更高质量的文章!2020为努力奋斗的你点赞👍,️新的一年,祝各位大牛牛气冲天,牛年大吉!😊😊

    文章目录

    Java面试总结汇总,整理了包括Java基础知识,集合容器,并发编程,JVM,常用开源框架Spring,MyBatis,数据库,中间件等,包含了作为一个Java工程师在面试中需要用到或者可能用到的绝大部分知识。欢迎大家阅读,本人见识有限,写的博客难免有错误或者疏忽的地方,还望各位大佬指点,在此表示感激不尽。文章持续更新中…

    序号 内容 链接地址
    1 Java基础知识面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104390612
    2 Java集合容器面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104588551
    3 Java异常面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104390689
    4 并发编程面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104863992
    5 JVM面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104390752
    6 Spring面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104397516
    7 Spring MVC面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104397427
    8 Spring Boot面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104397299
    9 Spring Cloud面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104397367
    10 MyBatis面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/101292950
    11 Redis面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/103522351
    12 MySQL数据库面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104778621
    13 消息中间件MQ与RabbitMQ面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104588612
    14 Dubbo面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104390006
    15 Linux面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104588679
    16 Tomcat面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104397665
    17 ZooKeeper面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104397719
    18 Netty面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/104391081
    19 架构设计&分布式&数据结构与算法面试题(2020最新版) https://thinkwon.blog.csdn.net/article/details/105870730

    Java内存区域

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

    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命令)来完成的。

    在这里插入图片描述

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

    说一下 JVM 运行时数据区

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

    img

    不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 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 中提供的几种对象创建方式:

    Header 解释
    使用new关键字 调用了构造函数
    使用Class的newInstance方法 调用了构造函数
    使用Constructor类的newInstance方法 调用了构造函数
    使用clone方法 没有调用构造函数
    使用反序列化 没有调用构造函数

    下面是对象创建的主要流程:

    img

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

    为对象分配内存

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

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

    选择哪种分配方式是由 Java 堆是否规整来决定的,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

    内存分配的两种方式

    处理并发安全问题

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

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

    内存分配时保证线程安全的两种方式

    对象的访问定位

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

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

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

    句柄访问

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

    img

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

    直接指针

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

    img

    优势:速度更,节省了一次指针定位的时间开销。由于对象的访问在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 没有任何引用链相连时,则证明此对象是可以被回收的。

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

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

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

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

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

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

    标记-清除算法

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

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

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

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

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

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

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

    img

    复制算法

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

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

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

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

    img

    标记-整理算法

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

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

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

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

    img

    分代收集算法

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

    img

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

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

    img

    • 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)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

    详细介绍一下 CMS 垃圾回收器?

    CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。

    CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

    新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?

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

    新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

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

    分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 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类的方式实现。

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

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

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

    什么是双亲委派模型?

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

    类加载器分类:

    • 启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
    • 其他类加载器:
    • 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;
    • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

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

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

    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 详细信息。
    展开全文
  • java虚拟机

    2014-09-30 14:38:32
    什么是Java虚拟机 Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现。Java虚拟机有自己想象中的硬件,如处理器、堆栈、寄存器等,还具有相应的指令系统。 1.为什么要使用Java虚拟机 ...
  • JVM:图文详解Java虚拟机的内存结构

    万次阅读 多人点赞 2019-09-29 07:33:11
    本文将全面讲解Java虚拟机中的内存模型 & 分区,希望你们会喜欢
  • Java 虚拟机

    2009-08-08 19:50:00
    Java 虚拟机Java Virtual Machine,JVM)是由 JDK 提供的一个软件程序。虚拟机的任务是执行 Java 程序 Java 语言之所有有这种跨平台的功能,要归功于 Java 虚拟机Java 虚拟机封装了底层操作系统的差异,不管是...
  • Java虚拟机

    2005-02-28 16:09:00
    Java虚拟机一、什么是Java虚拟机Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现。Java虚拟机有自己想象中的硬件,如处理器、堆栈、寄存器等,还具有相应的指令系统。1.为什么要使用Java虚拟机Java...
  • 文章目录1:Java虚拟机的介绍2:JVM的存在位置3:JVM整体结构4:Java代码执行流程5:JVM指令集的架构模型6:JVM的生命周期7:常见的Java虚拟机7.1 Sun Classic VM7.2 Exact VM7.3 HotSpot VM 1:Java虚拟机的介绍 ...
  • 深入理解java虚拟机.PDF《深入理解java虚拟机》高清PDF, 深入理解java虚拟机,书签,电子书 点我下载 在JavaEye上关注过这本书作者,拜读过他几篇关于虚拟机的文章。这本书一出版的第一时间我就买了,花了差不多1周...
  • java虚拟机java虚拟机的类加载机制

    千次阅读 多人点赞 2016-07-01 08:24:50
    这篇博文主要来总结一下java虚拟机加载一个类的过程,为了避免枯燥的解说,为了让读者在读完本文后能彻底理解类加载的过程,首先来看一段java代码,我们从一个例子入手: //ClassLoaderProcess.java文件 class ...
  • 这个插件不用多介绍,许多需要Java环境的软件必备。现在的网页如果缺少这个插件一般不提示,不能显示表单。此插件为微软java虚拟机,区别于sun java虚拟机
  • 本文为学习笔记,参考《深入理解Java虚拟机:JVM高级特性与最佳实践》一书。该书内容是基于JDK 1.7的,会具有一定的滞后性, 与现在Java虚拟机的状况不一定完全吻合。如想了解现在的Java虚拟机标准可以参考Java...
  • 《深入理解Java虚拟机》个人读书总结——JAVA虚拟机内存
  • Java虚拟机java虚拟机下的进程

    千次阅读 2012-02-22 14:48:29
    一、什么是Java虚拟机  当你谈到Java虚拟机时,你可能是指:  1、抽象的Java虚拟机规范  2、一个具体的Java虚拟机实现  3、一个运行的Java虚拟机实例 二、Java虚拟机的生命周期  一个运行中的Java虚拟机...
  • 这是我在学习周志明的《深入理解Java虚拟机》的第一篇总结,我的目标是每学习一章,总结一次。 Java虚拟机是什么,我们这里就不说了,我们直接进入正题 Java虚拟机运行时数据区域的五大块 Java...
  • 我们知道java之所以能够快速崛起一个重要的原因就是其跨平台性,而跨平台就是通过java虚拟机来完成的,java虚拟机属于java底层的知识范畴,即使你不了解也不会影响绝大部分人从事的java应用层的开发,但是如果你了解...
  • 通过前面两篇博客的铺垫:java虚拟机JVM–java虚拟机的结构, java虚拟机JVM–java虚拟机垃圾的回收机制详解, 本篇将从JVM的内存如何分配的以及内存是如何回收的 角度来介绍java虚拟机的内存管理,来回答这一个遗留下...
  • 文章目录一、引言二、运行时数据区域三、程序计数器四、Java 虚拟机栈五、本地方法栈六、Java 堆七、方法区 一、引言 对于从事 C、C++ 程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的“皇帝”,又...
  • 深入理解Java虚拟机-走近Java

    万次阅读 多人点赞 2020-01-02 15:02:08
    文章目录概述Java技术体系Java发展史Java虚拟机发展史展望Java技术的未来实战:自己编译JDK本章小结 本博客主要参考周志明老师的《深入理解Java虚拟机》第二版 读书是一种跟大神的交流。阅读《深入理解Java虚拟机...
  • Java虚拟机也可以看成一个抽象的计算机(有自己的指令以及各种运行时内存区域),它可以虚拟出一个让Java语言编写出的程序(准确来说能说class文件表达出来的编程语言)能够执行的环境。Java虚拟机可以运行在Li...

空空如也

1 2 3 4 5 ... 20
收藏数 108,763
精华内容 43,505
关键字:

java虚拟机

java 订阅