JVM的基本结构
了解JVM是每个学java人的必经之路,JVM对于java就像心脏对于人一样,JVM的结构如下图:
1. PC寄存器
每个线程拥有一个PC寄存器,在线程创建时创建,指向的是下一条指令地址,但是在执行本地方法时,PC的值是未定义的。
2. 方法区
保存装载的类的元信息(类型的常量池,字段、方法信息,方法字节码等),被所以的线程共享
3. java堆
和方法区一样,所以的线程都共享java 堆,对分代GC算法来说,堆也是分代的,结构如下:
4. java栈
java栈为线程私有,由一系列帧组成,帧中保存一个方法的局部变量、操作数栈、常量池指针
注意:小对象在没有逃逸的情况下,可以直接分配在栈上,如果要取消优化可以使用-XX:-DoEscapeAnalysis参数
5. java线程
每一个线程有一个私有工作内存(java栈、本地栈、PC寄存器),这个工作内存与主内存是独立的,并且工作内存存放主存中变量值的拷贝,工作内存和主存之间可以用下图表示:
当数据从主存复制到工作内存时,必须经过两个动作:read由主存执行读操作,load由工作内存执行load操作;当数据从工作内存拷贝到主存时,也必须经过两个动作:store由工作内存执行store操作,write主存相应的write操作。
因此对于普通变量,一个线程更新的值,不能马上反应在主存中,可以使用volatile关键字来消除这种情况,但是volatile并不能保证100%的同步,因为指令重排会破坏线程间的有序性,如下面这个例子:
class Test
{
volatile int a=0;
boolean flag=false;
//线程1调用
public void writer()
{
a=1;
flag=true;
/*上面两条语句在编译时为了优化,可能发生指令重排,重排后的结果如下
*flag=true;
*a=1;
*/
}
//线程2调用
public void reader()
{
if(flag)
{
int i=a+1;
......
}
}
}
JVM的配置参数
- -verbose:[gc|class|jni]
打开GC日志,显示已加载类的信息,显示有关JNI调用的信息 - -XX:+PrintGC、-XX:+PrintGCDetails、-XX:+PrintGCTimeStamps
显示GC的信息 - -Xloggc:path
指定GC log的位置,以文件输出 - -XX:PrintHeapAtGC
每次GC后,都打印堆信息 - -XX:+TraceClassLoading
监控类的加载,与-verbose:class相同 - -XX:+PrintClassHistogram
打印类的信息 - -Xmx -Xms
指定最大堆和最小堆 - -Xmn -XX:NewRatio -XX:SurvivorRatio
设置新生代的大小、新生代与老年代的比值、老年代与幸存器的比值 - -XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpPath -XX:OnOutOfMemoryError
OOM时导出堆到文件及文件的路径、执行的脚本 - -Xss
分配栈的大小
类装载器
class装载流程
- 加载
装载类的第一个阶段,取得类的二进制流,转为方法区数据结构,在java堆中生成对应的java.lang.Class对象 - 链接
- 验证
验证的目的是保证Class流的格式是正确的,包括文件格式验证(是否以0xCAFEBABE开头、版本号是否合理),元数据验证(是否有父类、是否继承了final类等),字节码验证,符号引用验证 - 准备
分配内存,并为类设置初始值(方法区中)
注意 final static变量在这阶段被直接赋值 - 解析
将符号引用替换为直接引用
- 验证
- 初始化
- 执行类的构造器clinit(包括static变量赋值语句、static 语句块)
- 子类的clinit调用前保证父类的clinit被调用
- clinit是线程安全的
JDK中的ClassLoader