JVM学习笔记

性能分析和检测工具

[top]
观察系内存和CPU消耗
top -Hp pid 观察进程中的线程CPU和内存消耗。
[jps]
打印所有java进程的 ID
[jstack]
jstack pid > pid.stack 生成虚拟机当前线程快照。
jstack -l pid 查看进程的状态
[jmap]
生成堆转储快照。
jmap -dump:format=b, file=/tmp/dump.file
jmap -heap pid 查看进程的堆内存/元空间的分配
jmap -histo pid 显示堆中对象统计信息(各个对象的分配次数和所字节数)
[jhat]
分析jmap生成的堆转储快照。
[jstat]
监视虚拟机各种运行状态信的命令工具 .内存状态,GC状态
jstat-gcutil pid 查看进程的GC情况
[jinfo]
实时查看和调整虚拟机各项参数。
[arthos]

生产中遇到问题的分析步骤:
1)使用 top -d 1 查看占用内存和CPU的进程
2) ps -o thcount pid 查看进程的线程数
3) top -Hp pid 查看进程的线程CPU和内存占用
4) jstack pid 查看进程中所有线程的堆栈信息,定位CPU消耗比较高的线程执行的任务及状态
5) jmap -histo pid 查看进程分配的对象统计信息。
6) netstat -nap 查看进程的所有建立的网络连接及状态(Recv/Send Q 以及TIMED_WAIT状态)
7) 到/proc/pid/fd 下查看进程打开的fd数

垃圾收集器

在这里插入图片描述垃圾回收器是随着内存越来越大的过程而演进。从分代算法演化到不分代算法。
[新生代]
主要采用复制算法。
Serial : 几十兆
Parallel New :Serial收集器的多线程版本.使用多线程进行垃圾收集. 几个G
Parallel Scavenge:
是一个并行的多线程新生代收集器,它也使用复制算法. 它的目标是达到一个可控制的吞吐量(Throughput)。
而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
[老年代]
Serial Old: 标记-整理 算法。
Parallel Old:Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
CMS(Concurrent Mark Sweep):
收集器是一种以获取最短回收停顿时间为目标的收集器。基于“标记-清除”算法。一般处理几十G,承上启下开启了并发回收.
初始标记:标记GC ROOT能直接关联的对象。 stop the world
并发标记:进行GC Roots Tracing的过程。
重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。 stop the world
并发清除:
缺点:对CPU资源非常敏感;无法处理浮动垃圾(并发清除阶段长生的);内存碎片(清除)
由于CMS运行期间用户线程还在运行,所以要是CMS运行期间预留的内存无法满足需要,就会出现“Concurrent mode failure”失败
这时CMS会临时启用 serial old 收集器进行老年代垃圾回收,这样停顿时间就很长了。所以CMSInitiatingOccupancyFraction设置太 高很容易引起 失败,性能反而降低。(现场有出现垃圾回收时间超过2小时)。

  G1 : 上百G, 逻辑分代,物理不分代.
  ZGC / Shenandoah:4T 逻辑和物理都不分代.
  Epsilon:

调优参数

HotSpot参数分类:
标准: -开头,所有的HotSpot都支持
非标准:-X 开头,特定版本HotSpot支持特定命令
不稳定: -XX 开头。下个版本可能取消
java -XX:+PrintCommandLineFlags -version //打印JVM的默认参数
java -XX:+PrintFlagsFinal //打印支持的参数选项
-Xms2G 初始堆大小. 默认为物理内存的1/64
-Xmx3G 最大堆大小。默认为物理内存的1/4
-XX:NewSie=200M 设置年轻代初始大小
-XX:MaxNewSize=800M 设置年轻代最大值
-XX:MinHeapFreeRatio=40 默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.
-XX:MaxHeapFreeRatio=70 默认空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-XX:PermSize=200M 设值永久代的初始大小
-XX:PermMaxSize=300M 设值永久代的最大值
-XX:SurvivorRation=8 设值eden区和suvivor区的比例
-XX:MaxDirectMemorySize=3G设值直接缓存区的大小
-XX:PretenureSizeThreshold 参数 令大于这个设置值得对象直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制。
-XX:MaxTenuringThreshold=0 垃圾最大年龄.
如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.
如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻 代即被回收的概率。该参数只有在串行GC时才有效.
-XX:+UseParNewGC //开启Parrale New 垃圾回收器
-XX:ParallelGCThreads=4 //垃圾回收的线程数
[CMS参数]:
-XX:+DisableExplicitGC //禁止手动调用System.gc()
-XX+CMSSCaverangeBeforeRemark 在重新标记之前来一次young gc (并发标记期间用户线程会继续运行,到remark的时候,eden区很可能已经有大量对象了)
-XX:+UseCMSCompactAtFullCollection //在完成垃圾收集后进行一次内存碎片整理
-XX:CMSFullGCsBeforeCompaction=9//在进行若干次垃圾收集后再启动一次内存碎片整理
-XX:+UseCMSInitiatingOccupancyOnly//只用设定的回收阈值,如果不指定,JVM仅在第一次使用设定值,后续则自动调整
-XX:CMSInitiatingOccupancyFraction=70//在老年代空间被使用多少后触发垃圾收集-默认为92
-XX:+ScavengeBeforeFullGC//在old gc之前触发一次young gc
-XX:+CMSParallelRemarkEnabled //开启并行remark
** [G1GC参数]
G1 中依然有分代管理的思想,主要采用分块管理的思想,通过将内存分为大小在 1M-32M 之间的一个个块, Eden、Survivor space 和 年老代 都是一系列不连续的逻辑区域。
-XX:+UseG1GC //开启G1GC
-XX:MaxGCPauseMillis=200 //GC 停顿时间
-XX:G1NewSizePercent=5 年轻代在堆中最小百分比,默认值是 5%
-XX:G1MaxNewSizePercent=60 年轻代在堆中的最大百分比,默认值是 60%
-XX:ParallelGCThreads=n 设置并行垃圾回收线程数值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多为 8。如果逻辑处理器不止八个,则将 n 的值设置为逻辑处理器数的 5/8 左右。这适用于大多数情况,除非是较大的 SPARC 系统,其中 n 的值可以是逻辑处理器数的 5/16 左右。
-XX:ConcGCThreads=n 设置并行标记线程的数量.将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。
-XX:InitiatingHeapOccupancyPercent=45 标记垃圾阀值, 默认45%
-XX:G1ReservePercent=10 设置空闲空间的预留内存百分比,以降低目标空间溢出的风险。默认值是 10%。
-XX:G1HeapRegionSize=n G1 的区域块大小,1M-32M 之间,必须是2的幂,G1 会根据块大小规划出不大于 2048 个块。
== 避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。==

