深入理解java虚拟机,深入理解虚拟机
深入理解java虚拟机,深入理解虚拟机
java虚拟机
java概述
- java为什么是跨平台的?是如何实现跨平台的?
java的目标是"一次编译,处处运行",就是说,java源码经过编译后可以在无论是windows还是linux下都能运行.
- java实现跨平台的原理是什么?
java实现跨平台的原理和C不同,java是使用编译器编译之后生成字节码文件(*.class),然后各种不同平台的虚拟机与所有平台都统一使用这种字节码,然后虚拟机将描述类的数据从class文件中加载到内存中,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的java类型
- c语言编译运行过程是
gcc -o a a.c 会生成一个可执行二进制文件a,不同平台下使用不同的编译器生成不同的可执行文件,这些文件不是跨平台的,所以c程序要想在不同平台下执行,都是要重新编译的然后生成该平台下的可执行文件才行的.
java程序是如何执行的?
- java编译器编译产生class文件
- class加载到jvm进行执行
jvm分类
jvm内存分为:
操作数栈,局部变量表,java堆,常量池,方法区
java内存区域与内存溢出异常
运行时数据区域
包括:
- 程序计数器
当前线程所执行字节码的行号指示器,字节码解释器工作时就是通过改变程序计数器的值来选取下一条需要执行的字节码指令
由于java虚拟机的多线程实现是通过线程轮流切换,并分配处理器执行时间的方式来实现,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令.
每条线程都有一个独立的程序计数器,各个线程的程序计数器互不影响,独立存储,被称为线程私有的内存.
如果线程执行java方法,这个程序计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是nativate方法,则计数器值为空.
此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域.
- java虚拟机栈
线程私有的
生命周期和线程相同
虚拟机栈描述的是java方法执行的内存模型;每个方法执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等
这个也是我们常说的java栈内存
局部变量表存放的是编译器可知的各种基本数据类型,对象引用类型,returnAddress类型
64为long和double类的数据都会占用2个局部变量空间,其他的数据类型只占用1个局部变量空间
局部变量表所需的内存是在编译期间完成分配的.当进入一个方法时,这个方法需要在帧中分配多大的内存空间是确定的,这个方法在运行期间不会改变局部变量表的大小.
异常种类:
StackOverflowError:线程请求的栈深度大于虚拟机允许的栈深度
OutOfMemoryError: 扩展时无法申请足够的内存
- 本地方法栈
与java虚拟机栈相似,虚拟机栈执行java方法服务,本地方法栈执行Native方法服务
异常种类和虚拟机栈相同
- java堆
java虚拟机所管理的内存最大的一块,java堆是被所有线程所共享的的一块内存区域,在虚拟机启动的时候创建.此内存区域的唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存,所有的对象实例和数组都要在堆上分配内存.
java堆也是java垃圾收集器管理的主要区域,很多时候也被成为GC堆. 从内存回收的角度来看,线程共享的java堆可能划分出多个线程私有的缓冲区.
java堆可以存放在物理上不连续的内存空间中
可以通过-Xmx和-Xms控制,如果堆中没有内存完成实例分配,并且堆无法扩展,将会抛出OutOfMemoryError异常
- 方法区
是线程共享的内存区,用户存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据
当方法区无法满足内存分配需求时,会抛出OutOfMemoryError
- 运行时常量池
运行时常量池是方法区的一部分,class文件中除了有类的版本,字段,方法接口等信息外,还有一项信息是常量池,用于存放编译期生成的字面量和符号引用,这部分将在类加载后进入方法区的运行时常量池中存放
常量池无法申请到内存时也会抛出OutOfMemoryError.
- 直接内存
它可以使用Native函数直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这一块对象的引用操作
Hotspot虚拟机对象
虚拟机遇到一条new指令时,首先检查这个指令的参数是否能够在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否被加载过,解析和初始化过.如果没有必须先执行相应的加载过程.在类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象所需的内存大小在加载完成之后便确定了.
垃圾收集器与内存分配策略
哪些内存需要回收?
首先,使用引用计数算法,对每一个对象被引用次数进行统计的话,如果对象之间循环引用的话而这两个对象又没有被用过,这种情况下,该算法的引用次数不为0,就无法通过GC收集器回收它们.
可达性分析算法
通过一系列的GC Roots的对象作为起点,从这些节点向下搜索,搜索所走过的的路径被称为引用链,当一个GC Roots没有任何引用链相连时,则证明此对象是不可用的.
- 作为GC Roots的对象包括:
虚拟机栈引用的对象,
方法区中静态属性引用的对象,
方法区中常量引用的对象,
本地方法栈中JNI(Native方法)引用的对象
- 强引用
永远都不会被回收
- 软引用
还有用,但并非是必需的对象
- 弱引用
非必需对象,强度比软引用更弱
- 虚引用
它是最弱的引用关系,唯一目的是能在这个回收器回收时收到一个系统通知.
什么时候回收?
如何回收?
标记-清除算法
- 标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象.
问题:
标记和清除效率不高,标记清除后会产生大量不连续的内存碎片
复制算法
- 将内存划分为大小相同的两块,当这一块用完了,将存活的拷贝到另外一块上,清理掉已经使用过的内存块,重新紧密排列,不会产生碎片.
- 问题:
复制收集算法在对象存活率过高时,就要进行较多的复制操作,效率就会变低.
标记整理算法
- 过程和标记清除算法相同,但后续步骤不同,不是直接对可回收对象进行清理,而是将所有存活对象向一端移动,然后直接清除掉端边界以外的内存.
分代收集算法
- 根据对象存活周期的不同将内存划分为几块,一般将java堆划分为新生代和老年代,这样可以在新生代使用复制算法,会stop the world,因为新生代每次回收的时候都会有大量的对象死去.而老年代因为经过多轮淘汰,对象的存活率比较高,没有额外的空间对它进行分配担保,就必须使用标记清除算法,或者标记整理算法进行回收,有可能会stop the world.
- Hotspot虚拟机的分代回收,分为一个Eden区,两个Survivor区以及Old Generation,Eden以及Survivor共同组成New Generatiton.
- 新生代回收Minor GC
- 老年代回收称为Major GC,除并发GC外均需堆整个堆以及Permanent Generation进行扫描和回收,因此又被称为Full GC
HotSpot的算法实现
枚举根节点算法
相关文章
- 暂无相关文章
用户点评