JVM:Java虚拟机,.class代码运行的地方
JVM运行时数据区

1.方法区:
- 方法区时所以线程共享的内存区域,存放已被Java虚拟机加载了的类信息、常量、静态变量等,即编译器编译后的代码。
2.java堆(Java Heap)
- Java堆时JVM管理的内存中最大的一块内存,被所有线程共享的区域,在虚拟机启动时创建,该区域的唯一目的就是存放对象实例。
- 在Java虚拟机规范中描述的是:所有对象实例以及数组都要在堆上分配。
- Java堆也是垃圾收集器管理的主要区域,因此也被成为GC堆
- Java堆在物理上是不连续的内存空间,都是可扩展的,如果堆中没有内存完成实例分配了,并且堆也无法再扩展时,会抛出OutOfMemoryError异常。
- 从内存回收角度来看Java堆可以分为:新生代和老生代。
- 从内存分配的角度上看,线程共享的Java堆中可能划出多个线程私有的分配缓冲区。
- 但是无论怎么划分,什么区域,Java堆都是为了更好地存放实例对象,回收内存,分配内存。
3.程序计数器
我的理解是和CPU的程序计数器类似,保存当前线程正在执行的字节码指令的地址,为了保证程序能够正常,按照编写逻辑地执行。 每个线程都会有一个独立的程序计数器。
4.Java虚拟机栈(JavaVirtual Machine Stacks)
Java虚拟机是线程私有的,他的生命周期和线程相同。
每个方法再执行时都会创建一个栈帧用来储存局部变量表,操作数栈,动态连接,方法出口等信息。
- 局部变量表:用来存储临时的基本数据类型,对象引用类型,returnAddress类型。
- 操作数栈:操作数栈就是用来操作的,例如执行 “a = 1+1″,他会先进行计算,在放入到局部变量表中去。
- 动态连接:用来指向被调用的方法地址。
5.本地方法栈(Native Method Stack)
- 它是虚拟机栈未虚拟机指向Java方法的服务
- 用native修饰的方法就是本地方法
- 存储C/C++语言函数的调用信息
- 一般都是比较底层的功能,
为什么要有本地方法栈的存在:
Java方法的执行是由JVM的解释器或JIT编译器管理,符合JVM规范。而本地方法遵循操作系统和底层硬件的调用约定(如C语言的栈帧接口),它们两者内存结构和执行方式完全不同,所以需要区分开。
在目前主流的HotSpot虚拟机中,Java虚拟机栈和本地方法栈合并为了一个。
直接内存
直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但还是受本机总内存限制的,也就是计算机中的普通内存。
在JDK1.4以后,加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方法,它可以使用native函数库直接分配堆外内存进行操作。
直接内存申请空间性能耗费高,而堆内存申请空间耗费低。
直接内存的IO读写的性能要优于堆内存。
垃圾回收机制(GC)
程序在运行过程中会产生大量的内存垃圾,程序已经不需要用他们了,或者说是已经无法被访问,用不了了的内存。Java虚拟机会在程序运行的过程中不断进行自动的垃圾回收。
程序员无法手动地清理内存,只能依靠GC。但是可以通过”System.gc”方法来建议程序执行垃圾回收。
新生代,老生代,永久代:
新生代又被分为三个区域:Eden,From Survivor,To Survivor
一般默认新生代 = 1/3的堆空间,老生代 = 2/3的堆空间
而新生代中Eden:From Survivor:To Survivor = 8:1:1 一般使用Eden和一块Survivor,总有一块Survivor是空闲的。
永久代是JVM的方法区,里面存放着被虚拟机加载了的类信息,静态变量,常数等,不容易被回收,所以叫做永久
数据首先分配到Eden中(如果是大对象(大量连续内存空间的对象)会直接放入老年代),当Eden没有足够空间时会发起一次Minor GC。如果对象经过一次Minor GC还存活,并且能被Survivor接收,就会移动过去,并且年轮为1。对象在Survivor中每经历一次Minor GC,年龄就+1。如果年龄达到一定程度(默认为15(可自己设置))就会升级到老年代中。
Minor GC,Major GC,Full GC:
Minor GC是新生代GC,由于java大多数对象生命周期很短,所以Minor GC非常频繁,回收速度也很快。当Eden区满时,会触发Minor GC。
Major GC是老年代GC,通常在执行Major GC时也会执行Minor GC,速度比Minor GC慢很多。
Full GC是清理整个堆空间。
Major GC和Full GC触发条件:(通常这两个是等价的)
- 每次升级到老年代的对象平均大小>老年代剩余空间
- Minor GC后存活的对象超过了剩余空间
- 永久代空间不足
- 执行System.gc()
- CMS GC异常
- 堆内存分配很大的对象
如何判断对象是否存活:
当一个对象没有被任何引用指向的时候,那么他就是垃圾
引用计数法:在每个对象被创建的时候,绑定一个计数器,每当一个引用指向该对象,计数器++,每当一个指向它的引用被销毁,计数器–。当计数器==0,对象死亡。但是当两个对象互相引用时,计数器失效。
可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径为引用链,当GC Roots没有走到的引用链,就说明该对象死了。
垃圾回收机制策略
标记-清除算法:每个对象存储一个标记为,记录状态。先对灭个对象进行查看,看是否死亡,再对所有对象进行GC清除操作。
标记-整理算法:根据标记-清除算法改进,不过时先将存活的对象整理一下,放到另外一个空间,然后把当前整个空间的所有对象都清除。解决了标记-清除算法出现的内存碎片化的问题。缺点是由于移动了可用对象,需要更新引用。一般应用于老年代,因为老年代生命周期比较长。
复制算法:将内存平均分为两部分,每次只是用一部分,当这部分内存满的时候,就把存活的对象复制到另外一部分内存中,再将当前内存清空,继续使用这部分。一般使用在新生代中,因为新生代生命周期短,进行拷贝的效率高。
