读《深入理解java虚拟机》笔记

最近花了两天时间看了下《深入理解java虚拟机》。现对觉得重点的内容做一个小结。

一、运行时数据区域:方法区、堆、程序计数器、虚拟区栈、本地方法栈。如图(图片来源于网络):

读《深入理解java虚拟机》笔记

    1.程序计数器(Program Counter Register)

    程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的信号指示器。字节码解释器就是通过改变该计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需依赖计数器来完成。

    每一个JVM线程都有独立的程序计数器,各线程间的计数器互不影响,独立存储,确保线程切换后能够恢复到正确的执行位置。

    在任意时刻,一条JVM线程只会执行一个方法的代码。该方法称为该线程的当前方法(Current Method),如果该方法是Java方法,那计数器保存JVM正在执行的字节码指令的地址;如果该方法是Native,那PC寄存器的值为空(Undefined)。

此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

    以上是书籍里对程序计数器的解释,我对程序计数器的理解:每一个jvm线程都有一个程序计数器,他的作用就是记录当前线程执行状态。



    2.Java虚拟机栈(Java Virtual Machine Stack)

    Java虚拟机栈与程序计数器一样,也是线程私有的,其生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

    局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址)。其中64位长度的long和double会占用2个局部变量空间(Slot),其余数据类型只占用1个。局部变量表所需的空间在编译期间完成分配,当进入一个方法时,其需要在帧中分配多大的局部变量空间是确定的,方法运行期间不会改变局部变量表的大小。

    Java虚拟机规范中对该区域规定了两种异常情况:

    1)如线程请求的深度大于虚拟机所允许的深度,抛出*Error异常。

    2)虚拟机栈动态扩展无法申请到足够的内存时,抛出OutOfMemoryError异常。

    以上是书籍里对Java虚拟机栈的解释,我对Java虚拟机栈的理解:用于记录和管理当前线程的java方法。



    3. 本地方法栈(Native Method Stack)

    Java虚拟机可能会使用到传统的栈来支持native方法(使用Java语言以外的其它语言编写的方法)的执行,这个栈就是本地方法栈(Native Method Stack)。本地方法栈与虚拟机栈非常类似,区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,二本地方法栈则为虚拟机使用到的Native方法服务。虚拟机规范对本地方法栈中的方法是用语言、使用方式与数据结构没强制规定,因此虚拟机可以*实现,如Sun HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一。       Java虚拟机规范中对该区域规定了两种异常情况:

    1)如线程请求的深度大于虚拟机所允许的深度,抛出*Error异常。

    2)虚拟机栈动态扩展无法申请到足够的内存时,抛出OutOfMemoryError异常。

   以上是书籍里对本地方法栈的解释,我对本地方法栈的理解:用于记录和管理当前线程的native方法。



     4 Java堆(Java Heap)

    Java堆是Java虚拟机管理内存中最大的一块,是所有线程共享的内存区域,随虚拟机的启动而创建。该区域唯一目的是存放对象实例,几乎所有对象的实例都在堆里面分配。Java堆是垃圾收集器管理的主要区域,被称作“GC堆”。

    Java虚拟机规范规定,Java堆可以出于物理上物理上不连续的内存空间中,只要逻辑上连续即可,如同磁盘空间一样,既可以实现成固定大小,也可以是扩展的,当前主流虚拟机都是按照扩展来实现的(通过-Xmx和-Xms控制)。

    Java虚拟机规范中对该区域规定了OutOfMemoryError异常:如果堆中没有内存完成实例分配,并且堆无法再扩展则抛出OutOfMemoryError异常。

以上是书籍里对java堆的解释,我对java堆的理解:用于记录和管理所有对象实例。



     5 方法区(Method Area)

    方法区与Java堆一样,是各个线程共享的内存区域,用于存储一杯虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Java虚拟机对这个区域的限制非常宽松,处理和Java对一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。

    Java虚拟机规范中对该区域规定了OutOfMemoryError异常: 如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常。



      6 运行时常量池(Runtime Constant Pool)

    运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等信息外,还有一项信息是常量池,用于存放编译期生成的各种字面常量和符号引用,这部分内容在类加载后存放到方法区的常量池中。

    Java虚拟机规范中对该区域规定了OutOfMemoryError异常: 当常量池无法申请到内存时抛出OutOfMemoryError异常。



       7 直接内存

    直接内存并不是虚拟机运行时数据区域的一部分,也非Java虚拟规范中定义的内存区域,但这部分内存也被频繁使用,并且可能导致OutOfMemoryError异常出现。Java虚拟机需要根据实际内存的大小来设置-Xmx等参数信息,如果忽略了直接内存,使得各个内存区域的总和大于物理内存限制,从而导致动态扩展时抛出OutOfMemoryError异常。


以上概念转载于这片文章:http://blog.csdn.net/nms312/article/details/37361121(我发现自己写东西好难,头大)


二.垃圾收集


1.如何判断对象是否已死?该做垃圾回收处理:

方法一:引用计数算法:给对象添加一个引用计数器,当对象被引用时,计数器值加1,引用失效时,计数器减1。任何时刻计数器为0的对象就是没有被引用的。可以做垃圾回收处理。

方法二:可达性分析算法:把GC ROOTS的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC ROOTS没有任何引用链相连时,则证明对象是不可用的。可被判定为可回收对象。

在java语言中,可作为GC ROOTS的对象包括一下几种:

虚拟机栈中引用的对象;

方法区中静态属性、常量引用的对象;

本地方法栈中JNI(Native方法)引用的对象;

一般的虚拟机使用第二种算法。

2.java的四种引用方式:

强引用、软引用、弱引用、虚引用。(具体定义及用法可以去搜索,网上有很多文章有详细的介绍)

Java中提供这四种引用类型主要有两个目的:

第一是可以让程序员通过代码的方式决定某些对象的生命周期;

第二是有利于JVM进行垃圾回收。

3.垃圾收集算法:

标记-清理算法、复制算法、标记-整理算法、分代收集算法。

标记-清理算法:将需要清理的引用做标记,然后执行清理操作。这种算法最简单直接,但是清理之后对现有内存空间没有做重新整理,导致可用空间可能不是连续的。这样就造成很多的内存碎片。之后使用内存时会非常的费劲。

复制算法:复制算法解决上边算法的问题,首先将内存分为两块,一块用完了做垃圾清理时,将清理后剩余的可用数据复制到另一块内存中,然后将当前内存做清理。这样就解决了内存碎片问题。这种算法的缺点是内存可用空间减小。并且需要做较多的复制操作。

标记-整理算法:沿用了标记-清理算法的操作,只是在清理完成后,会将剩余的可用空间按顺序归整到一侧,这样就解决了内存碎片的问题。缺点就是归整的过程也是耗时操作。

分代收集算法:就是讲内存分为几个区域,一般是把java堆分成两个区域:新生代和 老年代。

新生代:每次垃圾收集时,都会有大批对象死去,只有少量存货,适合复制算法。

老年代:对象存活率比较高,没有额外空间进行分配,适合用标记-清理或标记-整理算法。