深入理解jvm虚拟机(笔记)

jvm内存模型:

 

1:方法区:存储常量池,静态变量,类型信息

 

2:堆区:存放对象实力,句柄(包括类的成员变量的引用与实力)

 

3:虚拟机栈:每个线程都有独有的虚拟机栈,执行每个方法都会创建栈帧,执行方法压栈榨,执行完毕出栈,存放局部变量,方法参数,返回信息,对象引用。

 

4:本地方法栈:与虚拟机栈类似,存储c,c++相关的信息。

 

5:程序计数器:存放线程相关的字节码指令地址。

 

 

方法区与堆区 是线程共享的, 虚拟机栈,本地方法栈,程序计数器是线程独享的。

 

 

锁的类型:

1:偏向锁:线程独占的锁,默认不会存在其他线程与之争抢。

2:轻量级锁:锁竞争不激烈的情况。

3:重量级锁:锁竞争激烈。

自旋锁可对轻量级锁优化,短时间内高概率抢夺锁成功前提下,自旋锁使用有限循环来代替线程阻塞从而降低切换线程引发的开销,单核cpu下无意义。

 

 

内存溢出:

哪些内存区域会引发内存溢出?

1,堆区内存溢出:-Xms 8M 最小分配内存 -Xmx 20M 最大分配内存 -XX:+HeapDumpOnOutofMemoryError 可以在发生内存溢出是Dump出当前内存快照以便分析,触发场景:在循环内不停创建对象。

 

2,程序计数器不会引发内存溢出。

 

3,虚拟机栈和本地方法栈溢出:-Xss 设置虚拟机栈大小  -Xoss 设置本地方法栈大小  HotSpot虚拟机不区分虚拟机栈与本地方法栈,-Xoss实际无效

触发场景:递归调用方法,让虚拟机债深度不停增大,这个时候会引发StackOverFlowError 栈溢出,而要触发内存溢出,需要不停创建线程,耗尽虚拟机栈的内存极限。

 

4,方法区和运行时常量池溢出:-XX:PermSize —XX:MaxPermSize 限制方法区大小,间接限制常量池大小

触发场景:String.intern() 如果常量池的常量不存在,会在常量池中创建常量,循环调用这个方法创建不同常量,最后会引发方法区OutOfMemory,

同时方法区中类的类型信息也会造成溢出,例如使用cglib,动态代理等动态生成字节码的框架的情况,循环创建动态对象造成溢出。

 

 

5,本地直接内存溢出:-XX:MaxDirectMemorySize 通过这个指定,如果不指定,默认和堆一样。

触发场景:可以使用java提供的Unsafe,不过需要通过反射获取,然后调用allocateMemory方法,不停分配内存,造成内存溢出。这种溢出在HeapDump文件中是看不到的。

 

 

垃圾回收与内存分配策略

c,c++使用引用计数器来判断内存是否可以回收,而java中由于对象相互引用的存在,这种方式就变得不可行,所以引入了GC root方式,垃圾回收期判断对象是否可达来进行回收操作,一个对象到GC Root没有任何引用链,则就可以被回收。

能成为GC Root的 :成员变量引用的对象,方法区中静态与常量引用的对象,本地方法栈中JNI引用的对象。

引用类型:强引用:永远无法被回收,软引用:没有足够内存,引发内存溢出前被回收,弱引用:随时可能被回收,虚引用:无法通过虚引用来获得实例,只是在实例被回收的时候会发一个通知,其实是用来监测对象被回收的。

 

垃圾回收算法过程:

当一个对象需要被回收时,会被经过一次标记,如果对象没有复写finalize()方法,或者已经执行过finalize()方法,则被清除。

finalize()是对象起死回生的一个关键点,但是没什么卵用。

java堆区内存被划分为年轻代与老年代,年轻代又分为eden 和 两个小的survivor区 默认8:1:1

1,标记-清除算法:这种算法会把不可达对象作标记,然后清除对象,这样会造成内存碎片化。

2,复制算法:年轻代中eden和其中一个survivor区域被使用,垃圾回收期把还存活的对象复制进入另一个survivor区域中,然后把eden和另一个survivor区域清除,这种算法在还有大量对象存货的情况下比较耗资源,同时如果存活的对象比较多,那survivor空间不足,放不下的对象会存放进老年代中。

3,标记-整理算法:这种算法是将存货对象指针移动整理至内存开始区域,然后清除区域外的内存空间,适合老年代内存。

4,分代收集算法:一般只有少量对象存活就使用复制算法,老年代中对象存活率高,没有额外空间担保,所以采用标记-清楚,标记-整理算法。

 

安全点:

虚拟机需要记录GC root,一般而言类加载完成就会得到一个oopMap,这个oopMap存放着GC root,但是在运行时必须要找到安全点 才能动态去查询GCroot,一般安全点会在方法调用,循环跳转,异常跳转的时候。

