深入理解java虚拟机-总结

第一部分 略;
第二部分:自动内存管理机制

1运行时数据区域

jdk1.6版本JVM布局分为:heap(堆),method(方法区),stack(虚拟机栈),native stack(本地方法栈),程序计数器共五大区域。
其中方法区包含运行时常量池。堆和方法区是线程共享的,虚拟机栈和本地方法栈、程序计数器是随线程而建的。

1.1 程序计数器
1.2 方法区:

  存储已被jvm加载的类信息(类名,访问修饰符,字段描述,方法描述等)、常量、静态变量、即时编译器编译后的代码(不清楚什么是即时编译器编译后的代码)等数据。也就是说 final/static 的“基本数据类型变量”数据和指针放在方法区,没有final/static 的“基本数据类型变量”数据和指针放在虚拟机栈中。jdk1.6及以前方法区和堆独立区域,jdk1.7方法区中的字符串常量池放在堆中,jdk1.8删除方法区改成元空间(还没太了解)。

1.3 运行时常量池:

存储编译期间生成的各种字面量和符号引用。其中有个知识点是string的instern()方法。

1.4 虚拟机栈:

其实严格来说虚拟机栈包含局部变量表、操作数栈、动态链接、方法出口等信息。其中局部变量表存储八种基本数据类型(byte, boolean, char, short, int, float, long, double)和对象引用。平常讨论的栈都是指的局部变量。

1.5 本地方法栈:

本地方法栈和虚拟机栈作业是十分相似的,只不过虚拟机栈是为虚拟机执行java方法(也就是字节码)服务,而本地方法栈是为虚拟机执行native方法服务。sun hotspot虚拟机本地方法栈和虚拟机栈合二为一。

1.6 程序计数器:

可以看做是当前线程执行字节码的行号指示器。此区域是jvm规范中唯一没有规定任何OOM情况的区域。

1.7 直接内存:

在jdk1.4之后新加入了NIO,可以使用native函数库直接分配堆外内存(本机内存),然后通过一个存储在java堆中的directByteBuffer对象作为这块内存的引用进行操作。避免了io在java堆和native堆来回复制数据提示对鞋性能。直接内存大小默认是java堆xmx(不知道为什么这样设计 容易误导);

2 hotspot虚拟机对象探秘

2.1 对象的创建

  在语言层面上,创建对象(例如克隆、反序列化)通常仅仅是一个new 关键字而已。
  虚拟机遇到一条new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且.检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程,在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,把一块确定大小的内存从Java 堆中划分出来。假设Java 堆中内存是绝对规擎的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞” 。如果Java 堆中的内存并不是规整的,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”。选择哪种分配方式由Java 堆是否规整决定,而Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头〉,如果使用TLAB ,这一工住过程也可以提前至TLAB 分配时进行。这一步操作保证了对象的
  实例字段在Java 代码中可以不赋初始值就直接使用,程停能访问到这些字段的数据类型,所对应的零值。
接下来, 虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希值、对象的GC 分代年龄等信息。这些信息存放在对象的对象头之中。根据虚拟机当前的运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
从虚拟机的视角来看, 一个新的对象已经产生,但从java 程序的视角来看,对象创建在刚刚开始-一-<init> 方法还没有执行,所有的字段都还为零。所以一般来说new 指令之后会接着执行之init> 方法,把对象按照程序员的意愿进往初始化,这样一个真正可用的对象才算完全产生出来。

2.2 对象的内存布局

对象在内存中存储的布局可以分为3 块区域:对象头、实例数据、对齐填充。
对象头
HotSpot 虚拟机的对象头包括两部分信息,
1.用于存储对象自身的运行时数据, HashCode、GC 分代年龄、线程持有的锁、偏向钱程ID 、偏向时间戳等。
2.类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
实例数据
实例数据部分是对象真正存储的有效信息,是在程序代码中所定义的各种类型的字段
对齐填充
对齐填充并不足必然存在的,没特别的含义,仅仅起着位的作用,HotSpotVM的要求对象起始地址是8字节的整数倍,既对象大小是8字节整数倍

2.3 对象的访问定位

