内存中较小的内存空间,经过计数器的值能够选取下一条执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成。html
线程私有,生命周期跟线程相同。java
若是正在执行一个Native方法,那么这个计数器值将为空。程序员
线程私有,生命周期跟线程相同。算法
每一个方法在执行同时都会建立一个栈帧,用于存储局部变量表、操做数栈、动态连接、方法出口等信息。apache
在Java虚拟机规范中,对这个区域规定了两种异常状况:若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异常;
若是虚拟机栈能够动态扩展,若是扩展时没法申请到足够的内存,就会抛出OutOfMemoryError异常。ubuntu
跟虚拟机栈所发挥的做用类似,区别在于虚拟机栈为虚拟机执行Java(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。数组
用于存放对象实例,是Java虚拟机所管理的内存中最大的一块,同时也是全部线程共享的一块内存区域。浏览器
由于Java堆是垃圾收集器管理的主要区域,所以不少时候也被称为“GC"堆。因为如今收集器基本都采用分代收集算法,因此Java堆还能够细分为缓存
当一个对象被建立时,它首先进入新生代,以后有可能被转移到老年代中。安全
新生代存放着大量的生命很短的对象,所以新生代在三个区域中垃圾回收的频率最高。为了更高效地进行垃圾回收,把新生代继续划分红如下三个空间:
与Java堆同样,各个线程共享的内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即便编译器编译后的代码等数据。
方法区的一部分,用于存放编译器生成的各类字面量和符号引用。
运行时常量池相对于class文件常量池的另一个重要特征是具有动态性,Java语言并不要求常量必定只有编译期才能产生,也就是并不是预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的即是String类的intern()方法。
在JDK1.4中新加入了NIO类,引入了一种基于通道与缓冲区的I/O方法,它可使用Native函数库直接分配堆外内存,而后经过一个存储在Java堆中的DirectByteBuffer对象做为这块内存的引用进行操做。
堆外内存之 DirectByteBuffer 详解
在语言层上,建立对象一般仅仅是一个new关键字而已,而当虚拟机遇到一条new执行时,将由一下步骤:
在堆中分配内存
划分可用空间:
并发问题
对象在内存中储存的布局能够分为3块区域:
对象头
实例数据
对齐填充
内存溢出out of memory,是指程序在申请空间时,没有足够的内存空间供其使用,出现了Out of memory error。
当new一个对象或者数组时,若是超出了Jvm的head内存最大限制就会爆出异常。
伪代码:
while(ture){ new Object(); }
在Java虚拟机规范中,对这个栈规定了两种异常状况,若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOutFlowError异常,若是虚拟机能够动态扩展(当前大部分Java虚拟机均可动态扩展,只不过Java虚拟机规范中也容许固定长度的虚拟机栈),当扩展时没法申请获得足够的内存时将会抛出OutOfMemory。
线程中的stack是线程私有的,默认大小一般为1M,能够经过-Xss来设置,-Xss越大,则线程获取的内存越大。
常见问题在线程内过分的调用函数,函数调用会消耗栈空间。
伪代码:
public void SOFETest(){ SOFETest(); }
Java的栈空间被全部线程分配成一块一块的,每一个线程只占一块。而Jvm的栈空间的最小分配单位有-Xss来决定。-Xss有两个语义,即定义每一个线程的栈大小,也定义了虚拟机的最小栈内存的分配单位。
若是申请的线程没有得到栈空间能够分配了就会抛出OutOfMemoryError。表示栈空间不足,溢出异常。
代码:该代码可能致使JVM没法申请获得太多的栈内存而致使操做系统由于栈空间不足假死。
public class Main { public static void main(String[] args) throws ClassNotFoundException { CountDownLatch countDownLatch = new CountDownLatch(1); for(int i =0;i<1020000000;i++){ new Thread(new Runnable(){ @Override public void run() { int a = 1000; try { countDownLatch.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start(); } countDownLatch.countDown(); } }
内存泄漏memory leak,指程序在申请内存以后,没法释放已申请的内存空间,一次内存泄漏危害能够忽略,屡次memory leak将致使oom。
内存泄漏是指你向系统申请分配内存进行使用(new),但是使用完了之后却不归还(delete),结果你申请到的那块内存你本身也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给须要的程序。
该部份内容转自:JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解
jps主要用来输出JVM中运行的进程状态信息。语法格式以下:
jps [options] [hostid]
若是不指定hostid就默认为当前主机或服务器。
命令行参数选项说明以下:
-q 不输出类名、Jar名和传入main方法的参数 -m 输出传入main方法的参数 -l 输出main类或Jar的全限名 -v 输出传入JVM的参数
好比下面:
root@ubuntu:/# jps -m -l 2458 org.artifactory.standalone.main.Main /usr/local/artifactory-2.2.5/etc/jetty.xml 29920 com.sun.tools.hat.Main -port 9998 /tmp/dump.dat 3149 org.apache.catalina.startup.Bootstrap start 30972 sun.tools.jps.Jps -m -l 8247 org.apache.catalina.startup.Bootstrap start 25687 com.sun.tools.hat.Main -port 9999 dump.dat 21711 mrf-center.jar
jstack主要用来查看某个Java进程内的线程堆栈信息。语法格式以下:
jstack [option] pid jstack [option] executable core jstack [option] [server-id@]remote-hostname-or-ip
命令行参数选项说明以下:
-l long listings,会打印出额外的锁信息,在发生死锁时能够用jstack -l pid来观察锁持有状况 -m mixed mode,不只会输出Java堆栈信息,还会输出C/C++堆栈信息(好比Native方法)
jstack能够定位到线程堆栈,根据堆栈信息咱们能够定位到具体代码,因此它在JVM性能调优中使用得很是多。下面咱们来一个实例找出某个Java进程中最耗费CPU的Java线程并定位堆栈信息,用到的命令有ps、top、printf、jstack、grep。
第一步先找出Java进程ID,我部署在服务器上的Java应用名称为mrf-center:
root@ubuntu:/# ps -ef | grep mrf-center | grep -v grep root 21711 1 1 14:47 pts/3 00:02:10 java -jar mrf-center.jar
获得进程ID为21711,第二步找出该进程内最耗费CPU的线程,可使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,我这里用第三个,输出以下:
TIME列就是各个Java线程耗费的CPU时间,CPU时间最长的是线程ID为21742的线程,用
printf "%x\n" 21742
获得21742的十六进制值为54ee,下面会用到。
OK,下一步终于轮到jstack上场了,它用来输出进程21711的堆栈信息,而后根据线程ID的十六进制值grep,以下:
root@ubuntu:/# jstack 21711 | grep 54ee "PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]
能够看到CPU消耗在PollIntervalRetrySchedulerThread这个类的Object.wait(),我找了下个人代码,定位到下面的代码:
// Idle wait getLog().info("Thread [" + getName() + "] is idle waiting..."); schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting; long now = System.currentTimeMillis(); long waitTime = now + getIdleWaitTime(); long timeUntilContinue = waitTime - now; synchronized(sigLock) { try { if(!halted.get()) { sigLock.wait(timeUntilContinue); } } catch (InterruptedException ignore) { } }
它是轮询任务的空闲等待代码,上面的sigLock.wait(timeUntilContinue)就对应了前面的Object.wait()。
jmap用来查看堆内存使用情况,通常结合jhat使用。
jmap语法格式以下:
jmap [option] pid jmap [option] executable core jmap [option] [server-id@]remote-hostname-or-ip
若是运行在64位JVM上,可能须要指定-J-d64命令选项参数。
jmap -permstat pid
打印进程的类加载器和类加载器加载的持久代对象信息,输出:类加载器名称、对象是否存活(不可靠)、对象地址、父类加载器、已加载的类大小等信息,以下图:
使用jmap -heap pid查看进程堆内存使用状况,包括使用的GC算法、堆配置参数和各代中堆内存使用状况。好比下面的例子:
root@ubuntu:/# jmap -heap 21711 Attaching to process ID 21711, please wait... Debugger attached successfully. Server compiler detected. JVM version is 20.10-b01 using thread-local object allocation. Parallel GC with 4 thread(s) Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 2067791872 (1972.0MB) NewSize = 1310720 (1.25MB) MaxNewSize = 17592186044415 MB OldSize = 5439488 (5.1875MB) NewRatio = 2 SurvivorRatio = 8 PermSize = 21757952 (20.75MB) MaxPermSize = 85983232 (82.0MB) Heap Usage: PS Young Generation Eden Space: capacity = 6422528 (6.125MB) used = 5445552 (5.1932830810546875MB) free = 976976 (0.9317169189453125MB) 84.78829520089286% used From Space: capacity = 131072 (0.125MB) used = 98304 (0.09375MB) free = 32768 (0.03125MB) 75.0% used To Space: capacity = 131072 (0.125MB) used = 0 (0.0MB) free = 131072 (0.125MB) 0.0% used PS Old Generation capacity = 35258368 (33.625MB) used = 4119544 (3.9287033081054688MB) free = 31138824 (29.69629669189453MB) 11.683876009235595% used PS Perm Generation capacity = 52428800 (50.0MB) used = 26075168 (24.867218017578125MB) free = 26353632 (25.132781982421875MB) 49.73443603515625% used ....
使用jmap -histo[:live] pid查看堆内存中的对象数目、大小统计直方图,若是带上live则只统计活对象,以下:
root@ubuntu:/# jmap -histo:live 21711 | more num #instances #bytes class name ---------------------------------------------- 1: 38445 5597736 <constMethodKlass> 2: 38445 5237288 <methodKlass> 3: 3500 3749504 <constantPoolKlass> 4: 60858 3242600 <symbolKlass> 5: 3500 2715264 <instanceKlassKlass> 6: 2796 2131424 <constantPoolCacheKlass> 7: 5543 1317400 [I 8: 13714 1010768 [C 9: 4752 1003344 [B 10: 1225 639656 <methodDataKlass> 11: 14194 454208 java.lang.String 12: 3809 396136 java.lang.Class 13: 4979 311952 [S 14: 5598 287064 [[I 15: 3028 266464 java.lang.reflect.Method 16: 280 163520 <objArrayKlassKlass> 17: 4355 139360 java.util.HashMap$Entry 18: 1869 138568 [Ljava.util.HashMap$Entry; 19: 2443 97720 java.util.LinkedHashMap$Entry 20: 2072 82880 java.lang.ref.SoftReference 21: 1807 71528 [Ljava.lang.Object; 22: 2206 70592 java.lang.ref.WeakReference 23: 934 52304 java.util.LinkedHashMap 24: 871 48776 java.beans.MethodDescriptor 25: 1442 46144 java.util.concurrent.ConcurrentHashMap$HashEntry 26: 804 38592 java.util.HashMap 27: 948 37920 java.util.concurrent.ConcurrentHashMap$Segment 28: 1621 35696 [Ljava.lang.Class; 29: 1313 34880 [Ljava.lang.String; 30: 1396 33504 java.util.LinkedList$Entry 31: 462 33264 java.lang.reflect.Field 32: 1024 32768 java.util.Hashtable$Entry 33: 948 31440 [Ljava.util.concurrent.ConcurrentHashMap$HashEntry;
class name是对象类型,说明以下:
B byte C char D double F float I int J long Z boolean [ 数组,如[I表示int[] [L+类名 其余对象
还有一个很经常使用的状况是:用jmap把进程内存使用状况dump到文件中,再用jhat分析查看。jmap进行dump命令格式以下:
jmap -dump:format=b,file=dumpFileName pid
我同样地对上面进程ID为21711进行Dump:
root@ubuntu:/# jmap -dump:format=b,file=/tmp/dump.dat 21711 Dumping heap to /tmp/dump.dat ... Heap dump file created
dump出来的文件能够用MAT、VisualVM等工具查看,这里用jhat查看:
root@ubuntu:/# jhat -port 9998 /tmp/dump.dat Reading from /tmp/dump.dat... Dump file created Tue Jan 28 17:46:14 CST 2014 Snapshot read, resolving... Resolving 132207 objects... Chasing references, expect 26 dots.......................... Eliminating duplicate references.......................... Snapshot resolved. Started HTTP server on port 9998 Server is ready.
注意若是Dump文件太大,可能须要加上-J-Xmx512m这种参数指定最大堆内存,即jhat -J-Xmx512m -port 9998 /tmp/dump.dat。而后就能够在浏览器中输入主机地址:9998查看了:
上面红线框出来的部分你们能够本身去摸索下,最后一项支持OQL(对象查询语言)。
语法格式以下:
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
vmid是Java虚拟机ID,在Linux/Unix系统上通常就是进程ID。interval是采样时间间隔。count是采样数目。好比下面输出的是GC信息,采样时间间隔为250ms,采样数为4:
root@ubuntu:/# jstat -gc 21711 250 4 S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 192.0 192.0 64.0 0.0 6144.0 1854.9 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 192.0 192.0 64.0 0.0 6144.0 2109.7 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
要明白上面各列的意义,先看JVM堆内存布局:
能够看出:
堆内存 = 年轻代 + 年老代 + 永久代 年轻代 = Eden区 + 两个Survivor区(From和To)
如今来解释各列含义:
S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used) EC、EU:Eden区容量和使用量 OC、OU:年老代容量和使用量 PC、PU:永久代容量和使用量 YGC、YGT:年轻代GC次数和GC耗时 FGC、FGCT:Full GC次数和Full GC耗时 GCT:GC总耗时
hprof可以展示CPU使用率,统计堆内存使用状况。
语法格式以下:
java -agentlib:hprof[=options] ToBeProfiledClass java -Xrunprof[:options] ToBeProfiledClass javac -J-agentlib:hprof[=options] ToBeProfiledClass
完整的命令选项以下:
Option Name and Value Description Default --------------------- ----------- ------- heap=dump|sites|all heap profiling all cpu=samples|times|old CPU usage off monitor=y|n monitor contention n format=a|b text(txt) or binary output a file=<file> write data to file java.hprof[.txt] net=<host>:<port> send data over a socket off depth=<size> stack trace depth 4 interval=<ms> sample interval in ms 10 cutoff=<value> output cutoff point 0.0001 lineno=y|n line number in traces? y thread=y|n thread in traces? n doe=y|n dump on exit? y msa=y|n Solaris micro state accounting n force=y|n force output to <file> y verbose=y|n print messages about dumps y
来几个官方指南上的实例。
CPU Usage Sampling Profiling(cpu=samples)的例子:
java -agentlib:hprof=cpu=samples,interval=20,depth=3 Hello
上面每隔20毫秒采样CPU消耗信息,堆栈深度为3,生成的profile文件名称是java.hprof.txt,在当前目录。
CPU Usage Times Profiling(cpu=times)的例子,它相对于CPU Usage Sampling Profile可以得到更加细粒度的CPU消耗信息,可以细到每一个方法调用的开始和结束,它的实现使用了字节码注入技术(BCI):
javac -J-agentlib:hprof=cpu=times Hello.java
Heap Allocation Profiling(heap=sites)的例子:
javac -J-agentlib:hprof=heap=sites Hello.java
Heap Dump(heap=dump)的例子,它比上面的Heap Allocation Profiling能生成更详细的Heap Dump信息:
javac -J-agentlib:hprof=heap=dump Hello.java
虽然在JVM启动参数中加入-Xrunprof:heap=sites参数能够生成CPU/Heap Profile文件,但对JVM性能影响很是大,不建议在线上服务器环境使用。
程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束以后也会消失,所以不须要对这三个区域进行垃圾回收。垃圾回收主要是针对 Java 堆和方法区进行。
给对象添加一个引用计数器,每当有一个地方引用它,计数器值就加1;引用时效时,计算器值就减1;当计数器值为0的对象就是不可能再被使用的。
当两个对象相互引用时,此时引用计数器的值永远不为0,致使没法对它们进行垃圾回收。
public class ReferenceCountingGC { public Object instance = null; public static void testGC() { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA .instance = objB ; objB .instance = objA ; objA = null; objB = null; System.gc(); } }
以GC Roots为起始点,从这些节点开始向下搜索,可以搜索到的对象都是存活的,不可达的对象则为不可用。
在Java语言中,可做为GC Roots的对象包括下面几种:
不管是引用计数算法仍是可达性分析算法判断对象是否存活都与引用有关。在JDK1.2以后,Java对引用的概念进行了扩充,划分为强度不一样的四个的引用类型。
经过new来建立对象的引用类型,被强引用的对象永远不会被垃圾收集器回收。
Object obj = new Object();
经过SortReference类来实现,只有在内存不足的时候才会被回收。
Object obj = new Object(); SoftReference<Object> sr = new SoftReference<Object>(obj); obj = null;
经过WeakReference类来实现,只能存活到下一次垃圾收集发生以前。
Object obj = new Object(); WeakReference<Object> wr = new WeakReference<Object>(obj); obj = null;
WeakHashMap 的 Entry 继承自 WeakReference,主要用来实现缓存。
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
Tomcat 中的 ConcurrentCache 就使用了 WeakHashMap 来实现缓存功能。ConcurrentCache 采起的是分代缓存,常用的对象放入 eden 中,而不经常使用的对象放入 longterm。eden 使用 ConcurrentHashMap 实现,longterm 使用 WeakHashMap,保证了不常使用的对象容易被回收。
public final class ConcurrentCache<K, V> { private final int size; private final Map<K, V> eden; private final Map<K, V> longterm; public ConcurrentCache(int size) { this.size = size; this.eden = new ConcurrentHashMap<>(size); this.longterm = new WeakHashMap<>(size); } public V get(K k) { V v = this.eden.get(k); if (v == null) { v = this.longterm.get(k); if (v != null) this.eden.put(k, v); } return v; } public void put(K k, V v) { if (this.eden.size() >= size) { this.longterm.putAll(this.eden); this.eden.clear(); } this.eden.put(k, v); } }
也称为幽灵引用或者幻影引用,是最弱的一种引用关系。
经过PhantomReference类来实现,为一个对象设置虚引用关联的惟一目的就是能在这个对象被收集器回收时收到一个系统通知。
Object obj = new Object(); PhantomReference<Object> wr = new PhantomReference<Object>(obj, null); obj = null;
算法分为“标记”跟“清除”两个阶段:首先标记出全部须要回收的对象,在标记完成以后统一回收全部被标记的对象。
不足:
将内存分为大小相等的两块,每次只使用其中的一块,当这块内存用完了,就将还存活的对象负责到另外一块上面,而后再把一是要难过过得内存空间一次清理掉。
不足:
首先标记出全部须要回收的对象,而后将全部存活的对象都向一端移动,最后清理掉端边界之外的内存。
根据对象的存活周期将内存划分为几块。通常将Java堆分为新生代跟老年代,这样就能够根据各个年代的特色采用最适当的收集算法。
若是说手机算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
上图展现了7种不一样分代的收集器,若是两个收集器之间存在连线,就说明它们能够搭配使用。
知道目前为止尚未最好的收集器出现,更加没有万能的收集器,因此咱们只能选择对具体应用最合适的收集器。
最基本、最悠久的收集器,单线程收集器,复制算法
在它进行垃圾收集时,必须暂停其余全部的工做线程,直到它收集结束。
相比较与其余收集器,它具备:简单而高效的特色,对于限定CPU环境来讲,Serial收集器没有线程交互的开销。
依然是虚拟机运行在Client模式下的默认新生代收集器。
Serial的多线程版本、并行,复制算法。
是许多运行在Server模式下的虚拟机中首选的新生代收集器,由于目前除了Serial收集器外,只有它能与CMS收集器配合使用。
默认开启的收集线程数与CPU的数量相同,在CPU很是多的环境下,可使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
新生代、并行的多线程收集器,复制算法。
Parallel Scavenge收集器的目标是达到一个可控制的吞吐量:CPU用户运行用户代码的时间与CPU的执行时间,即吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。
停顿时间越短越适合须要与用户交互的程序,良好的响应速度能提高用户体验,而高吞吐量能够高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不须要太多交互的任务。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
Serial的老年代版本,单线程,标记-整理算法。
这个收集器的主要意义在于给Client模式下的虚拟机使用,若是在Server默认下,它还有两大用途:
parallel Scavenge的老年代版本,多线程,标记-整理算法,JDK1.6以后提供。
在注重吐吞量以及CPU资源敏感的场合,均可以优先考虑Parallel Scavenge加Parallel Old收集器。
CMS(Concurrent Mark Sweep),从名字来就能够看出,基于标记-清除算法。
并发收集、低停顿。
运算过程分为4个步骤:
整个过程耗时最长的并发标记和并发清除过程收集器线程均可以与用户安城一块儿工做。
CMS还远达不到完美的程度,还有如下3个缺点:
一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是将来能够替代掉JDK1.5中发布的CMS收集器。
与其余收集器相比,G1具备如下特色:
G1将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,可是新生代和老年代再也不是物理隔离级别,它们都是一部分Region的集合。
G1收集器之因此可以创建可预测的停顿时间模型,是由于它能够有计划低避免在整个Java堆中进行全区域的垃圾回收。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所得到的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据容许的收集时间,优先回收价值最大的Region。
虚拟机使用Remembered Set来避免全栈扫描,G1中每一个Region都有一个与之对应的Remembered Set,用来记录该Region对象的引用对象所在的Region。
若是不计算Remembered Set的操做,G1收集器的运做大体可分为:
Full GC的触发条件
对象的内存分配规则并非百分百固定的,其细节取决于当前使用的是哪种垃圾收集器组合,还有虚拟机中与内存有关的参数设置。
虚拟机规范严格规定了有且只有下面5种状况必须当即对类进行初始化:
如下状况不会初始化:
SuperClass[] sca = new SuperClass[];
。在加载阶段,虚拟机须要完成下面三件事:
验证是链接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前的虚拟机要求,而且不会危害虚拟机自身的安全。
从总体看,验证阶段大体上会完成4个阶段的检验动做:
正式为类变量分配内存并设置初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。同时设置变量的初始值(零值)。
将常量池中的符号引用替换成直接饮用的过程。
开始执行类中定义的java程序代码,根据程序员经过程序制定的主观计划去初始化类变量和其余资源,或者说执行类构造器<clinit>()方法的过程。
虚拟机设计团队把类加载阶段中“经过一个类的全限定名来获取描述此类的二进制字节流”这个动做放到java虚拟机外部去实现,以便让应用程序本身决定如何获取所须要的类。实现这个动做的代码模块称为“类加载器”。
对于任意一个类,都须要由加载它的加载器和这个类自己确立其在Java虚拟机中的惟一性,每个类加载器都拥有一个独立的类名称空间。两个类“相等”包括表明类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系断定等状况。
从Java虚拟机的角度来说,只存在两种不一样的类加载器:
从Java开发人员的角度来看,类加载器划分为更细致一些:
应用程序都是由三种类加载器互相配合进行加载的,若是有必要还能够加入本身定义的类加载器,这些类加载器之间的关系通常以下:
图中展现的类加载器之间的这种层次关系,称为类加载器的双亲委派模型,双亲委派模型除了顶层的启动类加载器外,其他的类加载器都应当有本身的父类加载器,这里加载器之间的父子关系通常不会以继承(Inheritance)的关系来实现,而是都是用组合(Composition)关系来服用父加载器的代码。
若是一个类加载器收到了类加载的请求,它首先会把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,所以全部的加载请求最终都应该传送到顶层的启动类加载器中,只有父加载器反馈本身没法尝试完成这个加载请求时,子加载器才会尝试本身去加载。
Java类随着它的类加载器一块儿具有了一种带有优先级的层次关系。
例如类java.lang.Object存放在rt.jar中,不管哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器来进行加载,所以Object类在程序的各类加载器环境中都是一个类。相反若是没有双亲委派模型,若是用户本身编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那么系统中将会出现多个不一样的Object类。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }