1.JDK1.5版本改动非常大,加入了自动装箱、泛型、动态注解、枚举、可变长参数以及遍历循环等。
JDK1.6提供动态语言支持,提供API编译,且JVM中改进了锁与同步、垃圾收集以及类加载等的算法。
JDK1.7提供新的G1收集器、加强对非Java语言的调用支持(目前未完全定型)、升级类加载架构等。
JDK1.8增加Lambda表达式、Jigsaw(未实现)。Lambda用来进行函数式编程。
不采用Lambda的老方法:
Runnable runnable1=new Runnable(){
@Override
public void run(){
System.out.println("RunningwithoutLambda");
}
};
使用Lambda:
Runnable runnable2=()->{
System.out.println("RunningfromLambda");
};
2.目前Java程序在64位虚拟机上运行需要付出较大的额外代价:一是内存问题,由于指针膨胀和各种数据类型对齐补白等原因,64位系统上通常比32位多消耗10%~30%内存。其次测试来看,64位虚拟机的运行速度几乎全面落后于32位大概15%左右的性能差距。但在J2EE方面,企业级应用经常使用超过4GB的内存,因此对于64位虚拟机的需求非常迫切。
上图为JVM运行时数据区,其中方法区和堆由所有线程共享的数据区,其余的为线程隔离的数据区。
1.程序计数器(Program Counter Register),一块较小的内存空间,可看作是当前线程所执行的字节码的行号指示器。字节码解释器的工作就是改变这个计数器的值来选择下一条需要执行的字节码指令,分支、循环、跳转、异常、线程恢复等基础功能都需要依赖这个计数器来完成。
在任一确定时刻,一个处理器(或多核的一个内核)都只会执行一条线程中的指令。同时为了线程切换后能恢复到正确的执行位置,每条线程都需要一个相互之间并不影响的独立的程序计数器,独立存储,这类内存区域为“线程私有”的内存。
若线程是正在执行Java方法,则计数器记录的是正在执行的JVM字节码指令的地址;若正在执行的是native方法,则计数器值为空。
此内存区域是唯一一个在JVM规范中没有任何规定OutOfMemoryError情况的区域。
2.Java虚拟机栈(JVM Stack):线程私有的,生命周期与线程相同。JVM Stack描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法的调用到执行完的过程即为入栈到出栈的过程。局部变量表存放的是各种基本数据类型、对象的引用和returnAddress类型(指向了一条字节码指令的地址)。其中long与double占两个局部变量空间。其余的只占一个。局部变量表的空间在编译时就完成了分配,进入方法时在帧中的空间分配是确定的,在方法运行期间不会改变局部变量表的大小。若线程请求栈深度大于JVM允许的深度,则抛出StackOverflowError异常,若JVM Stack可扩展,却无法申请到足够的内存时,抛OutOfMemoryError异常。
3.本地方法栈(Native Method Stack):类似于虚拟机栈。区别是本地方法栈为JVM使用到的native方法服务,而JVM Stack为Java方法,即字节码服务。也会抛出虚拟机栈的异常。
4.Java堆(Java Heap):JVM内存管理最大的一块。线程共享。JVM启动时创建。唯一目的就是存放对象实例以及数组。Java堆是垃圾回收管理的主要区域。也被称为GC堆(Garbage Collected Heap)。由于现在收集器基本上都采用分代回收算法,因此Java堆又可以分为年轻代和年老代以及持久代。
年轻代:是所有新对象产生的地方。年轻代被分为3个部分——Eden区和两个Survivor区(From和to)当Eden区被对象填满时,就会执行Minor GC。并把所有存活下来的对象转移到其中一个survivor区(假设为from区)。Minor GC同样会检查存活下来的对象,并把它们转移到另一个survivor区(假设为to区)。这样在一段时间内,总会有一个空的survivor区。经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的。需要注意,Survivor的两个区是对称的,没先后关系,from和to是相对的。
年老代:在年轻代中经历了N次回收后仍然没有被清除的对象,就会被放到年老代中,可以说他们都是久经沙场而不亡的一代,都是生命周期较长的对象。对于年老代和永久代,就不能再采用像年轻代中那样搬移腾挪的回收算法,因为那些对于这些回收战场上的老兵来说是小儿科。通常会在老年代内存被占满时将会触发Full GC,回收整个堆内存。
持久代:用于存放静态文件,比如java类、方法等。持久代对垃圾回收没有显著的影响。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。若堆中没有内存分配空间且无法扩展时,将抛OutOfMemoryError异常。
5.方法区(Method Area):线程共享的。用于存储已被JVM加载的类信息(类名,访问修饰符,字段描述,方法描述等)、常量、静态变量、即时编译器编译后的代码等数据。(非堆)。不需要连续的内存,可选择固定大小或可扩展,还可以选择不实现垃圾收集。这部分区域的内存回收目标主要是针对常量池的回收以及对类型的卸载,但回收效果一般,尤其是类型的卸载条件非常苛刻。方法区内存无法满足需求时,抛出OutOfMemoryError异常。
6.运行时常量池(Runtime Constant Pool):方法区的一部分。常量池,用于存放class文件编译期生成的各种字面量和符号引用。除了符号引用以外,直接引用也会存储在常量池中。常量池另一个特征是具备动态性,运行期新的常量也可以进入放入常量池中,如string类的intern()方法等。
7.直接内存(Direct Memory):不是JVM运行时数据区的一部分,也不是JVM规范中定义的内存区域,但也在被频繁使用,也可能导致OutOfMemoryError异常出现。JDK1.4新产生的new Input/Output引入的基于通道与缓冲区的I/O方式,可以使用native函数库直接分配堆外内存,通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。本地直接内存不会受到Java堆的大小限制,但会受到本机总内存(RAM/SWAP区或分页文件)大小以及处理器寻址空间的限制。-Xmx设置不合理也会导致OutOfMemoryError异常出现。
8.JVM中对象的创建、布局与访问
8.1 对象的创建
①检测对象是否加载完毕
JVM通过new先去检查是否在常量池中有对应的符号引用,且检查这个符号引用所代表的类是否被加载、解析和初始化过。没有的话,则必须执行相应的类加载过程。
②为对象分配堆内存
类加载检测通过之后,JVM为新生对象分配内存。类加载后对象所需内存即确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。
分配方法分为“指针碰撞”和“空闲列表”。假设堆内存绝对规整,用过的放一边,空闲的放一边,中间为指针进行分隔。指针碰撞即指针向空闲的一方移动大小为对象的内存空间。假设堆内存不完全规整,使用过的和空闲的交错,JVM必须维护一个列表记录哪些是空闲的,在分配时找到足够大的空间分配给对象实例,并更新列表的记录。这种分配方式是“空闲列表”。
Java堆是否规整由垃圾收集器是否带有压缩整理功能决定。
例如:Serial/ParNew等带compact过程的收集器是指针碰撞,而CMS这种基于Mark-Sweep算法的收集器采用空闲列表。
除如何划分可用空间之外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常 频繁的行为,即使是仅仅修改一个指针
所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原
来的指针来分配内存的情况。解决方法:一、堆分配内存空间的动作进行同步处理;二是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存(本地线程分配缓冲Thread Local Allocation Buffer,TLAB,解决线程不安全的问题)。哪个线程要分配,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。
③分配到的内存空间初始化为零值(不包括对象头)
保证了对的实例字段在Java代码中可以不赋初始值就可以直接使用,程序能访问这些字段的数据类型所对应的零值。
④对对象进行必要的设置(即设置对象头Object Header)
例如对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。存放在对象头当中。
通过以上四步,从JVM角度来看,一个新的对象就产生了,但从Java程序来说,对象创建才刚刚开始——
因此,执行完new之后,会接着执行
8.2对象的布局
对象在内存中的布局分为3个部分:对象头Header、实例数据Instance Data和对齐填充Padding
对象头包含两部分信息:用于存储对象自身的运行时数据以及类型指针。
注意:对于数组而言,对象头中还会记录数组的长度。JVM可以通过对象的元数据信息确定Java对象的大小。
但从数组对象的元数据中是无法获取数组大小的。
实例数据是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
无论是父类继承下来的还是子类中定义的。
存储顺序(HotSpot)
import java.util.ArrayList;
import java.util.List;
public class Test4 {
/*VM args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* */
static class OOMObject{
}
public static void main(String[] args) {
List list=new ArrayList();
while(true){
list.add(new OOMObject());
}
}
}