程序需要通过栈上的reference(java栈本地变量表的基本数据类型)数据来操作堆上的具体对象。目前主流的对象访问方式是:使用句柄、直接指针
使用句柄:java堆中划出一块内存作为句柄池,reference中存储的是对象的句柄地址,句柄中包含对象实例数据和类型数据。
直接指针:reference中存储的直接是对象地址

3 OutOfMemoryError 异常

3.1、java堆溢出:

Xmx:最大堆内存,Xms:最小堆内存,当Xmx=Xms时堆不扩展。-XX:+HeapDumpOnOutOfMemoryError ;实例化大量对象可测试堆溢出。
OOM解决:增大xmx扩大堆内存

3.2、虚拟机栈和本地方法栈溢出:

栈溢出有俩种情况:
1、栈的深度超过最大深度限制,抛出StackOverflowError异常
2、栈扩展时内存不足,抛出OOM;
Xss:每个栈的大小;Xoss:本地方法栈大小,不过实际上xoss无效,栈容量只由-Xss参数设置。
单线程下只有一个栈,所以只可能出现StackOverflowError,无论是栈的深度太大还是每个栈帧过大到账内存不足;多线程下会出现OOM,不断建线程时会出现内存不足;
OOM/StackOverflowError解决:
1、OOM:由于栈内存=操作系统内存 - 堆内存 - 方法区内存 - 程序计数器内存,所有可以减小堆内存来扩大栈内存大小。栈内存大小影响系统并发线程量(栈内存>=每个栈的大小xss线程量);具体设置由n台服务器,每台服务器m个cpu,则最大线程量=nm;每个栈的大小xss<=栈内存/n*m; 注意一下有个问题 ,这个公式没有直接内存?
2、StackOverflowError深度:如果使用jvm默认设置,栈的深度大多数情况下可达到1000~2000,足以在日常开发中使用。注意避免代码中存在超过1000的方法嵌套。每个方法嵌套对应一个栈帧。
3、StackOverflowError栈帧大小:单线程下避免代码中存在大量基本类型或对象引用。
4、多线程下假设每个栈帧特大,jvm是抛出OOM还是StackOverflowError?(待考察研究)

3.3、方法区和运行时常量池溢出:

-XX:PermSize:最小方法区内存大小;-XX:MaxPermSize:最大方法区内存大小。
OOM解决:避免大量的string.intern();避免大量的动态java(jsp,java反射)。

3.4、本机直接内存溢出:

-XX:MaxDirectMemorySize:最大直接内存;默认为Xmx
OOM解决:使用NIO分配本机内存多注意是否超过MaxDirectMemorySize;平常开发容易忽略直接内存;

第二部分:第三章

垃圾收集器与内存分配策略
就在GC要完成的3件事:
1.哪些内存需要回收?
2.什么时候回收?
3.如何回收?

1 垃圾回收条件

jvm垃圾回收分为俩点,
1.对象的内存回收也就是堆内存回收
2.字面量(运行时常量池)和类信息内存回收也就是方法区内存回收。
对象内存回收(heap)
如何判断对象是否可以回收,市场上有引用计数算法和可达性分析算法两种方法 :

1.1引用计数算法:

  简单来说就是,一个对象A被引用一次,对象A的引用次数就加一。当对象A的引用次数为0(也就是没有其他地方引用此对象)时可以回收。然而这里存在个问题就是 对象之间互相引用 A->B,B->A时 AB无法回收。不过微软公司的com(componet object model)技术用的就是这个算法,而目前流行的主流的jvm没有选用此算法的。

1.2 可达性分析算法:

  简单来说就是,通过一系统成为Gc Roots的对象为起始点,当冲GC Roots到达不了对象A时,则对象A是不可用的,可回收的。
java语言中,GC Roots包括四种对象:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 本地方法栈中(native方法)引用的对象
  3. 方法区中类静态属性引用的对象
  4. 方法区中常量引用的对象
    之所以是上面四种,总结为:GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots
1.3 引用

