基础篇-虚拟机JVM

JVM(Java虚拟机)内存模型

  • 栈内存 运行阶段存入内存 将引用标识记录堆中对象0x标识
    引用标识记录堆中对象0x标识位置,通过0x位置寻找方法区中的
    引用地址找到方法,方法执行时进栈,执行完后弹栈,先进后出
  • 堆内存 加载阶段存入内存 New 出来存储对象0x附带地址
    非静态成员变量初始化值,静态的存放在方法区,成员方法也存方方法区
    新创建一个对象则被Main中引用新的0x地址
  • 方法区 Class字节码文件加载时存入内存:
    静态区默认初始化值可以被使用 与 非静态区尚未初始化不能被使用
  • 本地方法区 native修饰
  • 寄存器 CPU进行使用

关于多线程

  • 栈内存: 多个栈多线程空间进栈的方法将指向堆中的同一对象0x标识成员变量
    每个线程又有自己独立的栈内存空间:
    Main主线程栈/Thread-0子线程/Thread-1子线程
  • 堆内存:堆中成员数据被多个线程共享资源/局部数据变量不共享

JVM 垃圾回收机制

什么是垃圾?

程序运行创建旧的对象被栈中引用标识记录该对象在堆中地址Ox,后来栈中引用标识记录了新的对象堆中地址值,堆中旧的对象地址0x没有被引用记录的时候,会被JVM认定为垃圾对象.垃圾回收器会在空闲的时候进行清扫.(只有垃圾触发到一定程度进行清扫)

如何验证垃圾会回收了?

垃圾回收器会执行该对象的父类Objcet类中的finalize()方法,重写finalize()方法,可以打印一句输出

如何通知垃圾回收垃圾?

System.gc();仅进行通知,是否执行权在JVM

虚拟机判断垃圾的方式有什么?

  1. 引用计数法(Java中不使用) 使用+1 不使用-1 ,但是循环中将会出现问题
  2. 根搜索算法:俗称"排除法"从根节点gcRoot寻找引用结点,直到寻找完毕,将没有牵引的gcRoot树的对象认定为无用结点全部清除

什么样的对象可以被gcRoot牵引

  1. 栈中引用记录的对象
  2. 方法区静态属性引用的对象
  3. 方法区常量引用对象(Final关键词)
  4. 本地方法区引用的对象(Native关键词)

如何避免垃圾

  1. 避免创建大对象,回收大内存的垃圾CPU效率低
  2. 避免频繁创建声明周期短的对象,会倒置很多内存碎片
    内存碎片:一整片大的空间,零零散散不连续,整理内存碎片,影响CPU效率

JVM优化

进程可能发生的问题:

  • 死锁:程序没有反应卡住了
  • 线程数量多:CPU资源占用突然升高

JVM运行参数

标准参数(稳定不需要修改)

-D
-server与-clint模式
-help
-version

非标准参数(不同JVM版本参数不同)

X非标准参数
 -Xint解释模式 编译快,运行慢
 -Xcomp编译模式 运行快,编译慢
 -Xmixed混合模式

XX非标准参数(使用率高)
  boolean类型
   -XX:[+-]<name>启用与禁用
 非boolean类型
   -XX:<name>=<value>

System.getProperty(“参数类型”)获取系统参数

  • Xms初始 与 -Xmx最大 堆内存大小
  • m:单位兆
  • s:size
  • x:max
  • -XX:+PrintFlagsFinal -version打印运行的参数 =默认 :=修改值
  • jinfo -falgs <进程id>查看进程运行参数
  • jps -l 查看正在运行的进程id和全包名
  • jstat -class <进程id>查看进程堆内存class加载统计
  • jstat -complier <进程id> 查看堆中编译统计
  • jstat -gc <进程id> 查看堆垃圾回收统计[使用率高]
  • jmap -heap <进程id>内存使用情况
  • jmap -histo | more 所有对象,包括活跃和非活跃
  • jmap -histo:live | more 所有活跃对象
  • jmap -dumop:formate=b,file=dumpFileName 保存到本地文件
  • jgat -prot <进程id> 对文件进行分析工具 [解决内存溢出]
    • Eclipse MAT工具 生成分析报表
  • jstack 查看jvm内部线程执行情况(线程状态)[解决线程死锁]

