文章目录
JVM内存结构
堆
- 作用:主要是用来存储所有的对象实例,分为新生代和老年代,这样的区分主要是用于垃圾回收
- 抛出异常:可动态增加内存大小,增加失败后会抛出OOM异常
- 设置大小:-Xms设置堆初始值;-Xmx设置堆最大值;-XX:NewSize设置新生代大小;-XX:MaxNewSize设置新生代最大值
JAVA虚拟机栈
- 作用:主要用来存储局部变量、操作数栈和对象引用等信息,从方法调用直至执行完成就对应一个栈帧在Java虚拟机中入栈和出栈的一个过程
- 抛出异常:请求深度超过最大值时,会抛出StackOverflowError,当栈进行动态扩展无法申请足够内存时抛出OOM异常
- 设置大小:-Xss设置栈的大小
JAVA本地方法栈
- 作用:与JAVA虚拟机栈类似,但是是为本地方法服务的,例如通过C或C++语言编写的程序
- 抛出异常:与JAVA虚拟机栈相同
程序计数器
- 作用:记录当前线程执行的字节码行号的指示器
- 抛出异常:不会抛出异常
方法区
- 作用:用来存储类加载器加载进来的类信息,类的信息,常量以及静态变量。在1.8之前存储在JAVA虚拟机内存中,称为永久代,1.8之后存储在本地内存中,其中将常量和静态变量移入堆中,称为元空间(放入本地内存的好处是避免永久代内存溢出)
- 抛出异常:和堆一样无需连续的内存,动态扩展时,如果申请不到足够的内存,则会抛出OOM异常
- 设置大小:-XX:PremSize方法区初始化大小;-XX:MaxPremSize方法区最大值(1.8 通过"-XX:MaxMetaspaceSize"来控制最大内存)
JVM垃圾收集
如何判断一个对象是否可回收
- 引用计数算法:每一个对象增加一次引用时,计数器加1,引用失效时减1,当无引用时,该对象可被清除;问题:当两个对象循环引用时该方法失效
- 可达性分析算法:通过GC Roots从起始点进行搜索,可以到达的对象是存活的,无法到达的对象可以被回收。主要是本地方法栈和虚拟机栈以及方法区中的常量和静态变量是否有引用。
可作为GC Roots的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的native方法)中引用的对象
常用的垃圾回收算法
- 标记-清除:标记出存活和可回收的对象,标记完成后进行清除;问题:会产生碎片,无法创建大的对象时,需要再次回收
- 标记-整理:标记出存活的对象,将其规整至一个区域中,区域外的即可全部清除;
- 复制:将其分成两块,第一块中存入对象,对象需要清除时,将存活的对象复制到另一块对象中,再清除第一块中所有的对象;问题:牺牲了内存空间来换取速度,适用于对象存活周期短的场景。堆中的新生代采用了此方式,将其分为Eden和Survivor1及Survivor2,对象首先存储在Eden区,回收时将存活对象存入Survivor1中,清除其他区域的对象,再次回收时,将存活的对象存入Survivor2中,清除其他的对象。Eden和Survivor大小默认比例是8:1,而老年代中采用的是标记-清除或者标记-整理的算法
- 分代收集:将虚拟机内存划分为不同的代,新生代采用复制算法,老年代采用标记-清除或者标记-整理的算法
常用的垃圾收集器
- 串行收集器:只有一个线程,且不能与用户线程同时运行,效率较低
- 并行收集器:有多个线程,但不能与用户线程同时运行
- CMS收集器
- 过程:
1. 初始标记:一个线程,与用户线程互斥,标记GC Roots直接关联到的对象,需要停顿,但很快
2. 并发标记:可与用户线程同时运行,但用时较短
3. 重新标记:多个线程同时运行,需要进行停顿,标记变化的对象
4. 并发清除:不需要停顿 - 缺点:会产生停顿,吞吐量低,导致CPU利用不够;在和用户线程并发处理时,会产生浮动垃圾;对老年代的标记-清除算法会产生碎片
- 过程:
- G1收集器:将堆划分成大小相等的区域,新生代和老年代没有了物理的分隔
- 过程:
1. 初始标记:一个线程,与用户线程互斥,标记GC Roots直接关联到的对象,需要停顿,但很快
2. 并发标记:可与用户线程同时运行,但用时较短
3. 最终标记:修正和用户并发标记时的对象,通过日志记录将其加入到Set中,该阶段需要停顿,但可并行
4. 筛选回收:根据回收的价值和成本进行排序筛选回收,可根据用户期望停顿的时间来回收对象 - 特点:1.整体上是标记-整理算法,而Regen区域内部是通过复制算法,故没有碎片;2.可让使用者控制回收对象的时间
- 过程:
Java虚拟机垃圾回收(三) 7种垃圾收集器:主要特点 应用场景 设置参数 基本运行原理
JVM内存分配和回收策略
内存分配策略
- 分配至Eden:新的且大小未达到分配到老年代的对象都优先分配至Eden区
- 分配至老年代:
- 大的对象,如数组和很长的字符串
- 年龄达到了设置的阈值(年龄的计算:移动到Survivor区一次,则加一)
- Survivor中相同年龄的对象大小大于Survivor空间大小的一半时
回收策略
- Minor GC
- 作用:对堆中的新生代进行一次垃圾回收,速度较快
- 条件:Eden满时触发
- Full GC
- 作用:对新生代和老年代进行一次垃圾回收,速度较慢
- 条件:
- 调用System.gc()方法,一般不建议调用
- 老年代满时
- jdk1.8之前方法区,永久代满时
- 空间分配担保失败时,也就是新生代的数据无法存入老年代时
- CMS GC存入老年代,老年代空间不足时
JVM类加载机制
类加载过程
- 加载
- 获取类的二进制字节流
- 将类的二进制数据转换为方法区的数据结构
- 在堆中创建类的Class对象,用作类访问的入口
- 连接
- 验证:校验类是否符合JVM规范,是否安全
- 准备:对类的静态变量赋初始值
- 解析:将常量池中的符号引用转换为直接引用
- 初始化:对类的静态变量进行赋值,静态语句块进行执行操作,且还会加载主动引用
- 使用
- 卸载
类何时会被加载
只有主动引用才会被初始化
- 主动引用:1.父类;2.new的对象;3.反射调用此类;4.main方法的启动类;5.调用此类的静态变量(final除外)和静态方法
- 被动引用:1.通过该类型定义的数组;2.通过获取父类的静态变量,子类是被动引用;3.final定义的常量
类加载器
- 启动类加载器:由C++实现,用来加载JAVA_HOME/jre/lib下的类以及加载扩展类加载器及应用类加载器,并不继承java.lang.ClassLoader
- 扩展类加载器:由JAVA实现,用来加载JAVA_HOME/jre/ext下的类
- 应用类加载器:由JAVA实现,用来加载CLASSPATH下的类
- 自定义类加载器:可以通过继承java.lang.ClassLoader来实现自己的类加载器
双亲委派模型
- 加载过程:加载过程首先会判断该类是否被加载,若未被加载则交给父类去加载。类加载器的结构如下:
- 好处:统一先让父类去加载类,保证了基础类的统一
JAVA内存模型—JMM
JMM工作方式
-
主内存
主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发现线程安全问题。
-
工作内存
主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝),每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。注意由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。
由于主内存为共享的内存,故而会出现线程安全问题,所以需要进行加锁等操作,之所以不将所有的数据都进行共享,而有工作内存的原因是JMM屏蔽了操作系统和硬件的内存访问差异,所以Java才得意在各个平台上达到访问内存的一致性。
JVM监控常用工具
- Java VisualVM:java自带工具,查看CPU,内存和线程运行情况等,可导入dump文件问下OOM问题
- jconsole:java自带工具,查看CPU,内存和线程运行情况等
- MAT:第三方工具导入dump文件问下OOM问题
JVM性能调优
- 整体目标:1.停顿时间;2.吞吐量;3.覆盖区
- 垃圾回收调优目标:1.GC的时间足够小;2.GC的次数足够少;3.尽量减少Full GC,原因是较为耗时;
- 垃圾回收调优方案:1和2实际上是矛盾的,当堆设的较大时,次数减少,时间增大,堆设的较小时则相反,故视具体情况而定;对于第3点,年轻代和年老代的默认比例为1:2,两种的比例和可以通过NewRadio来调整,年老代较小,则会增加Full GC的次数,但时间较小,如果年老代较大,则相反,故需要根据具体的情况来设置,如果临时对象较多则将年轻代设置更大,若常量较多,则设置更大的年老代。3.减少使用全局变量和大对象
Java虚拟机垃圾回收(四) 总结:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法 - 重要参数(可调优)解析:
- -Xms12g:初始化堆内存大小为12GB。
- -Xmx12g:堆内存最大值为12GB 。
- -Xmn2400m:新生代大小为2400MB,包括 Eden区与2个Survivor区。
- -XX:SurvivorRatio=1:Eden区与一个Survivor区比值为1:1。
- -XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory 异常可以上调这个值。
- -XX:+DisableExplicitGC:禁止运行期显式地调用 System.gc() 来触发fulll GC。
注意: Java RMI的定时GC触发机制可通过配置-Dsun.rmi.dgc.server.gcInterval=86400来控制触发的时间。 - -XX:CMSInitiatingOccupancyFraction=60:老年代内存回收阈值,默认值为68。
- -XX:ConcGCThreads=4:CMS垃圾回收器并行线程线,推荐值为CPU核心数。
- -XX:ParallelGCThreads=8:新生代并行收集器的线程数。
- -XX:MaxTenuringThreshold=10:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
- java常用查看性能工具:
- jps:查看进程状况
- jstat:整体监控信息,如垃圾回收,内存信息
- jmap:内存映射工具,查看进程的内存情况,对象数量大小等
- jstack:线程的堆栈信息