四大类型引用:分别是 强引用、软引用、弱引用、虚引用。

  1. 强引用:Object obj=new Object();只要强引用还存在,垃圾收集器永远不会回收被引用的对象。假如obj=null或者obj引用的栈帧出栈(不存在)就会被回收。
  2. 软引用:将要发生内存溢出溢出之前,将会把软引用的对象进行回收。SoftReference类实现软引用。
  3. 弱引用:只能生存到下一次gc之前。WeakReference类实现弱引用。
  4. 虚引用:这个需要注意,虚引用无法用来取得一个对象的实例。虚引用不会对对象的生存时间有任何影响,只是能在这个对象被回收时收到一个系统通知。
1.4 方法区内存回收:

  jvm规范不要求虚拟机在方法区实现垃圾收集(因为性价比低,回收一次回收的空间实际情况很小),但是jvm还是有方法区回收的。

  1. 字面量回收:如果一个字面量没有被引用就回被回收;
  2. 类信息回收:类信息回收需要同时满足3个条件:
  3. java堆中不存在此类的实例;
  4. 加载该类的classloader已经被回收
  5. 该类对应的java.lang.Class对象没有任何地方引用,也就是说无法再任何地方通过反射访问该类的方法。
  6. 其他常量池的东西
    finalize() 对象自救
      对象在可达性分析之后还需要进行一些逻辑。哪怕对象在可达性分析之后没有发现与GcRoots的引用链,jvm还需要进行下面几步(需要俩次标记):
    1.没有发现引用链,进行第一次标记
    2.判断对象是否要执行finalize()方法:当对象没有覆盖finalize()方法或者已经被执行过一次finalize()方法;则此对象不执行finalize()方法,否则进行执行finalize()方法(把对象放在F-Quene队列中);
    jvm自动建立的,低优先级的Finalizer线程去执行放在F-Quene的对象里的finalize()方法。
    3.进行第二次标记,判断对象是否有引用链,没有则回收。
    所以如果对象自救(第一次标记没有引用链,第二次标记有引用链),只有在覆盖finalize()方法激活对象(让对象被引用)。

2.垃圾收集算法

jvm垃圾收集可以分为四种(严格来说是三种):标记-清除算法;标记-整理算法;复制算法;分代收集算法。

2.1 标记-清除算法:

先把可回收内存标记,然后在清除掉;缺点是:会存在内存碎片导致内存泄漏。

2.2 标记-整理算法:

先把可回收内存标记,然后让存活的对象都向一端移动,最后清除点端边界以外的内存;缺点:比较标记-清除时间增长;

2.3 复制算法:

把内存平分为两份s1,s2;保证只用一个内存区s1,另一个为空s2;把存活的对象复制到为空的那个内存区域s2,然后在清除掉这个区域s1;缺点:内存减半

2.4 分代收集算法:

根据jvm特性,年轻代实际每次gc时存活对象较少,故用推荐复制算法;年老代存活对象较多,并且没有其他内存为年老代分配担保(分配担保:举个栗子:年老代为年轻代进行分配担保,当年轻代minor gc内存不足时【比如有个大对象obj1】obj1会放到年老代中;而年老代没有其他空间为其分担了)所以推荐标记-整理算法。

3.hotspot算法实现

3.1 枚举根节点

  从可达性分析中从GC Roots 节点找引用链这个操作为例, 可作为GC Roots 的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表〉中
  现在很多应用仅仅方法区就有数百兆,如果要逐个检查这里面的引用,那么必然会消挺很多时间。另外,可达性分析对执行时间的敏感还体现在GC 停顿上,GC 进行时必须停顿所有Java 执行线程其由于目前的主流Java 虚拟机使用的都是准确式GC ,所以当执行系统停顿下后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得知哪些地方存放着对象引用。在HotSpot 的实现中,是使用一组称为OopMap 的数据结构来达到这个目的的,在类加载完成的时候, HotSpot 就把对象内什么偏移量上是什么类型的数据计算出来, 在JIT 编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这样, GC 在扫描时就可以直接得知这些信息。

