java 整体运行结构以及JVM的结构
java的源程序 --》编译(字节码)JDK的功能 ----》JVM解释这个字节码 怎么找到的呢?
classLaoder --->java程序的执行JVM环境 执行引擎--》可移植性 JNI(本地方法接口)---》本地函数库
jdk1.8以后
依然是双亲加载机制不可改变
bootstrap 加载器
新版本的类加载器已经发生了变化的为AppClassLoader
运行时的数据区是整个JVm的关键所在,
运行时数据区域组成;
方法区:
栈 java的引用传递依靠的是堆内存,同一块堆内存空间可以被不同的栈内存所指向的。
堆 程序运行的单位,里面存储的信息都是与当前线程相关的内容,包括局部变量,程序的运行状态,方法的返回值
程序的计数器 是一个非常小的空间,对象的晋级问题(依靠的就是计数器)
本地方法栈 在进行递归调用的时候,保存栈帧内容(局部变量表,操作数栈 当前方法所属类的运行时的常量引用,返回地址)
JVm的关键部分是讨论堆内存的优化。既然要进行优化,必须清楚java的对象访问方式。
java进行对象引用的时候,并没有使用到句柄的概念,它直接采用的是hotspot的虚拟机标准的指针引用。
java是一个开源的编程语言,实际上有三个所谓的虚拟机标准,:
SUN 标准的hotSpot虚拟机
JRockeit 虚拟机
IBM :J9
mixed mode 混合模式
java -Xcomp 改变所谓的JVm的运行模式
当前的java行业不在适合客户端的程序开发了。
默认的jdk配置是server模式
优化的关键就是堆内存的空间的控制
堆内存就会产生大量的垃圾,我们需要考虑关于Gc的问题,真正导致我们程序变慢的原因就是堆的控制的策略上,控制我们的回收策略
jdk1.9 默认策略是G1回收器。
jdk1.8 之后
堆内存的空间 划分为年轻代 (eden伊甸园区, survivor1 survivor2) 老年代 元空间(物理内存)这是新的架构
在1.8之前的
堆内存的空间 划分为年轻代 (eden伊甸园区, survivor1 survivor2) 老年代 永久区(方法区)
堆内存组织结构以及内存有关的
年轻代:
伊甸园:新生的小对象。每当使用关键字new的时候,默认的话都在此空间进行对象的创建。
如果创建的对象过多,那么最终的结果造成伊甸园的内存空间占满,此时发生晋升的操作(若干次MinorGC执行还保留的对象,晋升到存活区)
存活区(survivor): 进行一些Gc后保存的对象。s1 s2 有一个是空的,是向老年代晋升。
老年代对象:这些对象都已经经历了无数次的GC之后依然被保留下来的对象,很难被清除
对于一个很大对象直接保存在老年代的。 如果现在一个老年代的不足,出现FullGC
也是我们最核心的问题,堆的结构优化,每一块空间都是有一个伸缩区
年轻代到老年代之间具有伸缩区
伸缩区的考虑在某个内存空间不足的时候,会自动打开伸缩区扩大内存,当发现当前的区域内存可以,满足要求的时候,就可以进行收缩了。
如果不进行收缩的话优点是:可以提升堆内存的结构优化。
如果不进行收缩的缺点:空间太大了。那么没有选择合适的GC算法,就会造成堆内存的性能下降。
package com.jvm.test;
public class TestGCDemo {
public static void main(String[] args) {
Runtime run = Runtime.getRuntime(); //单例对象
System.out.println("max_MEmory" + run.maxMemory()); //当前程序的最大内存
System.out.println("total_MEmory" + run.totalMemory()); //默认的可用内存
maxMemory 实际上当前可用的是等于当前物理内存的1/4
totalMemory是当前物理内存的1/64
剩下的都是伸缩区的空间,这么大的伸缩区的空间,所以在进行堆内存分配的过程中里面当前用户访问量增加的时候就一定会导致不断的判断空间是否充足。
不断的进行内存空间的增长,不断进行内存空间的收缩和释放
-Xms: 设置初始化的内存空间大小
-Xmx:设置最大的可用内存空间。 这两个参数很重要
怎么使用这两个参数 ? 可以使用的单位 KB MB
可以减少堆内存的收缩处理操作,这两个参数针对整个堆内存的。
-Xms2g -Xms2g
当堆内存空间很大的情况下,据需要考虑到Gc执行的效率了。所以我们说呢在这个环节里面我们考虑两个技术名词:
BTP(年轻代)、TLAB
年轻代优化的结构:在伊甸园是采用栈的形式将创建的对象放到栈顶的位置,计算每一个对象的长度,当空间不足的时候,MinorGC
TLAB 技术(分块保存)多块的空间多线程处理操作,
-Xmn: 设置年轻代的空间大小默认是物理内存的1/64; 这个建议不要调
-Xss:设置每一个线程的栈空间,
-X:SurvivorRator: 设置伊甸园与两个存活区的内存分配比,默认 8:1:1
老年代:
与年轻代的比率-XX:NewRatio
当对象很大的时候,往往不在年轻代保存,直接进入老年代保存,有个参数"-XX: PretenureSizeThreshold"
分水岭:jdk1.8取消了永久代,变成了元空间 ,直接利用物理内存了。
}
}
gc算法
算法的选择决定了程序的执行性能。
传统意义上的回收处理操作,只是认为有了垃圾产生,而后自动进行GC操作(MinorGC, FullGC),或者手工利用System.gc()操作(MIniorGc, FullGc)
java的GC机制,经历了20年的发展,对于电脑的硬件技术产生了很大的变化,最初在一块cpu上进行多线程分配,现在多核cpu,多线程支持,
对于GC算法里面就需要考虑不同的内存分代:
年轻代GC策略: 串行GC 并行GC
老年代GC策略:串行GC 并行GC
【年轻代串行GC的操作流程】
扫描年轻代的所有存活的对象:
使用MinorGC进行垃圾回收,同时将还能够存活下来的对象保存到存活区(s0, s1)里面;
每一次进行MinorGC的时候,都会引起s0和s1的交换,
经过若干次MinorGc还能够保存下来的进入到老年代。
【年轻代并行Gc流程】
算法:复制-清理算法,在扫描和复制的时候均采用了多线程的处理模式来完成。
在年轻代进行MinorGc的时候实际上也可能触发到老年代的Gc操作。
CMS引出老年代的操作的问题 缺点在第一次和重新标记的时候,影响会比较小,但是在后续的并发回收中会产生内存碎片
【老年代的并行】
在最早的时候主要是使用了此种GC算法,但是这种算法会有一个严重的问题:STW(产生中断,因为需要进行垃圾回收的标记)。
-- 暂停当前的所有执行的程序(挂起)
-- 标记出垃圾,标记的时间越长,那么挂起的时间越长,如果此时堆内存很大的话,那么时间一定会更长;
-- 预清除处理;
-- 重新标记过程,看看还有没有垃圾;
-- 进行垃圾的处理;
--程序的恢复执行
[【老年代串行GC】
算法:标记-清除-压缩(碎片整理)
扫面老年代的存活对象。进行标记对象;
遍历老年代的存储空间,回收标记的对象,
还需要进行老年代的内存压缩,也就是所谓的碎片整理(把空间都整理到一块去)
程序设置参数; -Xms48m -Xmx48m -XX:+PrintGCDetails
-Xms48m -Xmx48m -XX:+PrintGCDetails
-Xms48m -Xmx48m -Xlog:gc* -XX:+UseSerialGC:(手工式的修改GC)
不用串行GC -Xms48m -Xmx48m -Xlog:gc* -XX:+UseSerialGC
不用并行GC -Xms48m -Xmx48m -Xlog:gc* -XX:+UseParallelGC
并行老年代 -Xms48m -Xmx48m -Xlog:gc* -XX:+UserParallelOldGC
最终的Gc 不在使用上面的算法 上面的串行并行算法会影响程序的性能, 最初的电脑没有这么高的配置,内存是K为单位的,
现在的电脑配值,jdk9开始是G1算法
准备多块的内存空间 在其中的某一块上面执行Gc算法 G1支持的最大内存是64GB, 每一个小的区域里面可以设置的范围1-32MB
G1收集:-Xms48m -Xmx48m -Xlog:gc* -XX:+UseG1GC
JVM核心优化的问题 :减少伸缩区的使用 提升GC的效率 ,G1是现在最好用的GC算法, 不过这个里面还是在每一块子空间上面结合之前的串行和并行的算法的。
线程池的栈的大小的配置;
线程池的配置
在tomcat下面tomcat优化
在tomcat的bin目录下面;找到catalina.sh
CMS适合内存小的 G1的主要特征是可以进行一个大内存的使用。