dump保存快照到本地文件,继用软件进行解析

JVM调优图形工具(jdk目录.bin中VisualVM.exe)【推荐】
VisualVM JDK提供工具 

	本地监控进程 
	
	远程监控进程
		需要基于JMX应用 远程Tomcat需要修改JMX配置
		
Jconslo JDK提供线程专用工具

堆内存模型

1.7JDK
年轻代:伊甸区
老年代:
永久区:class字节码文件,占用JVM内存

1.8JDK(没有了永久代)

  • 堆内存
    • 年轻代:(1伊甸区 + 2*转义区)
    • 老年代:
  • 非堆内存
    • 元数据空间: 占用本地内存

为了新版JVM和JRokit VM融合,废弃了永久区,取而代之的是元数据空间

线程的状态

初始化状态
运行状态或就绪状态
阻塞状态
等待状态
超时等待状态
终止状态

五加一种状态

垃圾回收

天下没有完美的算法,只有适合自己的算法

垃圾回收常见算法

- 引用计数法[Java不适用]
悠久的算法,对象一个唯一的的引用计数器,如果计数器为0.则没有被引用则回收
 * 优点:实用性高;
 * 缺点:需要计数的统计开销;无法解决循环引用(互相依赖)最大缺点
- 标记清除法
标记/清除两阶段,需要被Root对象链引用则会标记,没有被标记的将会被清除
 * 解决了循环引用的问题
 * 缺点:效率低,需要梳理所有引用关系;需要暂停应用;内存空间碎片化;
 * 

暂停是因为进行清理时会将没有及时引用的误删除

- 标记压缩算法
和标记清除算法一样,唯一区别:清理阶段,将存活的对象整理到一段连续的空间,然后对垃圾开始清除
 *解决了碎片化的问题
 *需要对数据进行整理
- 复制算法[Java年轻带From To区]
将内存一分为二(From/To),存活的对象复制另一片区域,对垃圾区域直接清空不做判断。From复制到To关系进行互换
 *优点:效率高,无碎片
 *缺点:垃圾对象少不适用;内存使用率折半;
- 分代算法
年轻代:复制算法 
老年代:标记清除算法 或标记压缩算法

垃圾回收器以及内存分配

- 串行垃圾回收器:DefNew 一个线程
>STW Stop The World 整个世界都停止,进行垃圾回收

- XX:+UseSerialGC 指定年轻代和老年代都是用串行垃圾回收器
- XX:+PrintGCDetails 打印垃圾回收详情

GC当前区域清除
fullGC所有区域清除。
- 并行垃圾回收器:PraNew 多个线程
- XX:+UseParNewGC 将年轻带设置为并行垃圾回收机制
- ParallelGC垃圾回收机制
设置年轻带
设置老年代
垃圾回收停顿时间
- CMS垃圾收集器Concurrent Mark Sweep

解决垃圾回收的停顿问题,针对老年代
初始化标记:
并发标记:
预清理
重新标记:对标记进行重新标记,防止新的对象误删除
并发清除
调整堆大小
等地下一次执行

- G1垃圾收集器[Java重点 jdk9.0使用替代CMS]
开启垃圾回收器 、设置堆的最大内存、设置最大停顿
提供三种模式
* Young GC
* Mixed Gc
* FullGC
* 逻辑分区:
 * YoungGC:Eden区耗尽触发
  + Remembered Set:RSet:谁引用了我集合,进行扫描,不需要全量扫描
 * MixedGC:混合GC,一部分老年代默认百分之45,触发全局并发标记类似于CMS并不是真正的清理,只做标记,需要拷贝存活对象阶段进行清理 。Young和Old都会涉及清除