3.2 安全点

  OopMap可以快速准确 完成GC Roots的枚举,但OopMap指令多,会需要大量额外,实际上Hotspot只在特定的位置记录信息,这些位置叫安全点。即程序执行时并非所有地方都能停顿下来开始GC,只有在带的安全点才能暂停。安全点的选择已“是否具有让程序长时间执行的特性”为标准选择。
  另一个需要考虑的问题是:如何在GC发生时所有线程都“跑”到最近的安全点上再停顿下来。这里有两种方案可供选择:抢先式中断 和主动式中断。
抢先式中断
  不需要线程的执行代码主动去配合,在GC 发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。
主动式中断
  是当GC 需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象有需要分配内存的地方

3.3 安全区域

  线程处于Sleep 状态或者Blocked 状态,这时候线程无法响应JVM 的中断请求,"走” 到安全的地方去中断挂起, JVM 也显然不太可能等待线程重新被分配CPU 时间。安全区域是指在一段代码片段之中, 引用关系不会发生变化。在这个区域中的任意地方开始GC 都是安全的‘我们也可以把Safe Region 看做是被扩展了的Safepoint 。

4.垃圾收集器:

  共七中垃圾收集器,分别是serial、parnew、parallel scavenge和cms、serial old、 parallel old、G1;其中serial、parnew、parallel scavenge用于年轻代,cms、serial old、 parallel old用于年老代,g1用于年轻代和年老代。所有收集器都存在stop the world,不过在java发展中 一直在优化停顿时间。
先总结比较这七个收集器:

年轻代:

4.1.Serial:

  适用于年轻代垃圾回收,复制算法,jdk1.3之前 推荐用于客户端模式(Client)下的虚拟机,属于单线程,无对stop the world优化,属于最老的年轻代垃圾收集器产品;

4.2 ParNew:

  适用于年轻代垃圾回收,复制算法,jdk1.3发布,推荐用于服务端模式(Server)下的虚拟机,属于多线程,无对stop the world优化,其实就是Serial的多线程版本;是服务端开发的首先;

4.3 Parallel Scavenge:

  适用于年轻代垃圾回收,复制算法,jdk1.4发布,属于多线程,无对stop the world优化,它是一个可以控制吞吐量的收集器,拥有自适应调节策略;需要注意一点的是,gc停顿时间缩短是牺牲吞吐量和新生代空间来换取的。
stop The World:gc时 需要停止所有线程进行垃圾回收;
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
自适应调节策略:虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整参数以提高最合适的停顿时间或最大吞吐量。
年老代

4.4 Serial old :

  适用于年老代垃圾回收,标记-整理算法,jdk1.5之前,主要用于client下的虚拟机,如果用在server模式下主要有俩个作用:一是jdk1.5以及之前用于与Parallel Scavenger搭配使用,二就是为cms提供后备预案,属于单线程,无stop the world优化。

4.5 Parallel Old:

  适用于年老代垃圾回收,标记-整理算法,jdk1.6发布,属于多线程,注重于吞吐量控制,是为了Parallel Scavenge定制的;

4.6 CMS:

  适用于年老代垃圾回收,标记-清除算法,jdk1.5发布,推荐server模式下,属于多线程,对stop the world有优化,CMS是一种以获取最短回收停顿时间为目标的收集器,也就是优化服务器响应速度。CMS分为4步:其中 初始化标记,重新标记 stop the world

  1. 初始标记:仅仅只是标记GcRoots可以直接关联的对象;
  2. 并发标记:进行gcroots tracing(也就是 引用链搜索);优化成并发
  3. 重新标记:修正并发标记因用户程序继续运行而导致标记产生变动那一部分对象的标记记录。
  4. 并发清除:并发清理标记的对象内存。
    CMS缺点:
    (1) CMS对cpu资源非常敏感,默认启动的回收线程数是(cpu数量+3)/4;垃圾回收线程数量不少于25%的cpu资源,当cpu越大,线程数/cpu总量 越小;但是当cpu小于4个时 显得就不怎么合适了;
    (2) 无法处理浮动垃圾,需要等下一次gc才能处理;并且还需要预留一部分空间提供并发收集时的用户程序运作使用,即启动阈值(-XX:CMSInitiatingOccupancyFraction 阈值百分比);要是CMS运行期间预留的空间不满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这是虚拟机启动后备预案:临时启用Serial Old,这样一来停顿时间就很长了,所以-XX:CMSInitiatingOccupancyFraction 设置太高容易导致大量“Concurrent Mode Failure”失败,性能反而降低
    (3) 由于用的是标记-清除算法,所以会出现内存碎片;CMS提供-XX:UseCMSCompactAtFullCollection开关参数(默认为开),用于CMS收集器要进行FullGC时进行内存碎片合并整理,-XX:CMSFullGCsBeforeCompaction 用来设置执行多少次不压缩Full GC后跟着来一次带压缩的(默认为0,表示每次进入Full GC都进行碎片压缩)。
