深入理解Java虚拟机:JVM高级特性与最佳实践---笔记
第一部分 走进java
JDK(Java Development Kit):用于支持Java程序开发的最小环境,包括java程序设计语言、java虚拟机、javaAPI类库。
JRE(Java Runtime Environment)支持java程序运行的标准环境,包括javaAPI类库中的Java SE API子集和Java虚拟机。
自己编译JDK:
1)获取JDK源码
认真阅读README-builds.html;
2)在IDE工具中进行源码调试:到NetBeans网站(http://netbeans.org/)上下载最新版的NetBeans,下载时选择支持C/C++开发的那个版本。安装后,建一个新项目。
第二部分 自动内存管理机制
第2章 Java内存区域与内存溢出异常
1、运行时数据区域:
1)程序计数器(Program Counter Register):是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。在虚拟机概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。每个线程都需要有一个独立的程序计数器,各个线程之间的程序计数器互不影响,这类内存称为线程私有内存。
2)java虚拟机栈(Java Virtual Machine Stacks):也是线程私有,它的生命周期与线程相同。它描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Fram)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,都对应的一个栈帧在虚拟机中入栈到出栈的过程。
异常:StackOverflowError异常-------线程请求的栈深度大于虚拟机所允许的深度
OutOfMemoryError异常-------栈扩展时无法申请到足够的内存时
3)本地方法栈(Native Method Stack):与虚拟机栈类似,它们的区别是虚拟机栈为虚拟机执行的java方法(也就是字节码)服务,本地方法栈则为虚拟机使用到的Native方法服务。
异常:StackOverflowError异常-------线程请求的栈深度大于虚拟机所允许的深度
OutOfMemoryError异常-------栈扩展时无法申请到足够的内存时
4)Java堆(Java Heap):用来存放实例对象,被所有线程共享。是Java虚拟机所管理的内存中最大的一块。
5)方法区(Method Area):与java堆一样也是,是各个线程共享的区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
异常:OutOfMemoryError:当方法区无法满足内存分配需求时。
6)运行时常量池(Runtime Constant Pool):是方法区的一部分。Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
异常:OutOfMemoryError:当常量池无法申请到内存时。
7)直接内存(Direct Memory):它不是虚拟机运行时数据区的一部分,也不是java 虚拟机规范定义中的内存区域。在JDK1.4中新加入NIO(New Input/Output)类,引入一种基于甬道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存引用进行操作。这样避免了在java堆和Native堆中来回复制数据,能在一些场景中显著提高性能。
本机直接内存不会受到java堆内存的限制,但是会受到本机总内存大小以及处理器寻址空间的限制。当动态扩张时超出内存会出现OutOfMemoryError。
2、hotspot虚拟机对象探秘
1)对象的创建
a、遇到new指令
---》检查指令参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和初始化过。若无则必须进行相应的类加载过程
-------》为新对象分配内存
------》虚拟机将分配到的内存空间都初始化为零值(不包括对象头),这一操作可以保证对象的实例字段在java代码中可以不赋初始值就直接使用,程序能够访问到这些字段的数据类型所对应的零值
----------》对对象进行必要的设置。eg:该对象是哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息都存放在对象头(Object Header)里。
b、执行init方法,初始化对象。
2)对象的内存布局
a、对象头(Header)
第一部分:用于存储对象自身的运行时数据(eg:哈希码(HashCode)、GC粉代年龄、锁状态标志、线程持有的锁、偏向线程的ID、偏向时间戳 等);
第二部分:类型指针-----对象指向它的类元素指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
b、实例数据(Instance Data)
对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
c、对齐填充(Padding)
非必然存在,无意义的,仅仅充当占位符的角色。
3)对象的访问定位
a、句柄------reference存储稳定的句柄地址
java堆中划出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据和类型数据各自的具体地址信息。
b、直接指针----------速度快,节省了一次指针定位的时间开销。
reference中存储的是对象地址,java堆对象的布局要考虑如何放置访问类型数据的相关信息。
3、OutOfMemoryError
1)java堆溢出
VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
-Xms------堆最小值
-Xmx------堆最大值
-XX:+HeapDumpOnOutOfMemoryError-----------可以让虚拟机在出现内存溢出时Dump出当前的内存堆转储快照以便事后进行分析。
当java堆出现内存溢出时,异常堆栈信息"java.lang.OutOfMemoryError"后面会跟一句"java heap space"。
解决这个区域异常:
先通过内存映像分析工具(eg:Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,重点确认内存中的对象是否必要,就是先分
清楚时内存溢出还是内存泄漏。
如果内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收
集器无法自动回收它们。
若不是内存泄漏,就检查虚拟机的堆参数(-Xmx 与 -Xms)是否可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态过长的
情况,减少程序运行期的内存消耗。
2)虚拟机栈和本地方法栈溢出
栈异常:
StackOverflowError异常-------在单线情况下,使用-Xss参数减少栈内存容量或者定义大量的本地变量都出现此异常;单线程下,无论是栈帧太大还是虚拟机栈容量太小,当内存无法分配时,都抛出此异常。
VM Args: -Xss128k
public class JavaVMStackSOF
{
private int stackLength=1;
public void stackLeak()
{
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable
{
JavaVMStackSOF oom=new JavaVMStackSOF();
try{
oom.stackLeak();
}catch(Throwable e){
System.out.println("stack length:"+oom.stackLength);
throw e;
}
}
}
OutOfMemoryError异常-------通过不断创建线程的方式可以产生此异常,栈内存越大越快:
VM Args:-Xss2M
public class JavaVMStackOOM
{
private void dontStop()
{
while(true)
{
}
}
public void stackLeakByThread()
{
while(true)
{
Thread thread = new Thread(new Runnable(){
public void run()
{
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) throws Throwable
{
JavaVMStackOOM oom=new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
3)方法区和运行时常量溢出
String.intern()---Native方法--Java 1.6:如果字符串常量池中已经包含一个等于此字符串的对象,则返回常量池中此字符串,Java 1.7 返回此字符串的引用;
运行时常量池溢出,OutOfMemoryError后面跟随提示信息:“PermGen space”(运行时常量池属于方法区的一部分 )
4)本机直接内存溢出
DirectMemory容量可以由:-XX:MaxDrectMemorySize指定,不指定时默认与Java堆最大值(-Xmx)一致;
由于DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看到明显的异常,如果发现OOM后Dump文件很小,而程序中又直接或间接使用类NIO,很可能就是 由于DirectMemory导致的内存溢出。