[日志参数]:
-Xloggc:/var/log/processname-date +%F_%H-%M-%S-gc.log -XX:UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=20M
-XX:+PrintGCDetails //开启GC日志打印
-XX:+PrintGCTimeStamps //从JVM启动到打印GC时刻的秒数
-XX:+PrintGCCause
-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:\Users\Administrator\Desktop
-XX:OnOutOfMemoryError=‘kill -9 %p’
-verbose:gc

实战

日志讲解:

	[GC2016-08-28T10:46:09.846+0800: 68434.688: [ParNew: 11384636K收集前新生代占用的大小->61763K收集后新生代占用的大小(11953792K新生代的大小), 0.0716150 secs GC的时间] 12849103K收集前整个堆占用的大小->1528727K收集前整个堆占用的大小(14050944K整个堆的大小), 0.0719060 secs] [Times: user=0.28 sys=0.00, real=0.07 secs]
	
	[GC (CMS Initial Mark) [1 CMS-initial-mark: 19498K老年代被占用的大小(32768K老年代的总大小)] 36184K收集前被占用的大小(62272K整个堆的大小), 0.0018083 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
	[CMS-concurrent-mark-start]
	[CMS-concurrent-mark: 0.011/0.011 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
	[CMS-concurrent-preclean-start]
	
	虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)
	[CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
	[CMS-concurrent-abortable-preclean-start]
	
	增加这个阶段是为了避免在重新标记阶段后紧跟着发生一次垃圾清除
	 CMS: abort preclean due to time [CMS-concurrent-abortable-preclean: 0.558/5.093 secs] [Times: user=0.57 sys=0.00, real=5.09 secs]
	[GC (CMS Final Remark) [YG occupancy: 16817 K (29504 K)][Rescan (parallel) , 0.0021918 secs][weak refs processing, 0.0000245 secs][class unloading, 0.0044098 secs][scrub symbol table, 0.0029752 secs][scrub string table, 0.0006820 secs][1 CMS-remark: 19498K(32768K)] 36316K(62272K), 0.0104997 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
	
	这个阶段会标记老年代全部的存活对象,包括那些在并发标记阶段更改的或者新创建的引用对象
	[CMS-concurrent-sweep-start]
	[CMS-concurrent-sweep: 0.007/0.007 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
	[CMS-concurrent-reset-start]
	[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
-Xms14g -Xmx14g -Xss512k -XX:PermSize=384m -XX:MaxPermSize=384m -XX:NewSize=12g -XX:MaxNewSize=12g -XX:SurvivorRatio=18 -XX:MaxDirectMemorySize=2g -XX:+UseParNewGC -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=15  -XX:+CMSScavengeBeforeRemark -XX:+UseConcMarkSweepGC -XX:+DisableExplicitGC -XX:+UseCMSInitiatingOccupancyOnly -XX:+ScavengeBeforeFullGC -XX:+UseCMSCompactAtFullCollection -XX:+CMSParallelRemarkEnabled -XX:CMSFullGCsBeforeCompaction=9 -XX:CMSInitiatingOccupancyFraction=70
-Xms13g -Xmx13g -Xss512k -XX:MetaspaceSize=384m -XX:MaxMetaspaceSize=384m -XX:NewSize=11g -XX:MaxNewSize=11g -XX:SurvivorRatio=18 -XX:MaxDirectMemorySize=2g -XX:+UseParNewGC -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=15 -XX:+UseConcMarkSweepGC -XX:+DisableExplicitGC -XX:+UseCMSInitiatingOccupancyOnly -XX:+ScavengeBeforeFullGC -XX:+CMSScavengeBeforeRemark -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSClassUnloadingEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:-ReduceInitialCardMarks -XX:+CMSClassUnloadingEnabled -XX:+ExplicitGCInvokesConcurrent -XX:+PrintGCDetails
       -XX:+PrintGCDateStamps -XX:+PrintGCApplicationConcurrentTime -XX:+PrintHeapAtGC -Xloggc:/data/applogs/heap_trace.txt -XX:-HeapDumpOnOutOfMemoryError          -XX:HeapDumpPath=/data/applogs/HeapDumpOnOutOfMemoryError"
    MinHeapFreeRatio         = 0 //-XX:MinHeapFreeRatio=40 GC后java堆中空闲量占的最小比例,小于该值,则堆内存会增加
    MaxHeapFreeRatio         = 100  //XX:MaxHeapFreeRatio=70  GC后java堆中空闲量占的最大比例,大于该值,则堆内存会减少 
    MaxHeapSize              = 3221225472 (3072.0MB)
    NewSize                  = 715653120 (682.5MB)
    MaxNewSize               = 1073741824 (1024.0MB)
    OldSize                  = 1431830528 (1365.5MB)
    NewRatio                 = 2
    SurvivorRatio            = 8
    MetaspaceSize            = 21807104 (20.796875MB)
    CompressedClassSpaceSize = 1073741824 (1024.0MB)
    MaxMetaspaceSize         = 17592186044415 MB
    G1HeapRegionSize         = 0 (0.0MB)	

垃圾回收算法

常见问题

对象在内存中的布局

在这里插入图片描述

对象的创建过程

class T {
int num = 10;
}
T t = new T();
首先在常量池中查找该对象是否已经存在,如果存在则将其地址赋值给 t;
如果没有则:

  1. 在堆中分配内存;
  2. 调用构造函数初始化对象;
  3. 将在堆中创建的对象的地址赋值给局部引用变量 t。
    在这里插入图片描述astore_1 :从JVM操作数栈中弹出引用类型或returnAddress类型值,然后将该值存入有索引值1指定的局部变量中。

Object o = new ojbect 总共多少字节

20字节:
一个Object由: 16 字节
Mark Word : hashcode(31) + 分代年龄(4) + 偏向锁位(1) + 锁标志位(2) + unused(26) = 8 字节
Class pointer: 4 字节(开启了 -XX:+UseCompressedClassPointers)
实例数据 : 0 .// OOP Oridinary Object Pointer 普通对象指针:指的是引用类型的成员变量
对齐 : 4 字节
Object o在栈中分配的引用占4字节
所以总共20字节
== 为什么对象的年龄的最大值是15: 分代年龄只占 4 bit。==

volatile

	1) 可见性
	
	2) 指令重排序

DCL(double check lock)单例背后的秘密

在这里插入图片描述1 需要加锁并且需要 double check
创建对象是非原子操作,所以需要加锁。获取锁成功后需要再次判断是否为空,因为此时对象可能被其他线程创建。
2: 需要加 volatile关键字修饰单例对象
由对象的创建过程可以知道创建一个对象需要执行多个操作。
假如多个线程同时来获取单例对象,则有可能出现如下几种情况:
A) thread 1拿到锁进入new 分配内存阶段,此时thread 2 判断INSTANCE 为空进入等待锁阶段。thread 1 完成对象创建并释放锁,接着thread 2 获得锁,如果此时INSTANCE没有用 volatile修饰的话,thread 2 仍然可能得到 INSTANCE 为 null(因为 thread 2没有重新从内存中读取INSTANCE的值而是读取寄存器中的值)从再次创建INSTANCE。
B)thread 1拿到锁 并完成内存分配,如果INSTANCE不是volatile的话,有可能发生指令重排序,造成此时INSATNCE指向的是一个未完成初始化的对象。 要是此时刚好有 thread 2获取单例的话,它的的是 INSATNCE != null,直接拿这个未初始化的对象来进行操作。
在这里插入图片描述

引用类型

软引用:非常适合缓存使用。
如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联 合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

SoftReference<byte[]> m = new SoftReference<>(new byte[10 * 1024 * 1024]);
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(500L);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        System.out.println(m.get());
        //再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,则会把软引用干掉
        byte[] b = new byte[11 * 1024 * 1024];
        System.out.println(m.get()); //null

弱引用: WeakReference
和软引用类似,却别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
class M { @Override protected void finalize() throws Throwable { System.out.println("M.finalize"); super.finalize(); } } WeakReference<M> m = new WeakReference<>(new M()); System.out.println(m.get()); System.gc(); System.out.println(m.get()); //null and print M.finalize
net.xdclass.TestClass$M@60e53b93
null
M.finalize

虚引用:PhantomReference 主要用于对外内存。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

锁的状态及升级过程

无锁 -> 偏向锁 -> 轻量级锁 ->重量级锁

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值