4.7 G1收集器:

年轻代+年老代
jdk1.7发布,整体看是“标记-整理算法”,从局部(俩个Region之间)上来看是基金“复制”算法实现,也就是说没有内存碎片;
如果应用追求低停顿,那G1现在可以作为一个尝试的选择,如果应该追求吞吐量,G1并不会带来什么特别的好处!!!

5.内存分配策略:

共有 4个策略

5.1 对象优先在Eden分配:

major gc时经常会伴随至少一次的minor gc,但并非绝对,比如说 Parallel Scavenge就是直接major gc没有伴随minor gc;

5.2 大对象直接进入老年代

-XX:PretenureSizeThreshold设置最大对象,单位B;PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效;Parallel Scavenge收集器不需要设置;如果遇到必须使用此参数的场景,可以考虑ParNew+CMS组合;

5.3 长期存活的对象放入老年代:

虚拟机给每个对象定义了一个对象年龄(Age)计数器;gc一次Age+1,当Age大于一定程度(默认为15岁)就会被晋升到老年代;-XX:MaxTenuringThreshold:最大年龄;

5.4 动态对象年龄判定:

如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于改年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄;

5.4空间分配担保:

jdk6 update 24后,只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行major gc,否则Full GC;

第二部分:第四章

1 jdk的命令行工具简单总结介绍

1.1 jps: 虚拟机进程状况工具

获取虚拟机进程 vmid;jps -v :显示虚拟机对应启动的参数

1.2 jstat:虚拟机统计信息监视工具

jstat -gccause vmid:输出已使用空间占各自总空间的百分比;

1.3 jinfo:java配置信息工具

获取虚拟机参数值和动态修改部分运行期可改的参数。jinfo [option] pid例如:jinfo -flag PermSize vmid;显示vid对应的虚拟机的方法区大小;jinfo -flag +/- name:添加或删除name属性;

1.4 jmap:java内存映像工具

jmap -heap vmid:显示堆的详细信息,如参数配置,分代状况; jmap -histo vmid:显示堆中对象统计信息,包括类,实例数量,合计容量。

1.5 jhat:虚拟机堆转储快照分析工具
1.6 jstack:java堆栈跟踪工具
1.7 hsdis:jit生成代码反汇编

2 jdk的可视化工具:

jConsole和visualVm;其中VisualVM有个BTrace插件值得注意。

2.1 jconsole:java监视与管理控制台
2.2 visualvm:多合一故障处理工具

第二部分:第五章

简单总结:

一、jvm调优思路:

第一步:jps 获取jvm id;
第二步:获取jvm垃圾收集器种类
第三步:查看gc次数和时间,分析原因来优化
gc次数频繁:①、内存回收率低导致短时间内回收次数多;②、内存大小太小;
gc时间长:①、内存过大;②、内存扩展导致时间长(固定内存大小)
选择适合的收集器也可大幅度优化jvm。

二、优化思路注意点:

1、64位jdk的性能测试结果普遍低于32位jdk;
2、64位jdk由于指针膨胀和数据类型对齐补白导致消耗的内存比32位大;
3、使用nio时,堆外内存不足导致内存溢出。

第三部分:虚拟机执行子系统

第6章 类文件结构

java平台无关性的原因:虚拟机以及虚拟机使用统一的存储结构-字节码。

第7章JVM类加载机制

1 概述

  JVM把描述类的数据从class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型。这就是jvm的类加载机制。
