1.JVM体系结构
2.类加载过程
类加载一般分为7个阶段:加载、验证、准备、解析、初始化、使用和卸载,一般关注前5个阶段。其中验证、准备、解析又统称为连接阶段
- 加载:在这个阶段,虚拟机一般完成以下三个事情:
- 1:通过一个类的全限定名来获取定义此类的二进制字节流(这个动作是类加载器完成的)
- 2:将这个字节流所代表的静态存储结构转换成方法区的运行时数据结构
- 3:在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口
- 验证:验证数据信息是否符合JVM规范,是否是一个有效的字节码文件
- 准备:给类静态变量分配内存空间,并初始化(与程序无关,系统初始化)
- 解析:将常量池中所有的符号引用改为直接引用
- 初始化:负责将所有static域按照程序指定操作对应执行(给static变量赋值,执行static代码块中内容)
上述阶段执行没有严格先后执行顺序,通常是交叉执行
3.类加载器及关系
1.启动类加载器(Bootstarp ClassLoader)
2.扩展类加载器(Extension ClassLoader)
3.应用程序类加载器(Application ClassLoader)
4.自定义类加载器
4.双亲委派模型
什么是双亲委派模型:
当类加载器收到加载类请求时首先将加载请求委托给父类加载器加载,然后依次递归到启动类加载器来加载,
如果父类加载器可以完成加载任务,则成功返回,如果父类无法完成加载任务时才交由自己类加载器加载.
为什么交给父类加载器加载:
自定义java.lang.String类加载流程:
1.先将加载请求交给应用程序类加载器
2.应用程序类加载器将加载请求委托给父类扩展类加载器
3.扩展类加载器将加载请求委托给父类启动类加载器
4.启动类加载器发现java.lang.String类在rt.jar中已经加载过,此时编译器会报错,因为jvm不能识别两个相同的String类需要用到哪个,所以自定义String类加载不成功
交给父类加载器的主要作用是为了防止通过这种代码恶意修改jvm环境
5.本地方法栈,本地方法接口,本地方法库
本地方法栈用来存放java中带有native关键字的方法,java中带有native关键字的方法会调用本地方法接口,而本地方法接口可能需要本地方法库的支持
6.程序计数器
程序计数器也叫PC寄存器,每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,即将要执行的指令代码),由执行引擎读取下一条指令,是一块非常小的内存空间.几乎可忽略不计,所以不会有GC回收
7.方法区
方法区被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造方法,接口代码也都在此定义.简单来说.所有定义的方法的信息都保存在该区域,此区域属于共享区间
静态变量+常量+类信息(构造方法+接口定义)+运行时常量池存在方法区
实例变量存在堆内存中,和方法区无关
8.Java栈
Java栈是什么:
栈也叫栈内存,主管java程序运行,是在线程创建时创建,他的生命周期是根据线程生命周期,
线程结束栈的内存就释放,对于栈来说不存在垃圾回收问题,只有线程一结束该栈就over,
生命周期和线程一致,是线程私有的。8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配的
栈存储什么:
1.本地变量:输入参数,输出参数,方法内的变量
2.栈操作:记录出栈,入栈的操作
3.栈桢数据:包括类文件,方法等
9.堆
什么是堆:
jdk1.8以前从逻辑上分为:新生代,老年代,持久代
jdk1.8及以后从逻辑上分为:新生代,老年代,元空间
新生代分为伊甸园(Eden Space)、幸存者0区(Survivor 0 Space)、幸存者1区(Survivor 1 Space)
9.1新生代
新生代是类的诞生,成长,消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。
新生代又分为两个区域:伊甸园和幸存者区域。所有的类都是在伊甸园区被new出来的,幸存区有两个,分别为0区和1区。
9.2新生代各个区域的联系:
当伊甸园中空间用完后还要继续创建对象时,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园中不再被其它对象所引用的对象进行销毁。然后将伊甸园中剩余的对象转移到幸存0区。若幸存0区也满了则对其进行垃圾回收,然后将剩余对象转移到幸存1区,如果1区也满了则会对1区也进行垃圾回收,如果经过15次垃圾回收后仍然存活的对象将会被转移到老年代。如果老年代也满了则对老年代进行垃圾回收(Major/Full GC),清理老年代的内存。若老年代经过Full GC后仍空间不足则会抛出java.lang.OutOfMemberError: Java Heap Space。
java.lang.OutOfMemberError: Java Heap Space异常原因:
- 1.堆内存不够,可以使用-Xms和-Xmx来调整
- 2.代码中引用了大量大对象,并且长期不能被回收(存在引用)
9.3永久区
永久存储区是一个常驻内存区域,用于存放jdk自身存放的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被转载到此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。
java.lang.OutOfMemberError: PermGen Space 永久代内存不够用:
- 1.程序中需要引入特别多的jar包
- 2.Tomcat下部署特别多的应用
- 3.大量动态反射的类不断的被加载
Jdk1.6及以前有永久代,常量池在方法区
Jdk1.7已经逐渐去永久代,常量池在堆中
Jdk1.8及之后没有永久代,常量池在元空间
10.方法区和永久代的关系:
对于HotSpot虚拟机来说,很多开发者习惯将方法区称为永久代,但严格本质上来说两者并不相同,或者说使用永久代来实现方法区而已,永久代是方法区的一个实现(类似于接口的某个实现类,方法区就是接口,永久代是实现类)jdk1.7版本中已经将原来存在方法区中的字符串常量池移动到堆中。
11.堆和方法区的关系:
实际而言,方法区和堆一样,是各个线程共享的内存区域,用于储存虚拟机加载的:类信息、普通常量、静态常量、编译期编译后的代码等,虽然JVM将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫NON-Heap(非堆),目的就是要和堆分开。由抛出异常的描述信息也可以看出永久代和堆并不是同一块区域。PermGen Space和Java Heap Space
12.方法区和常量池的关系:
常量池是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息外还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放
13.堆调优
调优参数与堆关系:
- -Xmn:新生代初始大小
- -Xms:堆初始化内存,默认为物理内存的1/64
- -Xmx:堆最大内存,默认为物理内存的1/4
- -XX:+PrintGCDetails:输出详细的GC处理日志
- -XX:+HeapDumpOnOutOfMemoryError:当出现OOM异常时将jvm信息写入文件
- -XX:MaxTenuringThreshold:设置对象在新生代存活的次数
获取虚拟机使用的最大内存:
long maxMemory = Runtime.getRuntime().maxMemorySize();
获取jvm使用的内存
long totalMemory = Runtime.getRuntime().totalMemory();
14.垃圾回收器GC
14.1 分代收集算法
- 频繁收集Young区
- 很少收集Old区
- 基本不收Perm区
14.2 引用计数法
原理:
每个对象都维护一个引用计数器,当有对象引用到该对象时则将引用计数自增,取消引用时自减,当gc发现对象的引用计数器为0时则回收它
缺点:
- 1.每个对象都需要自己维护一个引用计数器,计数器本身也有消耗
- 2.无法解决循环引用的问题
14.3 复制算法(Copying)
比例:
E区:S0区:S1区 = 8 : 1 : 1
当E区和S0区(上次Minor GC后存活的对象)存在的对象在Minor GC后仍旧存活就使用复制算法将其复制到S1区,然后清空E区和S0区,并将S1区变为S0区,下次E区和S0区的对象在Minor GC后仍旧存活的就复制到S1区,接着重复上述步骤,每经过一次Minor GC,仍旧存活的对象的年龄就加1,当年龄超过15岁时,这些对象就会被放入到老年代。如果S1空间存放不下仍然存活的对象,会根据内存分配担保机制,直接将新生代仍然存活的对象复制到老年代内存中,创建大对象时,新生代没有足够的连续内存分配时,也直接在老年代中分配空间可以使用-XX:MaxTenuringThreshold来设置对象在新生代存活的次数。如下图:
优点:没有内存碎片,只考虑存活的对象,效率高
缺点:总有一块空间是空闲的(S1区),浪费空间。如果对象存活率过高也无法使用复制算法
14.3 标记清除算法(Mark And Sweep)
在程序运行期间,若可以使用的内存被耗尽,GC线程就会触发并将程序暂停,随后将依旧存活的对象标记一遍,最终将堆中所有未被标记的对象清除掉,接下来便让程序恢复运行
原理:
从根集合进行扫描,遍历所有的GCRoots,将GCRoots可达的对象表示为存活的对象,对存活的对象进行标记,然后再次扫描整个内存空间,回收未被标记的对象。可以作为GC ROOT对象包括下面:
- a:java虚拟机栈中引用的对象
- b:方法区中类静态属性引用的对象
- c:方法区中常量引用的对象
- d:本地方法栈中本地方法的引用对象
优点:不浪费空间
缺点:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
14.4 标记整理算法
标记完成后不直接清理可回收对象,而是让所有存活对象向一端移动,在移动过程中清理掉可回收对象,不会造成不连续的内存碎片,适用于存活率较高的老年代
复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。
推荐一个GC讲解比较好的博客地址:https://mp.weixin.qq.com/s/NJm28LyDWFYvNDR5yU9dag?