JVM学习笔记
Jvm初体验
导出堆快照,利用mat分析工具定位到问题,制定解决方案。
-XX:+HeapDumpOnOutOfMemory
Shallow heap:对象本身所占内存的大小,不包含引用对象
Retained heap:当前对象加上对象直接或间接引用的对象的大小的总和
Jvm再体验:JVM监控工具
Jdk安装目录下的jconsole.exe工具,是可视化的监控工具。实际代码在tools.jar上,exe是一层包装,比较小。
为什么折线图是上升又下降的?是因为垃圾收集器在起作用。
书写代码监控程序:
创建内存,会放在Eden
Space中
杂谈
虚拟机是由C写的。
Java的发展历史
Java之父:詹姆斯-高林斯
1991年,oak:用于嵌入式设备上,(类比安卓系统)
1995年5月,oak嵌入到网景浏览器中,(applet)改名为-java1.0。提出了Write once, run anywhere。
1996年1月,jdk1.0 jvm sun Classic VM (已淘汰)
1996年9月,首届JavaOne大会。
1997年2月,sun发布了jdk1.1 内部类,反射,新增了jar文件格式,jdbc,JavaBeans,rmi
1998年1.2发布。Java三个方向。J2E, J2EE, J2ME swing jit HotSpotVM
2000年5月 jdk1.3 Timer类 java2d。java开始高速发展
2002年2月jdk1.4 走向成熟 Strurts,Hibernate,Sring1.X 。框架开始支持1.4。新增了:
l 正则表达式
l Nio
l 日志
l XML解析器
2004年9月,jdk1.5 tiger
自动装箱拆箱,泛型,注解,枚举、变长参数,增强for循环,Spring开始使用注解,Spring2.X。减少了大量代码配置,现在的Spring4.X
2006年jdk1.6 J2EE等等改名,JavaEE,JavaSE… jdk1.6 à jdk6
l 提供了脚本语言的支持
l 提供了编译api以及http服务器api
2009年jdk1.7 开始规划,并未实现 Lambda(函数式编程)jigsaw(模块化编程)。 Sun收购74亿Oracle。
2011年7月 jdk1.7最终版发布!(并未完后所有的规划,比如lambda表达式)
2014年3月 jdk1.8 发布
Java虚拟机内存管理
什么是线程独占—> 每一个线程都会有自己的程序计数器
程序计数器(program counter)
1.程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器
2.程序计数器处于线程独占区
3.如果线程执行的是Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是native方法,这个计数器的值为undefined
4.此区域是唯一一个在Java虚拟机规范中没有任何OutOfMemoryError情况的区域。因为这是java虚拟机自己维护的。
goto特例。跳转到某一行,类似直接操作pc。Java现在这是不允许的!,现在作为保留字。作为保留字的原因是,java本身不用,也不让用户使用。。。
Java虚拟机栈
1.虚拟机栈描述的是Java方法执行的动态内存模型
2.栈帧
l 每个方法执行都会创建一个栈帧,伴随着方法从创建到执行完成。用于存储局部变量表,操作数栈,动态链接,方法出口等
3.局部变量表
l 存放编译期,可知的各种基本数据类型,引用类型,returnAddress类型
l 局部变量表的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧上分配多少内存是固定的,在方法允许期间是不会改变局部变量表的大小
4.大小
l StackOverFlowErro0r
l OutOfMemory
本地方法栈
(HotSpot是把虚拟机栈和本地方法栈合二为一了)
虚拟机栈为虚拟机执行java方法服务
本地方法栈为虚拟机执行native方法服务
什么是native方法?
一个NativeMethod就是一个调用非java代码的接口。一个native方法是这样的一个java方法:该方法实现由非java语言实现,比如C。
Java堆
1.存放对象实例
2.垃圾收集器管理的主要区域
3.新生代、老年代、Eden空间
4.OutOfMemoryError
5.–Xmx –Xms动态分配
方法区
1.存储虚拟机加载的类信息(以下为例),常量,静态变量,即时编译器编译后的代码等数据。
l 类的版本
l 字段
l 方法
l 接口
2.方法区和永久代
3.垃圾回收在方法区的行为(回收效率低下,一般很少进行垃圾回收)
4.异常的定义
l OutOfMemory
直接内存和运行时常量池
运行时常量池
运行时常量池属于方法区的一部分。
常量池:用来存放编译期生成的各种字面量以及符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
直接内存(nio)
对象的创建
l 给对象分配内存
l 线程安全性问题
l 初始化对象
l 执行构造方法
给对象分配内存
1.指针碰撞(规整的堆内存)
2.但是实际上堆内存没有上图那么规整,实际上是杂乱的,有的地方使用,有的地方并未使用。相互交错。这是不能使用“指针碰撞”。可以维护一个列表,记录着哪些内存。这种方式成为“空闲列表”;
具体用哪个,取决于垃圾回收器。
线程安全性问题
l 线程同步(锁)
l 本地线程分配缓冲TLAB
初始化对象
对象的结构
1.Header(对象头)
(1) 自身运行时数据(Mark Word)
①哈希值(native)
②GC分代年龄
③锁状态标志
④线程持有的锁
⑤偏向线程ID
⑥偏向时间戳
(2) 类型指针
2.InstanceData
Longs double
short char 相同类型分配在一起
3.Padding
对象的访问定位
l 使用句柄
为什么使用句柄池而不是直接指针指向?
不管stack中的引用的对象是否被回收或者移动位置了,stack中存放的永远是句柄池的地址,永远不会变。
l 直接指针
优点是速度快,线程开销低。
HotSpot使用的是直接地址的方式。
垃圾回收
概述
问题:
1、如何判定对象为垃圾对象?
2、如何回收?
3、何时回收?
回答:
1.引用计数法、可达性分析法
2.两种:
u 回收策略:
l 标记-清除算法
l 复制算法
l 标记-整理算法
l 分代收集算法
u 常见垃圾回收器:
l Serial
l Parnew
l Cms
l G1
垃圾回收-引用计数法
在对象中添加一个引用计数器,当有地方引用这个对象的时候,这个引用计数器的值就+1,当引用失效的时候,计数器的值就-1。
但是现在的java虚拟机一般不使用这种算法。
代码中打印垃圾信息:
l -verbose:gc
l -xx:+PrintGCDetail
缺点,当对象循环引用时,不能判断出是垃圾对象。
垃圾回收-可达性分析法
定义GCroot,通过GCroot节点向下搜索,走过的路径,称为引用链,如果有对象没有被引用链连接的话,此对象可被回收。
作为GCroot的对象
l 虚拟机栈(栈帧中的局部变量表)
l 方法区的类属性所引用的对象
l 方法区中常量所引用的对象
l 本地方法栈中的引用的对象
回收算法-标记清除算法
两个过程,标记、清除。首先标记出所有需要回收的对象,在标记完成后,统一回收被标记的对象。
方法缺点:效率问题、空间问题。
回收算法-复制算法
复习虚拟机内存结构。
l 堆
n 新生代
u Eden 伊甸园(只要新创建的对象,都被扔到伊甸园中。GC经常光顾,如果没有被回收掉的话,此对象进入survivor区)
u Survivor存活区
u Tenured Gen (GC很少光顾)
n 老年代
l 方法区
l 栈 本地方法栈 程序计数器
为了解决效率问题,
把没有回收的对象放在另一块区域,并排列好。
回收算法-标记整理-清除算法
先把需要回收的内存移动到某一块区域,再直接清除这块区域内的所有对象。
分代收集算法
针对新生代和老年代的特点,选择不同的垃圾回收算法。新生代一般使用复制算法,老年代一般使用标记整理算法
Serial收集器
最基本,发展最悠久。在jdk1.3之前,回收新生代内存的唯一选择。单线程垃圾收集器。Serial仍有用途,由于单线程特点,短暂停顿可接受,因此可用于桌面应用client模式。
Parnew收集器
Parnew其实就是serial收集器的多线程版本。但是,中途还是要停的!!!在客户端情况下,其实性能不如serial收集器。在jdk1.5的时候,sun发布了cms,真正做到了一边扔垃圾一边清理的功能。Cms用于收集老年代内存。所以一般是serial/parnew+cms一起使用。使用的复制算法。
Parallel收集器
也是使用复制算法(针对新生代)、多线程收集器。这两点与parnew相同。
它与parnew收集器不同之处在于,parallel收集器的目标是达到一个可控制的吞吐量。吞吐量是CPU运行用户代码的时间与CPU总消耗时间的比值。
吞吐量=(执行用户代码时间)÷(执行用户代码的时间 + 垃圾回收占用时间)。
参数:
l -XX:MaxGCPauseMillis 垃圾收集器最大停顿时间
指定为1,表示1ms。疑问?会提高性能吗?
答案:不会。原因:parallel收集器主要收集新生代内存,想要在1ms内回收,内存必然会变小,才能实现时间内垃圾回收完成。内存变小是之后,回收的频率变高。例:10s收集一次,每次100ms,à 5s收集一次,每次70ms。停顿时间确实变小,但是整体效率没有提高。所以具体停顿时间需要具体分析。
l -XX:GCTimeRatio 吞入量大小
n (0,100)
n 默认值99
服务端注重的是吞吐量,高并发。Parallel合适使用。
Cms收集器(Concurrent Mark Sweep)
使用在老年代中。
工作过程
1.初始标记
2.并发标记
3.重新标记
4.并发清理
优点:
l 并发收集
l 低停顿
缺点:
l 占用大量的cpu资源
l 无法处理浮动垃圾(举例:边扔垃圾边打扫,打扫完后扔下垃圾,但是我需要等会再来打扫,这样的垃圾成为浮动垃圾。)
l 出现Concurrent Mode Failure
最牛-g1收集器
优势:
l 并行与并发
l 分代收集
l 空间整合
l 可预测的停顿
步骤:
1.初始标记
2.并发标记
3.最终标记
4.筛选回收
内存分配
内存分配-概述
1.优先分配到eden
2.大对象直接分配到老年代
3.长期存活的对象分配到老年代
4.空间分配担保(空间不够的话,问老年代借)
5.动态对象年龄判断
-Xmx:20M -Xms:20M -Xmn:10M (-XX:SurvivorRatio=8 表示指定了eden区大小为8M,那么新生代除了eden外,还有两个survivor各为1M)
eden 区一旦装不下有,得GC!区别Full GC!新生代的GC,存活的不多,是垃圾收集器最多光顾的,再者,消耗时间短。Full GC调用频率较少。所消耗时间长。所以,eden装不下后,GC之后,只能向老年代借。将eden现有的对象放入老年代,然后接受新对象。、
内存分配-大对象直接进入老年代
-XX:PretenureSizeThreshold
大对象:字符串、数组。
内存分配-长期存活的对象进入老年代
-XX:MaxTenuringThreshold
在eden区使用复制算法的垃圾收集一次后,eden区域的内存复制到了survivor区域,ahe=1, age是年龄计数器,如果下次回收后,还存活,age再加1,到达默认值15(jdk6)后,成为长期存活的对象。
注意:jdk6后,不再严格执行该标准,age不一定是15,可能在第一或者第二次GC后,就放入了老年代。
内存分配-空间分配担保
-XX:-HandlePromotionFailure // 开启禁用空间内存分配担保,默认是开启的
内存分配-逃逸分析与栈上分配
栈上分配:当一个方法执行的时候,常见一个栈帧,方法执行,栈帧进栈,方法执行完毕,栈帧出栈。这块内存区域是根据方法的执行来进行分配与释放,不需要垃圾回收器来回收,这样的效率比较高。那么如何将对象分配到栈上?使用逃逸分析。
逃逸分析:分析对象的作用域。方法体 内的变量在外部不能被使用,称为未发生逃逸,就能分配到栈上。
虚拟机工具
在jdk安装目录的bin目录下,
工具都依赖与tools.jar。
l jps
l jstat
l jinfo
l jmap
l jhat
l jstack
l jConsole
jps工具
Java Process Status
本地虚拟机唯一id lvmidlocal virtune machine id
C:\Documents and
Settings\Administrator>jps 2680 5532 Jps |
pid:2680 指的是eclipse
jps –l显示进程执行的jar文件全程包括包名
C:\Documents and
Settings\Administrator>jps –l 5628 Main aaaa 4528 sun.tools.jps.Jps 2680 |
jps –m
标识这个进程的主类所接受的参数(args[])指的是ProgramArguments
C:\Documents and
Settings\Administrator>jps –l 5628 Main aaaa 4528 jps 2680 |
jps-v查看VMarguments 虚拟机参数
C:\Documents and
Settings\Administrator>jps –l 5628 Main aaaa 4528 sun.tools.jps.Jps 2680 |
可以连用 jps-lvm
jstat工具
用于监视虚拟机运行的状态信息:类加载、内存、垃圾收集、jit编译的信息
gcutil:显示垃圾回收信息
C:\Documents and Settings\Administrator>jstat -gcutil 2680 S0 S1E O MCCS YGC YGCTFGC FGCT GCT 0.00 100.00 73.3862.70 93.02 -11 0.560 00.000 0.560 |
l S0:
l S1:
l E:eden
l O:老年代
l M:元空间
l CCS:压缩类的空间
l YGC:新生代垃圾收集器收集的次数
l YGCT:次数耗费的总时间
l FGC:full gc
l FGCT:full gc 耗费的时间
l GCT:GC total 总时间
jstat –gcutil 2680 1000 10
监控10次,每次间隔1000ms。
C:\Documents and
Settings\Administrator>jstat -gcutil 2680 1000 10 S0 S1 EO M CCSYGC YGCT FGCFGCT GCT 0.00 100.00 9.65 64.6193.74 - 261.554 0 0.0001.554 0.00 100.00 10.53 64.6193.74 - 261.554 0 0.0001.554 0.00 100.00 10.53 64.6193.74 - 261.554 0 0.0001.554 0.00 100.00 11.40 64.6193.74 - 261.554 0 0.0001.554 0.00 100.00 11.40 64.6193.74 - 261.554 0 0.0001.554 0.00 100.00 11.40 64.6193.74 - 261.554 0 0.0001.554 0.00 100.00 11.40 64.6193.74 - 261.554 0 0.0001.554 0.00 100.00 11.40 64.6193.74 - 261.554 0 0.0001.554 0.00 100.00 11.40 64.6193.74 - 261.554 0 0.0001.554 0.00 100.00 11.40 64.6193.74 - 261.554 0 0.0001.554 |
具体查看jstat的官方文档
元空间的本质类似永久代。现在永久代被元空间取代了,元空间不保存在虚拟机,而存在于本地内存,取决于本地内存的大小。
jinfo工具
实时查看和调整虚拟机的各项参数
C:\Documents and
Settings\Administrator>jinfo Usage: jinfo [option] <pid> (to connect to running process) jinfo [option] <executable <core> (to connect to a core file) jinfo [option] [server_id@]<remote server IP or hostname> (to connect to remote debug server) where <option> is one of: -flag <name> to print the value of the named VM flag -flag [+|-]<name> to enable or disable the named VM flag -flag <name>=<value> to set the named VM flag to the given value -flags to print VM flags -sysprops to print Java system properties <no option> to print both of the above -h | -help to print this help message |
jinfo
C:\Documents and
Settings\Administrator>jinfo Usage: jinfo [option] <pid> (to connect to running process) jinfo [option] <executable <core> (to connect to a core file) jinfo [option] [server_id@]<remote server IP or hostname> (to connect to remote debug server) where <option> is one of: -flag <name> to print the value of the named VM flag -flag [+|-]<name> to enable or disable the named VM flag -flag <name>=<value> to set the named VM flag to the given value -flags to print VM flags -sysprops to print Java system properties <no option> to print both of the above -h | -help to print this help message |
jmap工具
以前用过一个参数,-XX:+HeapDumpOnOutOfMemoryError 来生成堆快照信息
C:\Documents and
Settings\Administrator>jmap -dump:format=b,file=c:\a.bin 2680 Dumping heap to C:\a.bin ... |
回顾
原理+工具+案例
原理:
1、Java虚拟机的运行时区域
l 线程独占区:栈、程序计数器、本地方法栈
l 线程共享区:堆、方法区
2、对象的创建和回收
u 垃圾对象的标记算法:引用计数法、可达性分析法
u 垃圾收集算法:标记-清除、复制、标记整理、分代收集
u 垃圾收集器:serial(单线程)、parnew(和cms结合,多线程)、parallel(用在服务端默认的垃圾收集器,parallel关注于吞吐量)、cms、g1
u 对象内存分配原则:对象首先在eden区域分配、大对象直接进入老年代、长期存活的对象存活在老年代。
u 空间的分配担保
u 逃逸分析以及栈上分配
工具:
1、命令行工具:jps、jstat、jinfo、jmap、jhat、jstart
2、图像化工具:jConsole(监控内存和线程)、VisualVM(基于插件)
案例:
Class文件简介和发展历史
Eclipse编译java代码,是使用javac吗?
class文件用 二进制查看器打开。 binary viewer
class文件结构
class文件是一组8位字节位基础单位的二进制流,无符号数、表—》 基本数据类型和引用数据类型。
1.魔数
2.class文件版本
3.常量池
4.访问标志
5.类索引,父类索引,接口索引集合
6.字段表集合
7.方法表集合
8.属性表集合
魔数
标识文件的类型。java的魔数是:CA FE BA
BE 。如果javac打开的不是正确的class文件,首先魔数的验证就会出错!
1.jdk1.8=52
2.jdk1.7= 51
3.jdk1.6= 50
4.jdk1.5= 49
5.jdk1.4= 48
6.jdk1.3=47
7.jdk1.2= 46
8.jdk1.4= 45
0000 是次版本号 0032是主版本号 0032转为十进制是50指的是jdk6!
常量
CP_info
001D转为十进制29,表示常量池的长度是29。常量项是1-28
第一组:
tagàOA à10
name_index
0006à6表示指向第6常量池的位置
000Fà15指向第15个常量池的位置
第二组:
tagà09à9
name_index
0010à16
0011à17
使用javap –version HelloWorld.class命令查看
文件结构-访问标志
访问标志占两个字节16位,标识代码的标识符。通过javap命令查找发现:
#28 = Utf8 (Ljava/lang/String;)V |
这些字符代表着常量池的结束,也说明后面跟着的是访问标志!如图所示:
占两个字节,说明是0100。怎么推断出public?
这一节没看懂。
文件结构-类索、父类索引与接口索引集合
如上图,访问标志0021后面的0005是类的索引。
l 类索引:0005 à #5 = Class #21 // Hello
l 父类索引:0006 à #6 = Class #22 // java/lang/Object
l 接口索引:0000 à 实现了0个接口!没有实现接口。如果是0002的话,表示实现了两个接口,然后往后的4个字节就是所实现的两个接口。
文件类型-字段表集合
字段用于描述接口或类中声明的变量。
l 0001表示了字段的数量。
l 0002表示access_flags。表示使用了private修饰。查表
特征值 | 标志符 |
0x0001 | ACC_PUBLIC |
0x0002 | ACC_PRIVATE |
0x0004 | ACC_PROTECTED |
0x0008 | ACC_STATIC |
0x0010 | ACC_FINAL |
0x0040 | ACC_VOLATIVE |
0x0080 | ACC_TRANSIENT |
0x1000 | ACC_SYNTHETIC |
0x4000 | ACC_ENUM |
l 0004表示name_index,指向常量池第4个。#4 = Utf8 a
l 0005表示discriptor_index,指向常量池第5个。#5 = Utf8 I 这个I指的就是int.
l 0000表示的是没有属性表。
文件类型-方法表集合
同字段表,方法表集合描述的是方法。
l 0002表示两个方法。无参构造方法默认的也算一个。
l 0001àpublic
l 0004à <init>
l 0005à()V:表示构造方法的标志符 Void
l 0001àpublic
l 0006àCode
l 未完待续。。。属性表集合!
(II)I à (int a,int b )返回值int
文件类型-属性表集合
属性表在字段表和方法表的描述、名字、数据类型的后面。描述额外的属性???
在Code下!
attribute_info{
u2 attribute_name_index;
u2 attribute_length;
u1 info[attribute_lenght];
}
具体的属性表怎么看,看书吧,不难。。
字节码简介
java虚拟机把代码翻译成了字节码指令。(javap –verbose
xx.class中最后的部分)。java虚拟机的指令由一个字节长度的,代表着某种特定操作含义的数字,称之为操作码,以及跟随其后的零至多个代表此操作所需参数的操作数而构成。
操作数的长度为1个字节,因此最大只有256条。
基于栈的指令集架构。
字节码与数据类型
在虚拟机的指令集中,大多数的指令都包含了其操作所需的数据类型的信息。例如:
l iload、fload:把局部变量中的数据加载到操作数栈中;
l 大多数的指令包含了类型信息;
l 也有不包含类型信息的:goto与类型无关,Arraylength操作数组类型;
l 类型多,指令少
加载指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
将局部变量表加载到操作数栈:iload lload fload dload aload(int long float double 引用。
将数值从操作数栈存储到局部变量表:istore lfda
分析class,args_size都比实际的参数大1,是什么??是this。
// a和b放在了局部变量表中,想要执行,就要加入到操作数栈中,所以必须调用iload,将第0个和第1个放入。
将常量加载到操作数栈:bipush sipush ldc ldc_w ldc2_w aconst_null iconst_m1 iconst
扩充局部变量表的访问索引的指令:wide
运算指令
运算或算术指令用于对两个操作数栈上的值进行某种特定的运算,并把结果存储到操作数栈顶。
加法指令:add i l f d 表示什么类型的相加
减法指令:sub
乘法指令:mul
除法指令:div
取余指令:rem
取反指令:neg
类型转换指令
类型转换指令可以将两种不同的数值类型进行相互转换,这些转换操作一般用于实现用户代码中的显示类型转换操作以及用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。
宽化类型处理和窄化类型处理。int i=10;占4个字节,比较窄,long a = i;占了8个字节比较宽(向上和向下转型)。User user = getUser(); Object obj = user;
i2b i2c i2s i2i intà long int à float …
对象创建与访问指令
l 创建类的指令:new
l 创建数组的指令:newarray anewarray
multinewarray
l 访问类字段getfield putfield getstatic
putstatic
l 把数组元素加载到操作数栈的指令:baload c s l I f d a
l 将操作数栈的值存储到数组元素:astore
l 取数组长度的指令:arraylength
l 检查实例类型的指令:instance of checkcast
调用实例初始化,父类初始化和私有方法。invokespecial
操作数栈指令
n 操作数栈指令用于直接操作操作数栈。
n 将操作数栈的一个或两个元素出栈:pop pop2
n 复制栈顶一个或两个数值并将复制或双份复制值重新压入栈顶:dup dup2
dup_x1 dup_x2
n 将栈顶的两个数值交换:swap
控制转移指令
n 控制转移指令:if else while 。。。。。
n 控制转移指令其实就是在修改pc,程序计数器的值!
n 条件分支:ifeq iflt ifle inne ifgt ifnull
ifcmple
n 复合条件分支:tableswitch lookupswitch
n 无条件分支:goto goto_w jsr jsr_w ret 但是goto不允许用!
n
方法调用和返回指令
方法调用:
l invokevirtune:调用对象实例方法
l invokeinterface:调用接口
l invokespecial:调用一些需要特殊处理的实例方法。调用init方法的时候。就需要这么用
l invokestatic:调用类方法
方法返回:
l ireturn breturn breturn freturn
l void方法是直接的return
异常处理指令
显示抛出异常操作由athrow指令实现,别的异常会由java虚拟机自动抛出。
Exception
table 异常表 维护try-catch。
同步指令
隐式,无需通过字节码指令控制。monitorenter monitorexit 。管程
类加载机制概述
1.类加载机制概述
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
懒加载机制。(用的时候去加载,节约系统资源。)jsp、osji模块化都是通过懒加载实现。
2.类加载的时机
加载在jvm规范中未指明。主流采用懒加载,启动的过程中,加载全部字节码。加载开始之后,加载和连接并不是先后关系,当加载完毕之后,连接再关闭。初始化的过程有严格的规定。如下:
l 遇到new、getstatic(读取类的静态字段时候)、putstatic、invokestatic这四条自己吗指令时
l 使用java.lang.reflect包
l 当初始化一个类的时候,如果发现父类还没有初始化,则需要先触发父类的初始化
l 当虚拟机启动后,用户需要制定一个要执行的父类,虚拟机会先初始化这个类
不被初始化的例子:
l 子类引用父类的静态字段,但子类不被初始化!
l 通过数组来引用类
l 调用类的常量 public static final int var = 20;
3.类加载的过程-加载
l 通过一个类的全限定名来获取定义此类的二进制流
l 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
l 在内存中生成一个代表这个类的class对象,作为这个类的各种数据访问入口
加载源:
l 文件
n Class文件
n Jar文件
l 网络
n applet
l 计算生成的一个二进制流
n $Proxy
l 有其他文件生成
n jsp生成servlet
l 数据库
4.类加载的过程-验证
l 验证是连接的第一步,这一阶段的目的是为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
l 文件格式验证 魔数+版本号+常量池+
l 元数据验证
l 字节码验证
l 符号引用验证
5.类加载的过程-准备
准备阶段正式为类变量分配内存并设置变量的初始值。这些变量使用的内存都将在方法区中进行分配。
这里的初始值并非我们指定的值,而是其默认值。但是如果被final修饰,那么在这个过程中,常量值会被一同指定。
class hello{
public static int a = 10;
}
6.类加载的过程-解析
l 解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时无歧义的定位到目标即可。直接引用可以使直接访问目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
l 类或接口的解析
l 类方法解析
l 接口方法解析
7.类加载的过程-初始化
初始化是类加载的最后一步
初始化时执行<cinit>()方法的过程
init时执行构造的时候执行,cinit是执行类和接口的
8.类加载器
l 启动类加载
n 由c++实现,是虚拟机的一部分,用于加载javahome下的lib目录下的类
l 拓展类加载器
n 加载javahome下/lib/ext目录下的类
l 应用程序类加载器
n 加载用户类路径上所指定的类库
l 自定义类记载器
自定义类加载器
1.定义各类,继承ClassLoader
2.重写ClassLoader方法
3.实例化class对象
4.双亲委派模型
虚拟机字节码执行引擎
1.运行时栈帧结构
2.局部变量表
3.操作数栈
4.动态链接
5.方法返回地址
6.附加信息
7.方法调用-解析调用
8.方法调用-静态分配调用
9.方法调用-动态分配调用
10.动态类型语言支持
11.字节码执行引擎小结
12.总结和回顾
java内存模型与线程
1.happens-before简单概述
2.重排序问题
3.锁的内存语义
4.volatile的内存语义
5.final域的内存语义