G1垃圾回收机制参数

G1垃圾回收参数建议:不建议覆盖默认算法设定

GCeasy日志分析工具(页面在线测试)

取消内存物理划分,取而代之用逻辑标记来区分
挪移复制,解决空间碎片
巨型对象:Humongous区域 不得不启用FUllGC 而不分配进老年代

TomCat 8.0 优化

[TomCat方面]
	禁用AJP服务:一种TCP长连接的连接协议,节省Socket重复连接。
[JVM方面]
	Tomcat执行器(线程池):tomcat每一个请求都是一个线程。
[程序方面]
	Javap 分析字节码执行步骤选择步骤少的

Win下载 上传Linux

Server Status :访问Tomcat8080

tmocat三种运行模式

  • bio
  • nio
  • nio2[推荐]
  • apr

性能压力测试Apache JMeter

  1. 禁用AJP服务提示吞吐量
  2. 配置执行器(线程池)修改最大连接线程 和初始线程数 取三次平均值
  3. 牺牲正确率,提高性能
    (并不是最大线程数越大性能越好)
  • 设置最大等待数量,对服务器做保护,错误率会提高 平均响应时间缩短 吞吐量明细提升
    (根据性能判断Tomcat集群数量)
  1. nio2运行模式
  2. 设置并行垃圾回收器
  3. 修改堆的初始大小 和 最大堆
  4. 使用G1垃圾回收器

程序优化
源码角度使用javap 查看class字节码
javap -v

class 源文件 版本信息 生成时间
涉及常量池
构造器
main方法信息【重点】

字节码列表: 一步一步执行列表指令,本地表量表记录引用 操作栈(临时存取)

  1. 尽量使用局部变量(栈种,声明周期随方法)
  2. 对变量的重复计算,如size
  3. 避免懒加载,需要时候才创建
  4. 异常不应该做逻辑控制判断
  5. 不要声明fianl接口和数组 不要public
  6. 不要创建不适用的类
  7. 建议避免使用反射,应该将反射的对象提升全局共享
  8. 使用数据库连接池和线程池
  9. 容器尽量确认张图
  10. Arraylist随机遍历快 ,linkedlist添加快
  11. 使用Entry方式遍历Map,避免获取key再去获取value
  12. 不用手动调用system.gc()
  13. String尽量少使用正则表达式
  14. 日志的级别
  15. 资源close分开操作,防止一个无法执行,建议独立

请添加图片描述
JVM版本 -version 查看
我们使用oracle -hostpot

内存结构分类

Java内存 和 非Java内存 —> 堆外内存 非堆内存

线程私有 :
每一个线程对有一片空间

线程共享: 堆 方法区

非java内存:
nio编程常用

JAVA内存区域 :
程序计数器
方法区

内存空间
分两大部分
程序计数器 虚拟机栈 + 本地方法去 本地私有区

其他的是共有区

程序计数器
很多请求对应很多方法
线程A从上向下执行 , 采用的调度 :分时调度

程序计数器:
线程A如果执行 需要拿到CPU 分出的时间片
才会执行 执行一般 时间片没有了
而线程B执行 时间片到了 , 停止 。
A又获取到了时间片,从上一次的位置继续往后执行
(线程A和B 应用程序编译后, 需要内存记录执行的位置,每个线程私有这片区域)

虚拟机栈:
记录方法的成员变量 方法 变量 ,每次调用方法,都会形成栈帧,多个栈帧,一个方法就是一个栈帧,活动的方法就是活动栈帧,同一个线程同一时刻叫做活动栈帧,多个栈帧合称为栈;
方法使用完,栈释放。
一个方法一个栈帧,但递归就会栈溢出
或大量局部变量栈溢出

