本篇是《深入理解Java虚拟机-Java 高级特性与最佳实践》学习笔记,周志明著,Understanding the JVM-Advanced Features and Best Practices,机械工业出版社,2011.6出版。
重温Java JVM知识,重点学习了与日常开发工作相关性最大的“自动化内存管理”模块,对Java容器优化、内存问题解决很有帮助;习惯了从互联网看电子书,难以集中和记忆,现在找几本纸质书重温,可以很清静、很安静的理解和消化,受益匪浅。
自动内存管理机制
Java和C++之间有一睹由动态内存分配和垃圾收集机制组成的墙,里面的人想出来,外面的人想进去。
在Java里面调用System.gc,会立刻引起一次FullGC和YougGC。
1、Java内存区域与内存溢出异常
虚拟机运行时数据区 | 存储内容 | 可能发生的溢出错误及优化措施 | 描述 |
堆(Heap) | 对象实例 | OutOfMemoryError: Java heap space,通过调节参数-Xms2048m -Xmx2048m,设置堆的初始内存和最大内存进行优化 | 在虚拟机启动时创建,是VM所管理的内存中最大的一块,是垃圾收集器管理的主要区域,也被称作“GC堆”,堆是所有线程共享的 |
虚拟机栈(VM Stack) | Java方法执行的内存模型:每个方法被执行时,都会同时创建一个栈帧(Stack Frame)用于存储局部变量表(基本数据类型和对象引用类型),操作数栈,动态链接,方法出口等信息 | 单线程可能会报StackOverflowError,多线程可能会报OutOfMemoryError,对应参数是-Xss2m,一般不设这个参数,而是采用默认值 | 生命周期和线程相同,每个方法被调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程,有时会通过减小栈和堆大小的方式优化,以保证可以开更多线程,栈是线程私有的 |
本地方法栈(Native Method Stack) | 本地方法栈和虚拟机栈所发挥的作用非常相似,其区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈为虚拟机使用到的Native方法服务。 在Sun HotSpot虚拟机中,本地方法栈和虚拟机栈是在一起的。 |
||
方法区(Method Area) | 类信息、常量、静态变量、即时编译器编译后的代码 | OutOfMemoryError: PermGen space,通过调节参数-XX:PermSize=1024m -XX:MaxPermSize=1024m,设置方法区的初始内存和最大内存进行优化 | 有个别名叫Non-Heap,对于HotSpot来说,也被称作“永久代”(Permanent Generation),运行时常量池(Runtime Constant Pool)是方法区的一部分,方法区是所有线程共享的 |
程序计数器(Program Counter Register) | 当前线程所执行的字节码的行号计数器 | 不会溢出 | 是线程私有的 |
直接内存(Direct Memory) | 直接内存不是虚拟机运行时数据区的一部分,是使用Native函数库直接分配的堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用,目的是在一些场景中显著提高性能,因为避免了再Java对和Native堆中来回复制数据;可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样;会报溢出错误:OutOfMemoryError | ||
三大商业虚拟机 | Sun HotSpot,Bea Jrockit,IBM J9,前两个虚拟机都已经被Oracle收购,Sun HotSpot是使用最普遍的虚拟机。 |
JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K。
应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能。
如果我们需要知道,JVM虚拟机的默认参数配置,可以使用命令查看:
java -XX:+PrintFlagsInitial | grep Ratio
我们可以看到,新生代和老年代的默认比值是2;Survior和Eden的默认比值是8.
补充一个案例,如下:
如果写了一个普通的死循环,没有发现,则会占用cpu时间,但是可能不会导致内存溢出的情况,因为可能对于堆和栈空间都没有太大影响;
但是如果写了个递归死循环,循环递归,则马上会报StackOverFlow,因为随着递归函数的调用,大量的本地变量被声明,很快,栈空间就满了;
另,对于不同的JDK,可能会变化,比如String的常量String.intern(),在JDK6中存放在“方法区”的“运行时常量池”中,但是在JDK7中,就存放在堆中了,所以,很多细节也是在一直变化中的。。。
参考网址:
http://blog.csdn.net/kthq/article/details/8618052
http://www.open-open.com/lib/view/open1324736648468.html
2、垃圾收集器与内存分配策略
对象已死的确定方法:
使用案例 | 描述 | |
引用计数算法 | COM、AS3、Python、Squirrel | 很难解决对象之间的相互循环引用问题 |
根搜索算法 | Java、C#、Lisp | |
在Java语言里,可以作为GC Roots的对象包括:(虚拟机栈中的/方法区中的类静态属性和常量/本地方法栈中JNI)引用的对象 Java对象的finalize方法,优先级低,运行代价高昂,不确定性大,无法保证各个对象的调用顺序,建议大家完全可以忘记Java中还有这个方法的存在;这个方法最多只会被垃圾收集器调用一次。 |
垃圾收集算法: