方法区(Method Area)
在JVM中,类型信息和类静态变量都保存在方法区中,类型信息是由类加载器在类加载的过程中从类文件中提取出来的信息。
需要注意的一点是,常量池也存放于方法区中。
程序中所有的线程共享一个方法区,所以访问方法区的信息必须确保线程是安全的。如果有两个线程同时去加载一个类,那么只能有一个线程被允许去加载这个类,另一个必须等待。
在程序运行时,方法区的大小是可以改变的,程序在运行时可以扩展。
方法区也可以被垃圾回收,但条件非常严苛,必须在该类没有任何引用的情况下,详情可以参考另一篇文章:Java性能优化之JVM GC(垃圾回收机制) - 知乎专栏
Java堆(JVM堆、Heap)
当Java创建一个类的实例对象或者数组时,都在堆中为新的对象分配内存。
虚拟机中只有一个堆,程序中所有的线程都共享它。
堆占用的内存空间是最多的。
堆的存取类型为管道类型,先进先出。
在程序运行中,可以动态的分配堆的内存大小。
堆的内存资源回收是交给JVM GC进行管理的,详情请参考:Java性能优化之JVM GC(垃圾回收机制) - 知乎专栏
Java栈(JVM栈、Stack)
在Java栈中只保存基础数据类型(参考:Java 基本数据类型 - 四类八种 - 知乎专栏)和自定义对象的引用,注意只是对象的引用而不是对象本身哦,对象是保存在堆区中的。
拓展知识:像String、Integer、Byte、Short、Long、Character、Boolean这六个属于包装类型,它们是存放于堆中的。
栈的存取类型为类似于水杯,先进后出。
栈内的数据在超出其作用域后,会被自动释放掉,它不由JVM GC管理。
每一个线程都包含一个栈区,每个栈中的数据都是私有的,其他栈不能访问。
每个线程都会建立一个操作栈,每个栈又包含了若干个栈帧,每个栈帧对应着每个方法的每次调用,每个栈帧包含了三部分:
局部变量区(方法内基本类型变量、变量对象指针)
操作数栈区(存放方法执行过程中产生的中间结果)
运行环境区(动态连接、正确的方法返回相关信息、异常捕捉)
本地方法栈
本地方法栈的功能和JVM栈非常类似,用于存储本地方法的局部变量表,本地方法的操作数栈等信息。
栈的存取类型为类似于水杯,先进后出。
栈内的数据在超出其作用域后,会被自动释放掉,它不由JVM GC管理。
每一个线程都包含一个栈区,每个栈中的数据都是私有的,其他栈不能访问。
本地方法栈是在程序调用或JVM调用本地方法接口(Native)时候启用。
本地方法都不是使用Java语言编写的,比如使用C语言编写的本地方法,本地方法也不由JVM去运行,所以本地方法的运行不受JVM管理。
HotSpot VM将本地方法栈和JVM栈合并了。
程序计数器
在JVM的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了各条线程之间的切换后计数器能恢复到正确的执行位置,所以每条线程都会有一个独立的程序计数器。
程序计数器仅占很小的一块内存空间。
当线程正在执行一个Java方法,程序计数器记录的是正在执行的JVM字节码指令的地址。如果正在执行的是一个Natvie(本地方法),那么这个计数器的值则为空(Underfined)。
程序计数器这个内存区域是唯一一个在JVM规范中没有规定任何OutOfMemoryError(内存不足错误)的区域。
JVM GC(垃圾回收机制)
在学习Java GC 之前,我们需要记住一个单词:stop-the-world 。它会在任何一种GC算法中发生。stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行。当stop-the-world 发生时,除GC所需的线程外,所有的线程都进入等待状态,直到GC任务完成。GC优化很多时候就是减少stop-the-world 的发生。
JVM GC回收哪个区域内的垃圾?
需要注意的是,JVM GC只回收堆区和方法区内的对象。而栈区的数据,在超出作用域后会被JVM自动释放掉,所以其不在JVM GC的管理范围内。
JVM GC怎么判断对象可以被回收了?
· 对象没有引用
· 作用域发生未捕获异常
· 程序在作用域正常执行完毕
· 程序执行了System.exit()
· 程序发生意外终止(被杀线程等)
在Java程序中不能显式的分配和注销缓存,因为这些事情JVM都帮我们做了,那就是GC。
有些时候我们可以将相关的对象设置成null 来试图显示的清除缓存,但是并不是设置为null 就会一定被标记为可回收,有可能会发生逃逸。
将对象设置成null 至少没有什么坏处,但是使用System.gc() 便不可取了,使用System.gc() 时候并不是马上执行GC操作,而是会等待一段时间,甚至不执行,而且System.gc() 如果被执行,会触发Full GC ,这非常影响性能。
JVM GC什么时候执行?
eden区空间不够存放新对象的时候,执行Minro GC。升到老年代的对象大于老年代剩余空间的时候执行Full GC,或者小于的时候被HandlePromotionFailure 参数强制Full GC 。调优主要是减少 Full GC 的触发次数,可以通过 NewRatio 控制新生代转老年代的比例,通过MaxTenuringThreshold 设置对象进入老年代的年龄阀值(后面会介绍到)。