-
- 类文件结构
类型 | 名称 | 数量 | 描述 |
u4 | magic | 1 | 魔数 |
u2 | minor_version | 1 | 次版本号 |
u2 | major_version | 1 | 主版本号 |
u2 | constant_pool_count | 1 | 常量池容量计数值 |
cp_info | constant_pool | constant_pool_count-1 |
|
u2 | access_flags | 1 | 访问标志 |
u2 | this_class | 1 | 类索引 |
u2 | super_class | 1 | 父类索引 |
u2 | interfaces_count | 1 | 接口索引集合数量 |
u2 | interfaces | interfaces_count | 接口索引集合 |
u2 | fields_count | 1 | 字段表索引数量 |
field_info | fields | fields_count | 字段表 |
u2 | methods_count | 1 | 方法表索引数量 |
method_info | methods | methods_count | 方法表 |
u2 | attributes_count | 1 | 属性 |
attribute_info | attributes | attributes_count | 属性表 |
魔数是啥?
每个Class文件的头4个字节为魔数,唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。很多文件存储中也会使用魔数进行身份识别,如图片格式(gif、jpeg等)
为什么使用魔数识别而不是扩展名?
出于安全考虑,扩展名可以随意改动。而文件格式的制定者可以自由选择魔数值,只要这个魔数值没有被广泛采用过且不会引起混淆。Class文件的魔数为0xCAFEBABE。
扩展1:难道Class文件中的魔数不会被篡改么?
紧接着的是2字节的次版本号,和2字节的主版本号。从JDK1.1的45.0开始。虚拟机拒绝执行高于自己版本的Class文件,即使文件格式未变化。
字节码文件包含了哪些内容?有什么内在关联?
字节码文件包括了魔数、常量池、版本号、父类索引表、方法索引表表、字段索引、属性表。比如方法索引描述了方法头的一些信息,比如访问权限,是否同步等,是否static、final、native、abstract修饰等,而代码部分则在属性表中,名称则索引到常量池。对于字段表也是类似,主要包括字段被哪些关键字修饰,而名字、类型(可能是自定义的类类型)则引用到常量池。因而,常量池包含的内容很多,除了定义的常量,字符串常量等,还有字段名、字段类型,方法名、方法返回类型等。
魔数、常量池
垃圾回收与算法
垃圾收集器 | 算法 | 代 | 描述 |
Serial | 复制 | 新生代 | 优点:简单高效 缺点:服务暂停 |
Serial Old | 标记-整理 | 老年代 |
|
ParNew | 复制 | 新生代 | Serial的多线程版 |
Parallel Scavenge | 复制 | 新生代 | 高吞吐量 |
Parallel Old | 标记-整理 | 老年代 |
|
CMS | 标记-清除 |
| 高并发、低停顿 优点:并发、低停顿 缺点:空间碎片、并发阶段低吞吐 |
G1 | 整体:标记整理 局部:复制 | 不分代 | 并发并发 可预测停顿 |
JVM内存模型
Java内存管理包括哪些内容?如何组织起来的?
猜测:
(1)JVM的运行时内存分区(不同区的功能划分与协作)
(2)堆内存的分配与回收
(3)
拓展1:Java内存模型包括哪些内容?又是如何管理的?
线程私有的空间:虚拟机栈、程序计数器、本地方法栈
线程共享空间:堆、持久代/元空间、直接
JVM的各种空间
栈:默认大小1M
扩展1:对于这些空间的大小,如果需要优化,应该调大或调小还是其它的处理手段呢?
答:
(1)栈的默认空间1M,一般需要调小,128K即可
程序计数器:每个线程有一个独立的程序计数器,用于存储当前正在执行的程序的内存地址。(由于多线程交叉执行,在当前线程被中止,需要交出CPU控制权时,需要记录当前线程在内存的地址)
Java虚拟机栈:在创建线程时创建一个与之对应的栈,每个栈包含多个栈帧,栈帧与方法关联起来,栈帧包含局部变量表、操作数栈、动态链接、返回地址。当一个方法执行完,会将该栈帧执行后的元素作为返回值弹出,可能作为下一个栈帧的操作数。程序计数器指向当前正在执行的栈帧。Java栈与线程对应,因而不是线程共享的,不涉及线程安全问题。
栈帧包括:局部变量表、操作数栈、动态连接、返回地址,虚拟机规范允许增加附加信息(如与调试相关的信息)
字节码指令
invokestatic:调用静态方法
invokespecial:调用实例构造器<init>方法、私有方法、父类方法
invokevirtual:调用所有虚方法
invokeinterface:调用接口方法,在运行时再确定一个实现此接口的对象
invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。
Invokestatic、invokespecial调用的方法可以在解析阶段确定唯一版本,符合条件的有静态方法、私有方法、实例构造器、父类方法。称为非虚方法
synchronized的字节码原理
对于synchronized方法,是给方法加了一个标记
对于synchronized代码块,使用了Moniter,每个对象头中有一个monitor,进入synchronized块时对应moniterenter,退出时对应moniterexit,对于有try-catch的代码,在try部分和catch部分各有一个moniterexit。
问题1.为什么不直接在finally里加呢?
问题2.对于没有定义finally的方法,是否会有一个默认的finally块?
静态分派
静态分派针对的是重载,即在编译期就能确定引用的静态类型,在运行时根据其静态类型确定方法的正确重载版本。找到与实参最匹配的形参的方法的重载版本,如果没有完全匹配的则向上转型(eg:char->int->Character->Serializable)。
动态分派
动态分派针对的是重写,即在运行期才能确定引用真正指向的类型,在运行期根据搜索引用真正指向的对象的符号引用,找到对象类型,匹配方法,若匹配不到则向父类搜索匹配。
问题1:对于一个子类有重写父类方法,用父类型引用指向子类型对象来调用方法,最终执行的是哪个版本?为什么?
答:执行的是子类方法,Java静态多分派,动态单分派。这就是常见的多态。
可作为GC Roots的对象有哪些?
1.虚拟机栈(本地变脸表)中引用的对象
2.方法区:类静态属性
3.方法区:
java.lang.invoke包
java.lang.invoke包的作用是提供动态确定目标方法的版本,区别于依靠符号引用的方式,将方法的版本不是固化在字节码上,而是由用户用户代码来确定。可以实现类似C/C++中的函数接口的功能。
应用场景1:调用祖父类的同名方法
MethodType mt = MethodType.methodType(返回值类型.class);//eg:void.class
MethodHandle mh = lookup().findSpecial(GrandFather.class,”方法名”,mt,getClass());
mh.invoke(this);
这样就可以调用祖父类的指定方法了(“方法名”换成指定的方法)。P268
1.内存泄露和内存溢出的差别及各自的解决办法?
猜测:
泄露是没有有效回收无用内存导致的内存不够?
溢出是指正常运行使用的内存空间超过了可用空间?
2. java.lang.OutOfMemoryError的不同场景与解决方法
-
- 内存管理
-
- 虚拟机性能监控与故障处理工具
名称 | 作用 | 备注 |
jps | JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程 |
|
jstat | JVM Statistics Monitoring Tool,收集HotSpot虚拟机各方面的运行数据 |
|
jinfo | Configuration Info for Java,显示虚拟机配置信息 |
|
jmap | Memory Map for Java,生成虚拟机内存转储快照(heapdump文件) |
|
jhat | JVM Heap Dump Browser,用于分析heapdump文件,会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果 |
|
jstack | Stack Trace for Java,显示虚拟机的线程快照 |
|
jps
列出正在运行的虚拟机进程,并显示虚拟机执行主类(main方法所在类),以及这些进程的本地虚拟机唯一ID(LVMID,与操作系统PID一致)
命令格式:jps [option] [oolea]
-q:只输出LVMID,省略主类名称
-m:输出虚拟机进程启动时传递给主类main()方法的参数
-l:输出主类全名,如果是jar包,输出jar路径
-v:输出虚拟机进程启动时的JVM参数
jstat
可显示本地或远程(需要远程主机提供RMI支持)虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。在只提供文本控制台环境的服务器,是运行期定位虚拟机性能的首选工具
命令格式:jstat [option vmid [interval[s|ms] [count]] ]
对于本地虚拟机,VMID即LVMID;对于远程主机,VMID格式是:
[protocol:] [//]lvmid[@hostname[:port]/servername]
intervar代表查询间隔,count代表查询次数,省略表示只查一次
选项 | 作用 |
-class | 监视类装载、卸载数量、总空间以及类装载所耗费的时间 |
-gc | 监视Java堆状况,包括Eden区、2个survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息 |
-gccapacity | 监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间 |
-gcutil | 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比 |
-gccause | 与-gcutil功能一样,但会额外输出导致上一次GC产生的原因 |
-gcnew | 监视新生代GC状况 |
-gcnewcapacity | 监视内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间 |
-gcold | 监视老年代GC状况 |
-gcoldcapacity | 监视内容与-gcold基本相同,输出主要关注使用到的最大、最小空间 |
-gcpermcapacity | 输出永久代使用到的最大、最小空间 |
-compiler | 输出JIT编译器编译过的方法、耗时等信息 |
-printcompilation | 输出已经被JIT编译的方法 |
jinfo
实时查看和调整虚拟机各项参数。
Jinfo [option] pid
jmap
用于生成堆转储快照(一般称为heapdump或dump文件)
jmap [option] vmid
选项 | 作用 |
-dump | 生成Java堆转储快照。格式为:-dump:[live,]format=b,file=<filename>,其中live子参数说明是否只dump出存活的对象 |
-finalizerinfo | 显示F-Queue中等待Finalizer线程执行finalize方法的对象。只在Linux/Solaris平台下有效 |
-heap | 显示Java堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在Linux/Solaris平台下有效 |
-histo | 显示堆中对象统计信息,包括类、实例数量、合计容量 |
-permstat | 以ClassLoader为统计口径显示永久代内存状态。只在Linux/Solaris平台有效 |
-F | 当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照。只在Linux/Solaris平台下有效 |
jhat
与jmap搭配使用,用于分析jmap生成的堆转储快照。
Jstack
用于生成虚拟机当前时刻的线程快照(一般称为threaddump或javacore文件)。
线程快照:当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。
Jstack [option] vmid
选项 | 作用 |
-F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用本地方法,可以显示C/C++的堆栈 |
java.lang.Thread的getAllStackTraces()可以获取虚拟机中所有线程的StackTraceElement对象,能够实现jstack的大部分功能。
-
- JMM
Java内存模型可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。通过内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。
Java内存模型的目标:定义程序中各个变量的访问规则。
工作内存的描述比较类似虚拟机栈的部分区域,主内存类似于堆内存,但并不是同一个层次,为了获得更好的运行速度,虚拟机可能会让工作内存优先存储于寄存器和高速缓存。
Java内存模型定义了8种内存交互的原子操作
lock:作用于主内存的变量
unlock:作用于主内存的变量
read:V主-----à工作内存
load:V工-----à工作内存变量副本
use:V工-----à执行引擎(使用变量值的字节码指令都会执行)
assign:执行引擎-----àV工(赋值字节码指令都会执行)
store:V工----à主内存
write:V主-----à主内存变量
read与load、store与write需要保证顺序,不需要保证连续
8中原子操作还有8条操作规则,原子操作与规则,加上volatile特殊规则,确定了线程安全的操作,先行发生原则是原子操作与操作规则的等效判断原则,用来确定一个访问在并发环境下是否安全。
volatile是Java虚拟机最轻量级的同步机制。
-
- 调优
- GC调优
- 调优
一般来说,堆越大越好
(1)降低GC频率,但频率过低会增加单次GC时间
(2)对象更可能成为垃圾
32位操作系统单进程最大可用内存位2G,64位无限制
平衡New和Old的比例
-Xms:最小堆
-Xmx:最大堆
-XX:NewSize:新生代初始大小
-XX:MaxNewSize:新生代最大值
-XX:NewRatio:New和Old的比值
-Xmn:用于设定新生代大小(处于性能考虑,可固定新生代大小)
-XX:SurvivorRatio:Eden和Survivor的比值
-XX:+PrintTenuringDistribution:查看每次Minor GC后年龄的分布和计算出来的Tenuring Threshold
每次堆调整会触发一次Full GC,为避免频繁调整,可设置-Xms=-Xmx
设置-Xms为堆的预期大小(堆调整的代价较大)
内存允许的情况下,设置-Xmx为比-Xms更大的值
新生代调优:
增大Eden能够降低Minor GC频率,不一定会增大Minor GC的时间
尽可能让对象待在Survivor中,使其被新生代回收(减少对象晋升和降低旧生带GC频率)
避免长时间存活的对象在Survivor间的不必要拷贝
Tenuring Threshold:对象晋升的年龄阈值。每熬过一轮GC,年龄加1。Serial Copying和ParNew每次Minor GC后重新计算年龄。
老生代调优:
尽可能优先调优新生代,尤其CMS的promotion
(对CMS)在不紧要的时间段手动进行Full GC
去掉不必要的缓存;Oracle 10g时PreparedStatement缓存太大