jvm初步理解
1、什么是运行时数据区?
javac 指令:编译java文件生成class文件
java指令:运行class文件即将数据放到jvm中
class文件运行,后将不同的数据放到jvm中不同的位置这就是运行时数据区的由来
2、运行时数据区的划分
-
PC寄存器(计数器)(存储当前线程执行方法的记录,下次接着下去,不用从头开始。) 每一个线程都有一个计数器,专门用于存储当前线程正在执行方法的所在位置。
-
java虚拟机栈
一个线程表示一个java虚拟机栈,方法的执行可以通过压栈的方式。
如下线程t1调用a()–>a()调用b()–>b()调用c() :出栈的过程返回地址、局部变量、参数。
-
堆
存储对象(包括普通成员变量),数组 如:new Person() class class== -
方法区
类的信息(创建时间,元数据信息)、常量、静态变量、即时编译器编译后的代码。
堆和方法区都是线程共享,由于其他线程共享也就是说是线程不安全。 -
本地方法区(本地方法栈)
其他语言写的方法类库 -
运行时常量池
数据结构定义:Runtime constant pool代表运行时每个class文件中的常量表,包含:编译期的数字常量、方法或者域的引用(在运行时解析)
运行时常量池是方法区的一部分,所有它的存储受方法区的规范约束
3.垃圾回收
-
怎么样的对象才算垃圾
1 引用法:通过方法是否被引用,存在a引用b,b引用a那么这两者互相引用永远不会成为垃圾。
2 可达性分析:通过一系列称为‘gc roots’的对象作为起始点,从这些节点开始向下搜索,搜索过的路径称为‘引用路径’当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。以下图为例:
-
垃圾回收算法
GC算法:
1 标记-清除算法:找出内存需要回收的对象,并且把它们标记出来。
问题:此时堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时。内存碎片化。如果下次有比较大的对象实例需要在堆上分配较大的内存空间时,可能会出现无法找到足够的连续内存而不得不再次触发垃圾回收。2 复制算法(Java堆中新生代的垃圾回收算法):首先还是先标记处待回收内存和不用回收的内存,下一步将不用回收的内存复制到新的内存区域,这样旧的内存区域就可以全部回收,而新的内存区域则是连续的。
问题:它的缺点就是会损失掉部分系统内存,因为你总要腾出一部分内存用于复制。3 标记-整理算法(Java堆中老年代的垃圾回收算法):
对于新生代,大部分对象都不会存活,所以在新生代中使用复制算法较为高效,而对于老年代来讲,大部分对象可能会继续存活下去,如果此时还是利用复制算法,效率则会降低。标记-压缩算法首先还是“标记”,标记过后,将不用回收的内存对象压缩到内存一端,此时即可直接清除边界处的内存,这样就能避免复制算法带来的效率问题,同时也能避免内存碎片化的问题。老年代的垃圾回收称为“Major GC”。
4.所谓的JVM调优
堆结构:
堆,在jdk1.8之后分为新生代、老生代、元数据空间三部分。在1.8之前分为新生代、老生代、永久代。元数据空间与永久代最大的不同点就在于,永久代是存在于java虚拟机的堆内存,而元数据空间在jdk1.8中与堆内存完全隔离,存在于物理内存之中。另外需要补充一点,方法区在java代码中其实是一个抽象的概念,最终保存信息靠的就是元数据空间。
堆空间的大小其中新生代约占1/3,老生代约占2/3,在新建一个对象的时候,对象会放在新生代的Eden区中,当Eden区填满的时候,会做一次小的gc,幸存者会进入Survivor Space区域中,每执行一次gc,每个幸存的对象在From与To之间更换位置,当幸存的对象躲避gc次数,达到某个限定值之后就会转移到老生代,老生代的内存区域比新生代大,当老生代的内存满了之后,会再次触发full GC,而当full GC被触发的时候,所有的线程都会被挂起等待垃圾清理,这时候问题就来了,影响程序的运行,所以JVM调优的重点就应该放在full GC,应该避免尽量少执行gc,发生full GC的周期足够的长,尽可能将对象预留在新生代,减少老年代的GC次数。