对于多线程,有两种方式,抢占式中断和主动式中断,抢占式中断是中断所有线程,发现其中线程没有进入安全点的时候就恢复线程让其跑到安全点上, 不过几乎没有虚拟机采用这种方式,一般会采用主动式中断。GC在需要中断线程的时候,会设立一个标志,每个线程主动轮询这个标志,发现中断标志就中断挂起,轮询标志的地方和安全点是重合的。

设立安全点看似很完美,但还有情况是当程序不执行,线程休眠的时候(cpu没有分配时间片),这种情况就需要安全区域来解决。

安全区域是指线程执行到区域中的任何地方开始GC都是安全的,线程进入安全区域的时候,首先标识自己已经进入了安全区域,当这个时间里jvm发起gc时候就不需要管进入安全区域的线程了。线程要离开安全区域时,要检查系统是否已经完成了gcroot 枚举,如果完成则继续执行,否则等待直到收到可以安全离开安全区域的信号为止。

 

垃圾回收器:

 

serial收集器:这种收集器是单线程收集器,进行垃圾收集的时候会中断工作线程。

 

parallel收集器:相对serial收集器来说,是多线程的实现。

 

cms(concurrent Mark Sweep)收集器:老年代并行收集器,可以让收集与用户线程同时工作,重视GC时间短,适合服务器,响应速度快,停顿时间短。

 

parallel Scavenge收集器:吞吐量优先收集器。如果设置吞吐小,那gc频繁。可以自适应调节的收集器。

 

serial old 收集器:是serial收集器的老年代版本,使用标记-整理算法,是cms收集器的后备方案,与parallel scavenge收集器配合使用。

 

parallel Old 收集器:是parallel scavenge收集器的老年代版本,使用多线程和标记-整理算法,也是注重吞吐量。

 

g1收集器:并行并发,分代收集,空间整合,可预测的停顿,将java堆划分成多个大小相等的独立区域,还保留新生代和老年代概念,但是不存在物理上的隔离, g1中新生代,老年代都是独立的区域。

g1收集分为四个部分:初始标记—主要标记与GCroot直接链接的对象, 并发标记—从gcroot开始对堆中对象进行可达分析,最终标记:修正用户线程产生的变动,需要中断线程,筛选回收—对区域中回收价值与成本排序,根据用户期望的GC停顿时间制定回收计划。

 

分析jvm虚拟机性能工具:

 

jps 查看java所有进程以及进程id   -v显示虚拟机启动时的参数  -m显示传递给主类main()的参数  -l显示主类全名 -q只显示进程id

 

jstat 查询虚拟机进程的类装载,内存,垃圾收集,jit编译等运行数据, ( jstat -gc 进程号 250 20   分析gc堆状况), 文本显示,不够直观

 

jinfo 查看虚拟机各项参数,可对可调整的参数作出调整

 

jmap 对java堆内存转储成快照.

 

jhat 对java堆快照的分析工具,是一个微型的html服务,所以不太常用

 

jstack java堆栈的跟踪工具,生成线程快照,是一个顺时的状态数据,在java中可通过Thread的 getAllStackTraces获取所有线程的堆栈信息(主要用来分析线程长时间停留的原因)

 

hsdis是虚拟机jit编译代码的反汇编插件. 该插件可以让虚拟机通过jit即时编译生成的本地机器码反汇编成汇编代码来分析程序的执行逻辑。

 

jconsole,visualVm jdk中监控和分析的可视化工具. jconsole是jdk1.5时出现的,visualVm 是jdk1.6时出现的。

 

 

字节码详解:

每个字节码文件都是由8位字节构成,其中前4个字节是字节码文件的唯一标识,也称为魔数,固定是0xcofebabe 5,6两个字节代表小版本,7,8两个字节代表大版本,第九个字节代表常量池的容量。容量池大小从1开始计算,0号位预留来代表不引用任何常量的含义。

 

常量一般从字面理解,分为字面量和符号引用,包含字符串常量和final修饰的常量值等,而从编译原理区分,分为三类:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。

 

虚拟机类加载机制:双亲委派机制,实现方式不是通过集成,而是通过组合。

 

bootstrap classloader ——>extension classloader —> application classloader—>user classloader 

 

bootstrap classloader 加载  JAVA_HOME/lib 目录下的类库

 

extension classloader 加载 JAVA_HOME/lib/ext 目录下的类库

 

user classloader 加载 用户classpath下的类库

 

user classloader 加载类时会试图从父加载(application classloader)加载,父加载器加载不到再从extension class loader加载 ,extension classloader加载不到从bootstrap classloader加载  如果还加载不到才会使用user classloader去加载。

你可能感兴趣的:(java)