一个线程一个栈 ,堆还没分配空间
一个栈默认1024KB 1M 一兆
-Xss修改

多线程用了共享了成员变量 线程才不安全

所以栈内存是线程私有,不存在共享,线程安全

栈值越大,会让线程越少 如8个G 8个线程 8000kb 8000个线程

frames 帧

google反着翻译**

Debug运行的栈帧 叫活动栈帧 ,使用完后 弹栈

栈内存就释放了

每个方法调用形成 栈帧
多个栈帧 成为 栈

配置VM options 修改配置

我们自己定义的方法

本地方法栈
JDK自己定义的方法 不一定是Java定义的方法
源码看不到实现<—他是本地实现

本地方法栈就是被 native 修饰的 执行会分配到本地方法栈


堆 new 内存 内存最大的一块

新生代 / 老年代

新生代分为 伊甸园区 幸存区

堆这么分 是JVM 垃圾回收 叫 分代垃圾回收
不同的代根据不同的垃圾回收算法

新生代 刚创建的对象 ,当使用者

垃圾回收多次查询,就定义为老年代

新生代 伊甸园区 : 对象new出来的时候 伊甸园区分配内存,如果内存不足,触发新生代的垃圾回收,会把垃圾回收仍然存活的,存入两个幸村区其一(两个有一个空闲,存入:和复制算法有关系)

年轻代 伊甸园
1、3

年老代
2、3


非堆内存/ 堆外内存 --> 元空间 —> 指的方法区(不占内存,只是一个概念,早期叫 永久代, 1,8叫 元空间 metaspce。存放类对象 ,加载器信息,方法区当中保存,)

方法区 不是具体的实现

元空间是方法区的具体实现

元空间 也会 内存溢出 引入大量Jar包
运行时将字节码全部加载就会导致内存溢出

写代码中 引入代码 会占用 元空间的大小

直接内存 NIO中进行IO操作

直接内存使用 性能比较高


设置JVM的参数 如何配置
options

Tomcat 调节配置参数
配置文件
启动

使用

8080客户端 可以 查看 Heap 堆
Non-heap 非堆

Bootsra
可以查看


如何检测死锁
Gconsou应用


蓝窗口应用

String.intern 先会从字符串池 查找字符串 ,查到就返回,
查不到就 创建字符串

垃圾回收
分代垃圾回收

内存结构分为
堆 方法区 栈 程序技术去
新生代 (伊甸园区 幸存区(有一片是空闲的,用于复制算法))
老年代

JVM 每个加载进来的东西 如:A被引用 A的计数+1 用一次继续+1
引用取消-1 引用为0 回收
引用计数法
有弊端 A引用B B引用A 循环引用 没法解决

目前
可达性分析算法
一系列CG root根节点 触发 从这个节点找到对象的引用,说明这个对象不是垃圾
同样GCRoot 找到ABC ABC都不是垃圾,沿着ABC继续找,引用链能找到就不是垃圾,沿着链去找,发现没找到,没在链子上的游离对象,这些就被标记为垃圾对象,GC回收

栈帧 静态 本地引用 都可以人为GC Root对象
防止标记的状态发生变化,暂停全部
垃圾回收的时候都是标记,标记哪些是垃圾,防止发生变化,当垃圾回收 标记阶段,所有用户线程暂停,标记完后,用户线程继续 。主要目的不是为了暂停,而是为了标记

垃圾回收算法

标记清楚:
GCRoots 根节点去标记 : 找到蓝色的快不是垃圾,粉色没有找到就是垃圾。
一,标记
二,收集清除,碎片过多,仍会使的连续空间少
弊端:标记清楚算法,碎片过多

标记整理
一,标记清楚
二,整理,连续空间变大,减少内存碎片
弊端:多了判断整理步骤,效率变低

复制 (幸村区专用)
开辟两份大小相同的空间
编辑哪些是垃圾 ,采用复制算法,form区把幸存的内存进行拷贝到to区域,
然后全部清楚
弊端:需要的内存空间大,多了一份空间