Java中类的加载,连接和初始化都是在程序运行期间完成。(好处:增加了应用程序的灵活性;缺点:增加性能开销)

2 类的加载时机

类的生命过程: 加载, 验证,准备, 解析,初始化,使用和卸载。其中验证,准备和解析叫做连接阶段。其中加载,验证,准备,初始化和卸载必须是按照顺序,而解析可以在初始化后再开始。
对于类的加载,验证,准备和解析的时刻没有明确的规定,但是对类的初始化有着明确的规定:
1. 遇到new, getstatic, putstatic和invokestatic这4条字节码指令的时候。若类没有初始化,则先进行初始化。(对应在java代码中, new, 读取或者设置一个类的静态字段以及调用一个类的静态方法。)
2. 使用java.lang.reflect对类进行反射调用时;
3. 若初始化一个类的时候,其父类还未初始化,那么先初始化其父类;
4. 当虚拟机启动的时候,用户需要指定一个需要执行的主类,虚拟机会先初始化这个类。
具体的内容包括:
·创建一个Java类的实例。如 MyClass obj = new MyClass()
·调用一个Java类中的静态方法。如 MyClass.sayHello()
·给Java类或接口中声明的静态域赋值。如 MyClass.value = 10
·访问Java类或接口中声明的静态域,并且该域不是常值变量。如 int value = MyClass.value
·在顶层Java类中执行assert语句。
初始化过程的主要操作是执行静态代码块和初始化静态域。在一个类被初始化之前,它的直接父类也需要被初始化。但是,一个接口的初始化,不会引起其父接口的初始化。在初始化的时候,会按照源代码中从上到下的顺序依次执行静态代码块和初始化静态域。

3 类加载的过程

3.1加载

在加载的时候,虚拟机完成了如下步骤:
1. 获取此类的2进制字节流;
2. 将静态存储结构转化为方法区运行时数据结构;
3. 在java堆中生成代表这个类的java.lang.class对象,作为方法区这些数据的访问入口。

3.2 验证

确保输入的字节流符合当前jvm的要求,主要包括如文件格式的验证,元数据验证,字节码验证和符号引用验证。

3.3 准备阶段

正式为类变量分配内存,并设置类变量(static变量,不包括实例变量)初始值的阶段。这些类存都在方法区中进行设置。对于非final的变量,其初始值都将先设置为0值,而对于final变量,在改阶段就直接设置为用户定义的值。

3.4解析过程

虚拟机将常量池类的符号引用替换为直接引用。(感觉就是讲逻辑的地址转为物理地址的意思。)

3.5 初始化阶段

根据程序员制定的主观计划去初始化类变量和其它资源,就是执行类构造器方法clinit()的过程。

4 类加载器

4.1 类与类加载器

  对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立它在java虚拟机中的唯一性。也就是说即使同一个class文件,但使用的类加载器不同,那么这2个类必定不相等。

4.2 双亲委派模型

绝大部分Java 程序都会使用到以下3 种系统提供的类加载器。
1.启动类加载器:这个类将器负责将存放在JAVA_HOME\lib 日录中的,或者被-Xbootclasspath 参数所指定的路径中的,并-且是虚拟机识别的(仅按照文件名识别,如比jar,名字不符合的类库即使放在lib 目录中也不会被加载〉类库加载到虚拟机内存中。启动类加载器无法被Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那直接用null 代替即可。
2.扩展类加载器:这个加载器由sun.misc.Launcher.ExtCJassLoader实现,它负责加载 JAVA_HOME\lib\ext 目录中的,或者被java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
3.应用程序类加载器:这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader 中的getSystemCiassLoader()方法的返回值,所以一般也称他为系统类加载器,他负责加载用户类路径上所指定的类库,开发者可以直接使用这个类加载器。一般是程序默认的类加载器。
应用程序是这3中加载器互相配合进行加载的。关系如图:

这种层次关系就叫双亲委派模型。
好处就是java类随着他的类加载器一起具备了一种优先级层次关系。如java.lang.Object,在顶层类加载器,所有的Object都一样。

4.3 破坏双亲委派模型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江北望江南

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值