阳哥:jvm
█ 1.JVM组成部分:
JVM包含两个子系统和两个组件,
两个子系统为Class loader(类装载)、Execution engine(执行引擎);
两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。
**Class loader(类装载):** 将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。
分类:
虚拟机自带的加载器(启动类加载器bootstrap // 拓展类加载器Extension // 应用程序类加载器appclassloader)
|||||| 用户自定义加载器
Execution engine(执行引擎):执行classes中的指令。
Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。
作用 :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,(而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的)命令解析器执行引擎(Execution Engine)将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
○ 1.1 Java程序运行机制步骤
1)首先利用IDE集成开发工具编写Java源代码,源文件的后缀为.java;
2)再利用编译器(javac命令)将源代码编译成字节码文件,字节码文件的后缀名为.class;
3)运行字节码的工作是由解释器(java命令)来完成的。
○ ○ 1.2.0 深拷贝/浅拷贝
深拷贝和浅拷贝
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
○ ○ 1.2.0.1 堆栈的区别
物理地址:
堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
内存分别:
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。
栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
存放的内容:
堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。
PS:
静态变量放在方法区
静态的对象还是放在堆。
程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
○ 1.2 JVM 运行时数据区:PC寄存器 | stack栈 | 堆
1.2.1 PC寄存器
每个线程都有一个程序计数器,私有,指针,
记录了方法之前调用和执行情况。
作用: 存储指向下一条指令的地址。
1.2.2 方法区
储存了每一个类的结构信息,是一种规范。
1.2.3 stack本地方法栈
有可能会有错 :stackOverflowError
线程创建时创建,跟线程生命期 栈不存在垃圾回收问题。私有。
包括:8种基本类型的变量 + 对象的引用变量 + 实例方法。
int char double boolean float long short byte
栈中的数据以 栈帧 格式存在,栈帧是一个内存区块,是一个数据集。
栈帧 存储 局部变量表、操作数栈、动态链接、方法返回地址、指向运行时常量池的引用。
栈的大小和jvm有关 1Mb左右。
1.2.4 堆
一个jvm实例只有一个堆内存,大小可调。 存类、方法、常变量。
堆内存逻辑上 分:新生+养老+元空间
1.2.5 Java 虚拟机栈
用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
○ 1.3 双亲委派
一个类收到了类加载器请求,他会为委派给父类去完成,只有父类完成不了,子类才做。
好处:保证使用不用发的类加载器 最终得到 同一个Object对象。
█ 2.GC
○ 2.1 JVM垃圾回收 如何确定垃圾 GC roots
垃圾:内存中不被用的空间
判断能否回收:1.引用计数法 // 2.枚举根节点做可达性分析(从GC roots对象,找有没有 引用链是否连接,遍历)
GC roots:
1.虚拟机栈中引用的对象 局部变量
2.方法区的类静态属性引用的对象
3.方法区中常量引用对象
4.本地方法栈JNI
○ 2.2 GC算法 4大垃圾回收算法思想:
1.引用计数 // 2.复制 // 3.标记清除 // 4.标记整理
最合适的是 分代收集算法。
年轻代是:
老年代是:??????????
○ 2.3 gc回收类型:
UseSerialGC、UseParallelGC、UseConcMarkSweepGC、UseParNewGC、UseParallelOldGC、UseG1GC
-XX:+UseParallelGC 和 -XX:+UseParallelOldGC可互相激活
○ 2.4 4种主要垃圾收集器 落地实现:
1)串行垃圾回收器(Serial) < 单线程,暂停所有用户线程,直到收集结束,,,,,停顿,不适合服务器环境,适用于client > +XX:UseSerialGC
2)并行垃圾回收器(Parallel) < 多个垃圾收集线程,暂停了用户线程,,,,,适用于科学计算 大数据处理 弱交互场景 >
3)并发标记清除垃圾回收器(CMS) < 用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程 ,,,,,互联网公司多用,适用 对响应时间有要求的场景 >。
4)G1垃圾回收器 < 将 堆内存分割成不同的区域 然后并发的 对其进行垃圾回收 >
○ 2.5 并发垃圾回收器 CMS 4步
Concurrent Mark Sweap并发标记清除,并发收集低停顿。
用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程 ,,,,,互联网公司多用,适用 对响应时间有要求的场景。
希望获取最短回收停顿时间。
CMS非常适合 堆内存大、CPU核数多的服务器端应用。
XX:+UseConcMarkSweepGC
开启后会自动开启-XX:+UseParNewGC
开启参数后,使用ParNew(Young) + CMS(Olde)+ Serial Old
的收集器组合。
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器
进行垃圾清除,此时的性能将会被降低。
CMS4步骤:
1初始标记:标记GC Root能关联的对象,需要暂停所有的工作线程。
2并发标记:GC root跟踪,不需暂停工作线程
3重新标记:修正 程序继续运行 导致 标记变动 要暂停
4并发清除:和用户线程一起,不需要暂停工作线程,直接清理对象。
13停,24并发
优点:并发收集低停顿
缺点:并发执行对CPU资源压力大 // 采用的标记清除算法会导致大量碎片
CMS必须要在老年代堆内存用尽之前完成垃圾回收, 否则CMS回收失败时 用serial Old GC
○ 2.6 年轻代 老年代
○ 2.6.1 年轻代 新生代 Young Gen 整个堆的1/3 复制算法
串行GC(Serial)/(Serial Copying)
并行GC(ParNew)
并行回收GC(Parallel)/(Parallel Scavenge)
年轻代的垃圾回收算法用的 复制算法,,把内存分成两块,用完之后补上。不会产生内存碎片。浪费内存;;存活率高,花费时间长。
○ ○ 2.6.1.1 Minor GC的过程
普通GC 只针对新生代区域的GC,频繁,速度快
HotSpot jvm把年轻代分成了1个Eden
和 2个survivor
8:1:1
复制->清空->交换
1.新创建的分配到eden区,eden区满了之后,Minor GC,把或者的对象copy到survivorFrom区,
eden区再次GC时同时扫描Eden和from回收,剩下的到 复制到SurvivorTo,年龄+1,
如果达到老年标准就 复制到 老年代。
清空eden/survivorFrom区,谁空谁是 TO,so from和to交换。
默认参数是15
○ 2.6.2 老年代 永久代 Tenure Gen 整个堆的2/3 标记整理算法
永久代 java8之后被Metaspace取代;
存放:1.虚拟机加载的类信息 /// 2.常量池 /// 3.静态变量 ///4.及时编译后的代码
串行GC(Serial Old)/(Serial MSC)
并行GC(Parallel Old)/(Parallel MSC)
并发标记清除GC(CMS)
老年代 一般 标记清除 或者标记清除+整理
○ ○ 2.6.2.1 Full GC major GC
全局GC ,发生在老年代的垃圾收集动作。慢Minor GC 10倍。
##### CMS Concurrent Mark Sweap并发标记清除,并发收集低停顿。
用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程 ,,,,,互联网公司多用,适用 对响应时间有要求的场景。
希望获取最短回收停顿时间。
CMS非常适合 堆内存大、CPU核数多的服务器端应用。
XX:+UseConcMarkSweepGC
开启后会自动开启-XX:+UseParNewGC
开启参数后,使用ParNew(Young) + CMS(Olde)+ Serial Old
的收集器组合。
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器
进行垃圾清除,此时的性能将会被降低。
CMS4步骤:
1初始标记:标记GC Root能关联的对象,需要暂停所有的工作线程。
2并发标记:GC root跟踪,不需暂停工作线程
3重新标记:修正 程序继续运行 导致 标记变动 要暂停
4并发清除:和用户线程一起,不需要暂停工作线程,直接清理对象。
13停,24并发
优点:并发收集低停顿
缺点:并发执行对CPU资源压力大 // 采用的标记清除算法会导致大量碎片
CMS必须要在老年代堆内存用尽之前完成垃圾回收, 否则CMS回收失败时 用serial Old GC
○ ○ 2.6.2.2 元空间和永久代的区别
元空间 并不在虚拟机中,而是使用本地内存,默认情况下,元空间的大小仅受本地内存限制。
其中存的信息有:1.虚拟机加载的类信息 || 2.常量池 || 3.静态变量 || 4.即时编译后的代码
9. 垃圾回收器的理解
○ 2.9 G1GC 垃圾收集器 配置和参数
+XX:-UseG1GC
分代收集器
不像之前的垃圾回收器,G1的Eden survivor Tenured,而是变成了region块(1M-32M不等G1HeapRegionSize默认2048个分区 64G内存)大小相同
不物理隔离了
整体上采用 标记整理算法,局部复制
,不会产生内存碎片
region 整了一系列不连续的内存区域,避免了全内存区的GC操作。
G1中 Region一部分包含新生代,垃圾收集也要暂停所有线程,也要拷贝存活的
G1中 Region一部分包含老年代,复制到另一个区域
G1中 有块Humongous巨型区域,直接分配在老年代。
过程:
缺点:
并发执行,对CPU资源压力大,,采用的标记清除算法会导致大量碎片。
G1的参数设置:
○ ○ 2.9.1 G1和CMS的优势
- G1没有内存碎片,整理空闲空间更快 ,CMS 先标记后清除 有内存碎片 但低暂停时间
- 可以精确地控制停顿 —XX:MaxGCPauseMillis=100
○ 2.10 垃圾处理器的比较
○ 2.11 如何选择合适的垃圾收集器
根据CPU
○ 2.12 如何查看默认GC收集器:并行
java -XX:+PrintCommandLineFlags -version
█ 3.jvm参数设置
jvm参数类型:
1.标配参数<java -version |-help | java -showversion > // 2.X参数 // 3.XX参数
○ 3.1 jvm常用参数:
jvm参数查看命令:
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
jps -l | jinfo -flag
○ 3.2 -Xms和-Xmx
○ 3.3 查看jvm默认值 系统默认值
█ 3.0 jvm调优
对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数。
Full GC因为需要对整个堆进行回收,所以比较慢,因此应该尽可能减少Full GC的次数。
○ 3.0.1 导致full GC 的原因:
1)年老代(Tenured)被写满
调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象 。
2)持久代Pemanet Generation空间不足
增大Perm Gen空间,避免太多静态对象 , 控制好新生代和旧生代的比例
3)System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制
在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节,下面详细介绍对应
○ 3.0.2 JVM调优的方法和步骤
1、监控GC的状态
使用各种JVM工具,查看当前日志,分析当前JVM参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分和GC执行时间,觉得是否进行优化。
崩溃前的现象:
(1)每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4、5s
(2)FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC
(3)年老代的内存越来越大并且每次FullGC后年老代没有内存被释放
之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值,这个时候就需要分析JVM内存快照dump。
2、生成堆的dump文件,进行分析
通过JMX的MBean生成当前的Heap信息,大小为一个3G(整个堆的大小)的hprof文件,如果没有启动JMX可以通过Java的jmap命令来生成该文件。
**Mat(Eclipse专门的静态内存分析工具)推荐使用**
如果满足下面的指标,则一般不需要进行GC:
Minor GC执行时间不到50ms;
Minor GC执行不频繁,约10秒一次;
Full GC执行时间不到1s;
Full GC执行频率不算频繁,不低于10分钟1次;
3、调整GC类型和内存分配
如果内存分配过大或过小,或者采用的GC收集器比较慢,则应该优先调整这些参数,并且先找1台或几台机器进行beta,然后比较优化过的机器和没有优化的机器的性能对比,并有针对性的做出最后选择。
○ 3.0.3 JVM调优参数参考
1)堆,一般可以通过-Xms -Xmx 通常把最大、最小设置为相同的值; 为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间;
2)年轻代和年老代将根据默认的比例(1:2)分配堆内存, 可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代。
比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。
合理值:
(1)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 。
(2)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间。
如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性。
3)(多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC 。
4)线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。
理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。
█ 4.强软弱虚 java引用
强引用:
内存不足,出现OOM也不GC。给对象赋一个引用变量,引就是强引用。。。java内存泄露。
软引用:
java.lang.ref.SoftReference
内存充足 不会被回收 // 不足就会;;;用在 高速缓存
○ 4.1 读取大量本地图片 软引用
弱引用:
一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
○ 4.2 WeakHashMap:
这个只要gc 就没了
虚引用 幽灵引用
java.lang.ref.PhantomReference类实现。
○ 4.3 ReferenceQueue
█ 5.oom的认识
○ 5.1 StackOverflowError
○ 5.2 OOM: java heap space
byte str+=
○ 5.3 OutOfMemoryError: GC Overhead limit exceeded
连续多次GC,都只回收 不到2%的极端情况。
arraylist.add()一直往堆内存里面加,这样就会爆满了
○ 5.4 OOM: Direct buffer memory
本地内存用完了。
他对应的是本地内存。
16G内存的1/4
○ 5.5 OOM: unable to create new native thread
创建了太多线程
线程与语言关系不大,与操作系统有关。
在linux系统中, javac -d . UnableCreateNewThreadDemo.java
编译之后就会有了com这个包 文件
java com.bao.baobao.UnableCreateNewThreadDemo
出现错误,
ps -ef|grep java
这个错误 是非root用户登录Linux系统测试 1024<root是unlimited> || 服务器界别调参调优
解决方式:
ulimit -u
vim /etc/security/limits.d/90-nproc.conf
218103768(20.8M)
○ 5.6 OOM: Metaspace
metaspace溢出
java8之后metaspace代替 永久代;;metaspace不在虚拟机内存,是本地内存。
查看初始化参数: java -XX:+PrintFlagsInitial查看其中的 -XX:Metaspacesize