JVM
JDK=JRE+Java编译器、开发工具和更多的类库
JRE=Java虚拟机+Java基础类库
简单来说JDK支持Java程序的开发。
JRE是Java Runtime Environment的简称


这幅图中我们只需要关注运行时数据区中的5个部分即可。
-
程序计数器
当前线程所执行的字节码的行号指示器。
.java文件编译成.class文件,最终交给jvm执行,.class文件也是要按逻辑找到行号执行的,程序计数器就是对当前线程应该执行哪一行字节码做指示的。假设线程A暂停,过一段时间后线程A继续执行,这时候线程A应该从哪个地方继续就是靠程序计数器来完成的。
每个线程都有一个程序计数器,是线程
的,就是一个指针,指向方法区中的方法字节码(
),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
这块内存区域很小,
,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。
如果执行的是一个Native方法,那这个计数器是空的。
-
本地方法栈
本地方法栈和虚拟机栈发挥的作用相似,本地方法栈为本地方法服务。
-
虚拟机栈
虚拟机栈描述的是java方法执行的内存模型。
FIFO 先进先出
栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,所以,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。
+
+
都是在函数的栈内存中分配。
一个线程一个栈,一个方法一个栈帧。栈帧可以理解成栈中的一小块,一个栈中有多个栈帧。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
栈帧中主要保存3 类数据:
- 本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
- 栈操作(Operand Stack):记录出栈、入栈的操作;
- 栈帧数据(Frame Data):包括类文件、方法等等。
每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM的实现有关,通常在256K~756K之间。
栈
-
方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,
。
+
+
+
存在方法区中
注意:实例变量存在堆内存中,和方法区无关
-
堆
Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。堆被划分成三个不同的区域:新生代 ( Young )、老年代 ( Old )和永久区。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。Java堆也是垃圾回收的主战场,也被称为GC堆。
1. java堆被划分成不同的区域:新生代 ( Young )、老年代 ( Old )
2. 新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
3. 新生代占整个堆的1/3、Eden占新生代的1/8、from和to的大小一致各占新生代1/10、老年代占2/3
4.from区和to去的大小空间是一样的,为什么呢?因为YGC 采用复制算法

搞不清楚堆内存之
类加载机制
双亲委派模型
内存模型
GC
年轻代回收步骤
1.在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。
2.紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,
3.而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。
年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。
4.经过这次GC后,Eden区和From区已经被清空。
这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。
不管怎样,都会保证名为To的Survivor区域是空的。
5.Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
经过半个多世纪的发展,目前内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了“自动化”世代,那为什么我们还要去了解GC和内存分配呢?答案很简单:当需要排查各种内存溢出、内存写类问题实,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和条件。
标记清除
标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;然后,在清除阶段,清除所有未被标记的对象。
1.它的缺点就是效率比较低(递归与全堆对象遍历),导致stop the world的时间比较长
2.这种方式清理出来的空闲内存是不连续的
标记压缩/标记整理
标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记;但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端;之后,清理边界外所有的空间。
1.如果在对象存活率较高时就要进行较多的复制操作,效率将会变低。
2.更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选中这种算法。
复制算法
与标记-清除算法相比,复制算法是一种相对高效的回收方法
不适用于存活对象较多的场合,如老年代(复制算法适合做新生代的GC)
将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
1.复制算法的最大的问题是:空间的浪费
Tables | Are |
---|---|
-Xms | 初始堆大小。如:-Xms256m |
-Xmx | 最大堆大小。如:-Xmx512m |
-Xmn | 新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90% |
-Xss | JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。 |
-XX:NewRatio | 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3 |
-XX:SurvivorRatio | 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10 |
-XX:PermSize | 永久代(方法区)的初始大小 |
-XX:MaxPermSize | 永久代(方法区)的最大值 |
-XX:+PrintGCDetails | 打印GC日志 |
XX:+HeapDumpOnOutOfMemoryError | 让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用 |
网友评论