说在前面的话
<深入理解java虚拟机> link 是一本jvm入门经典,推荐所有java工程师阅读,并应该多读,不同阶段读. 这篇博客就是为了总结本人从该书中的领悟.
运行时的数据分区
- 程序计数器:线程执行的字节码行号.
- 虚拟机栈:生命周期和线程相同,描述的是java方法执行的内存模型, 每个方法执行的同时都会创建一个栈帧(stack frame).存放局部变量、操作数栈、动态链接、方法出口等信息.
- 堆:存放对象实例.随着JIT编译器、逃逸分析技术,栈上分配、标量替换导致不一定所有的对象都在堆上.
- 方法区:虚拟机加载的类信息、常量、静态变量、JIT编译的代码。堆的一个逻辑部分。hotspot使用永久代实现方法区,会受限于MaxPermSize
- Direct Memory: DirectByteBuffer分配堆外内存.不受-Xmx参数控制.
java对象
java 对象的组成: 对象头(header)+实例数据(instance data)+对齐填充
- 对象头:类的元数据、hash码、gc分代).
- 实例数据:包括父类和自身定义的字段内容.
- 对齐填充,保证8字节的整数倍.
对象的访问定位,通过栈上的reference操作堆.
内存/栈溢出
-
heap oom,分代(-Xmx -Xms:堆大小控制,Xmn:新生代大小,SurvivorRatio: survivor区大小).
Memory Leak:内存无法被回收,GC Roots引用链. Memory overflow:调大内存,优化代码.
-
stack overflow(虚拟机栈和本地方法栈)
栈深度支持到1000-2000,如果超过会导致stackoverflow. 如果线程数量过多,则会报错out of memory(受限于一个进程可用内存-堆内存-方法区内存),每个线程需要分配一个栈容量,参数为-Xss.
-
方法区和运行时常量池的溢出
String.intern :常量池. 方法区,除了已加载的类,还有许多动态生成类,如cglib、动态代理、osgi、jsp等。
-
本机直接内存directMemory
用反射操作unsafe.
GC
对象存活的判定算法:判断对象和GCROOT引用链是否可达来判断对象是否已死. GC root包括:
- 虚拟机栈的本地变量表引用的对象.
- 方法区中的静态变量引用的对象.
- 方法区中常量池引用的对象.
- 本地方法栈JNI引用的对象
引用的分类, 由强到弱:
标题1 | 标题2 | 标题3 |
---|---|---|
强引用 | Object o = new Object | 永远不会回收被引用的对象 |
软引用 | SoftReference | 在内存溢出前被回收 |
弱引用 | WeakReference | 活到下一次gc. |
虚引用 | PhantomReference | 无法通过引用获取,在被回收时收到通知 |
方法区的GC,回收效率低,效果差:
- 无效的常量,如"abc"进入了方法区,但没有地方引用.
- 无效的类信息, 如所有实例被回收/classLoad卸载.
- 反射生成的类, 需要虚拟机具备类卸载功能.
垃圾收集算法:
- 标记清除算法. 产生空间碎片,标记和清除的效率都比较低.
- 标记复制算法. 内存可用空间降低, 新生代使用,由于大部分对象朝生夕死, 不需要1:1,一般是8:1:1.
- 标记整理算法. 老生代使用.
垃圾收集器:
垃圾收集器 | 作用分代 | 特点 | 其他 |
---|---|---|---|
Serial | 新生代 | 简单,回收效率低,stop the word | 用于client模式下 |
ParNew | 新生代 | 并行回收,stop the word | 用于server模式下. |
Parallel Scavenge | 新生代 | 并行回收,关注吞吐量 | 适合后台运算. |
SerialOld | 老年代 | 简单,回收效率低,stop the word | 用于client模式下 |
ParallelOld | 老年代 | 并行回收,关注吞吐量 | 适合后台运算. |
CMS | 老年代 | 多次标记,锁定stop the word时间, 关注响应时间 | 适合交互式的应用. 因为是标记清除算法,有内存碎片. |
G1 | 独立完成新生代和老年代收集 | 分regin,按优先级回收,可预测的停顿 | 追求停顿时间短 |
GC日志
项 | 说明 |
---|---|
33.125 | 从虚拟机启动到GC时经过的秒数 |
GC | 表示本次GC并没有stop the world(并不是分代的区别)DefNew和收集器相关,Serial收集器定义的新生代 |
3324K->152K(3721K) | GC前当前区域已使用的内存->GC后当前区域已使用的内存(该区域最大内存) |
0.0025925secs | GC所占用的时间,有些收集器会给出更详细的时间: |
[Times:user=0.01 sys=0.00,real=0.02secs]
其他 | 外面的3324K->152K(11904K),..
表示当时java堆的情况
GC相关参数
参数 | 说明 |
---|---|
NewRatio | 新生代( Young ) 与老年代( Old ) 的比例,默认为1:2. |
SurvivorRatio | Eden区和survivor区的比例,默认8:1. |
MaxTenuringThreshold | 对象多少次minorGc后进入老年代. |
虚拟机故障处理工具
名称 | 用途 |
---|---|
jps | 列出所有java进程 |
jmap | 输出java内存信息 |
jstat | java统计信息 |
jhat | 分析java内存dump文件 |
jstack | 输出java线程堆栈 |
jinfo | 虚拟机配置信息 |
jvisualvm | 可视化工具,Btrace织入调试代码. |
最佳实践
- java进程内存不宜过大,64位jdk内存利用率低于32位.
- Runtime.getRuntime().exec() 是fork一个镜像java进程,去执行命令,效率低.
- 非堆内存包括direct memory,线程堆栈,socket buffer ,jvm本身占用.
- 集成慢系统,考虑用消息队列解耦.
- Map有效数据占比不高.
类文件结构
关于类的文件结构,只讲几个比较重要的概念
- 类的全限定名: java/lang/String.
- 方法描述: int typeOf(String a) 描述为(Ljava/lang/String a) I
- javap -verbose xx.class.
字节码指令
- 基于操作数栈的指令.
- 宽化处理, int->long,float,double ... 窄化处理,不会抛出运行时异常.
- 异常由异常表处理,而不是指令.
- 方法级的同步由管程(monitor)处理, 代码块由指令处理.
虚拟机类加载机制
- 加载. 通过全限定名获取二进制流/将二进制流加载到虚拟机/生成class对象作为入口, 用户可以自定义加载方式(压缩包、网络、数据库、文件等)。
- 验证. 验证字节流是否符合class文件规范,如果多次加载,可以关闭这个步骤.
- 准备. 初始化类变量(0值).
- 解析. 将符号引用转为直接引用.
- 初始化. 执行类的()方法(()是实例方法), 静态块/类变量赋值等.
- 使用.
- 卸载.
类加载模型
双亲委派模型
- 启动类加载器, javahome/lib,由c++实现.
- 扩展类加载器, javahome/lib/ext.
- 应用程序类加载器, 应用的classpath.
- 先通过父加载器查找,如果查找不到则在本加载器内查找.
-
- spi上层调用下层(通过线程上下文加载器)都是破坏双亲委派模型.
Osgi : 网状的模型.
- spi上层调用下层(通过线程上下文加载器)都是破坏双亲委派模型.
虚拟机的字节码执行引擎
- 基于栈桢, 包括操作数栈、局部变量表、动态链接、方法出口.
- java是静态多分派(重载),动态单分派(重写).
- 静态语言是变量类型编译器决定,符号引用中包含类型. 变量有类型.
-
tomcat 是正统的双亲委派模式. 默认 common/shared/webapp合三为lib.
- common : tomcat/应用共享.
-
shared: 应用间共享
-
webapp: 应用内.
- jsp: 动态部署.
早期编译期优化
-
- 分析与字节码生成中需要解语法糖(包括泛型、自动装箱/拆箱、可变参数、try resource、switch、遍历循环、内部类、断言、条件编译等).
- java是低糖语言.
JMM
- 原子性. 基础操作是原子的, 更大粒度的原子操作需要借助lock/unlock.
- 可见性.volatile, 可以保证可见性, 在依赖运算判断/联合其他可变变量的情况以外,可以替代锁. final/synchronized也保证来可见性.
-
有序性, 在多线程的情况下,对指令重排序,来加快运算速度。
- volatile, 保证写优先与读.
- 先行发生原则:程序有序/管程有序/Thread.start/Thread.interrupt/Thread终止/finalizer/volatile/传递性.
Java线程
- 线程模型:内核态、用户态、混合模型.
- 线程的调度方式:抢占式/协调式(不靠谱的优先级).
- 线程的状态切换:
线程安全和锁优化
- 悲观锁(阻塞式), synchronize, ReentrantLock(多个条件绑定、公平和非公平锁、等待中断).
- 乐观锁(非阻塞式), cas 由硬件支持.
- 无锁模型: 可重入代码/ThreadLocal、生产者消费者.
- 锁优化: 适应性自旋、锁消除、锁粗化、轻量级锁(先尝试一次cas)、偏向锁(同一个线程只做一次cas)