垃圾回收算法,在不同代配置使用。

分代垃圾回收
Java代码只能或一小部分 Java对象很长一段时间
根据新生代和老年代判定 新生代 才new的 ,新生代很少活少,采用复制算法·1,老年代生存生存时间长,适用于标记 清楚 标记整理算法
新生代内存不足会触发GC MinorGC ,暂定时间短
老年代内存不足会触发GC Full GC ,暂停时间长

分代垃圾回收

尝试伊甸园区分配
在新生代 new 出来一个对象 分配内存空间 首先 新生代区 的伊甸园区 分配内存空间,如果伊甸园区分配不下,会触发 新生代的 垃圾 复制算法,会将它复制到幸村区,用户线程全部暂停,进行标记,复制,全部清楚
To 就是复制到那
将存货的对象复制到To区域 from 是空闲的
标记位进行转换:From和To交换位置,另外幸村去对象开始记录寿命,进入To区域 需要From和To位置转换
没有垃圾回收区域时To是空闲的
复制时,会临时使用To
经历了垃圾回收,开始记录寿命,如果再次计算,还活着位1 ,2,3 +1轮询都活着
如果存储的太大,就算伊甸园区空,也存不下,伊甸园区进行尝试
会使用 大对象直接普升至老年代:

测试 修改 内存区 (deap堆)
新生代 10M 老年代 10M 新生代比例 伊甸园8 :from1 :to1
配置此参数可以输出

采用复制算法 幸村区 存储不下 一部分回去 老年代

new 对象分配对象 会尝试尝试进入伊甸园区

To平时空闲 ,GC垃圾复制算法可能会临时使用

多次存货的对象
触发垃圾回收 ,存货的对象 会被开始记录寿命 ,幸村去多次存货 15次不死 ,晋升 到老年代 ,不能再高频清楚的新生代了,老年代垃圾回收频次少很多 ,除非 新生代空间 不足 。或 大对象 存不下 也会进入。

老年代连续 内存不粗 触发 FullGC ,触发了垃圾回收 ,没有内存释放 ,继续装装不下,就会堆内存溢出报错 ,

垃圾回收器
垃圾回收器 的指标: 吞吐量 和 暂停时间
工作时间 / 总时间 百分比越高 吞吐量越大
工作时间 + 垃圾回收时间 / 暂存时间 (stop-the-world)

垃圾回收器:

单线程/串行垃圾回收器 (采用单线程执行垃圾回收工作,适用于CPU服务器)

GC线程 仅 一个

使用时,我们是配对使用,新生代和老年代,会配置两个 Serial + SerialOld
新生代线程采用 复制算法
老年代线程使用 没有两块to from 标记清楚 或 标记整理

缺点:单线程 效率低 :目前CPU都是多核处理器

多线程回收器-吞吐量优先
分两类
吞吐量优先:新生代 老年代 采用算法一样
需要配置对应的参数

响应时间优先

G1 GOne 垃圾优先 JDK7 后 针对 内存空间大 《----------------

G1垃圾回收器 ,把内存分会若干个区域 :把内存的空间分为 E新生代去 S幸村区 O老年去 H大空间
标记时 可以设置暂停时间 ,H大空间根据 指定暂停时间 ,进来按照你预设的进行垃圾回收 ,不一定时间内完成

G1垃圾回收器 : 只配一个 就可以了

可以查看Tomcat 查看使用的垃圾回收器
CMD : jconsole

若想堆Tmocat指定 垃圾回收器 ,只需Tomcat的 bat 配置文件中修改 配置
修改配置 仅需 配置 一个 另一个会自动启用 因为是一对
当然可以手动修改,但配对使用是由规律的

根据JVM版本有关系
SerialNew 没有关系表则不能用 ,由规范规律

重点: 分代垃圾回收

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值