什么是JVM
jvm是java二进制文件(.class)的运行环境
好处
1.一次运行,处处运行(可移植性)
2.自动内存管理,垃圾回收功能
3.数据下标越界检查,会抛出异常,不会覆盖数组的其他元素
4.采用虚方法表进行多态,多态是指同一操作作用于不同的对象
比较
JVM的内部结构和执行过程
1.java文件首先被编译成二进程class文件。
2.class文件被类加载器classloader加载到jvm中
3.类都被放在jvm的方法区method Area中 ,产生都对象都被放在堆中heap
4.对象在调用方法时就会用到虚拟机栈jvm stacks ,程序计数器pc register和本地方法栈native method stacks
5.方法执行时,由执行引擎中的解析器interpreter逐行执行,把字节码文件解释成机器码,交给CPU执行;被频繁使用的代码会被即时编译器 JIT Conmpiler优化后执行
6.垃圾回收器会对堆里面不再被引用的对象进行回收,本地方法接口用于调用操作系统的方法与JVM联系。
学习路线 -------1.jvm内存结构--------2.jvm内存回收--------3.java文件变成class文件的优化-----4.类加载器----5.对热点代码进行编译优化的即使编译器
JVM内存结构
1.程序计数器 PC Register ——由CPU中的寄存器实现
程序计数器的作用
用来记录下一条jvm指令的地址,交给解释器编译成机器码。
阅读以下代码
JAVA源代码首先被编译成二进制字节码和jvm指令,而程序计数器会记住下一条jvm指令执行的地址,然后解释器从程序计数器中拿到执行的指令,并把二进制字节码释成机器码,CPU再对机器码进行执行和处理。
程序计数器的特点
(1).程序计数器是线程私有的,每一个线程都有自己单独的程序计数器。
线程私有的好处
CPU执行多个线程时采用时间片轮转,当线程1执行到某一个JVM指令时间消耗完毕时,程序计数器会记录下一个jvm指令的地址,此时会暂停线程1执行线程2.当线程2时间到达又执行线程1时,就会继续执行在线程1中的程序计数器中记录的jvm指令。
(2).程序计数器不会存在内存溢出
2.虚拟机栈 JVM Stacks——线程私有的内存空间
(1).什么是虚拟机栈
虚拟机栈是线程运行时需要的内存空间。一个线程对应一个栈,是线程私有的,每一个栈都是由多个栈帧元素组成,但只能由一个活动栈帧;
什么是栈帧
栈帧是每个方法运行时需要的内存空间,一个栈帧对应一次方法的调用,存储了操作数栈,局部变量表和方法返回值,动态连接。
当对象需要具体调用方法时,首先会创建一个栈帧,为方法开辟空间,然后存入栈帧中;然后把栈帧压入栈中,最后把栈放入到对应的线程之中交给cpu执行。
(2).常见问题
a.垃圾回收不会涉及栈内存,因为在方法调用结束后,虚拟机栈就会释放掉栈帧,方法的内存空间也就被释放了。
b.栈内存分配不是越大越好,当栈内存分配大时,会导致线程数变少,降低cpu线程并发的数目。
c.栈帧内的局部变量是线程安全的,但是方法参数和返回值不是线程安全的。因为每一个线程调用此方法时,都会创建新的栈帧,局部变量不会收到影响,初始值和结果一致,但是方法参数和返回值作为开头和结果可能会被其他线程影响。
如下:
方法m1中的stringbuilder是线程安全的,因为是局部变量;方法m2和方法m3中的stringbuilder不是线程安全的,因为可以被外部引用修改。
(3).虚拟机栈内存溢出-——java.long.StackOverflowError
-Xss可以设置虚拟机栈内存大小
a.栈帧过多,导致栈内存溢出;
如在方法递归调用中,没有设置正确的结束条件,
就会导致栈中的栈帧越来越多,且无法释放,此时栈内存就会溢出。
b.栈帧过大,导致栈内存溢出;
如一个栈帧的内存超过了栈的内存,导致内存溢出
(4).线程运行诊断
a.某一个线程导致cpu占用过多
1.linux中定位出占用过多的线程
top:
定位出进程那个进程对cpu占用过高
ps H -eo pid,tid,%cpu |grep 进程id(如 1234) :
定位出进程中那个线程对cpu占用过高
jstack 进程id:
JDK提供的查看每一个线程的执行情况,找出对应的cpu占用过高的线程和代码查看
2.window中直接打开任务管理器即可。
c.程序运行很长时间都没有结果
1.多个线程发生了死锁
jstack 进程id:
会提示出相应死锁的线程和具体的某一行代码。
3.本地方法栈——线程私有的内存空间
(1).什么是本地方法栈
本地方法栈是JVM在调用本地方法时本地方法所需要的内存空间,本地方法栈是线程私有的。
本地方法栈的作用
给本地方法提供内存,让JVM可以调用不是java代码编写的底层方法,实现一些底层的功能。
4.堆——线程共享的内存空间
(1).什么是堆
堆用于存储new关键字产生的对象
(2).堆的特点
a.堆是线程共享的,堆中的对象都需要考虑线程安全的问题
b.堆有垃圾回收机制
(3).堆内存溢出——java.lang.OutofMemoryError
-Xmx 设置堆空间的大小
当不断的产生新的对象,并且这些对象都不断的在使用,对象无法进行垃圾回收,机会导致堆内存溢出
堆内存溢出诊断工具
1.jps工具
jps
查看系统中有哪些进程
2.jmap工具
jmap -heap 进程ID
查看某一个进程某一个时刻的堆内存占用情况
3.jconsole工具
图形界面的检测工具,连续检测堆内存,cpu占用率,进程。
常见问题和解决办法
多次垃圾回收以后,内存占用率仍然很高
1.首先利用jps,jmap或者jconsole常看内存占用情况
2.使用jvisualvm的 堆dump 功能抓取堆的当前时刻的使用情况。
3.点击查找,选择查看内存最大的对象个数(默认20)
4.然后点击进入到需要查看的对象当中,就可以查看此对象占用内存的具体情况
5.方法区
二进制字节码(class文件):类基本信息,常量池,类定义方法,虚拟机指令。
方法区在虚拟机被启动的时候创建,用于存放二进制字节码(class文件)的相关信息。1.6之前由jvm管理,占用堆内存,常量池存放stringtable,1.8之后不由jvm管理,不占用堆内存,占用操作系统内存,常量池不存放stringtable。
(1).方法区内存溢出
-XX 设置方法去内存大小
加载的类太多,导致方法区空间存放不下。
1.6以前会导致 永久代 内存溢出——java.lang.OutOfMemoryError:PermGen space
1.8以后会导致 元空间 内存溢出——java.lang.OutOfMemoryError:Metaspace
(2).方法区特点
a.方法区是线程共享的
b.方法区存在垃圾自动回收机制
(3).运行时常量池
常量池就是一张表,jvm指令根据常量表找到执行的类名,方法名,参数类型,变量等信息。
运行时常量池是用来存放被加载的class文件的常量池,并把以前常量池的符号地址变成真实地址。
(4).StringTable(字符串常量池)
用于存放字符串,是hashtable结构,采用数据+链表的形式,不能扩容。
使用 javap 进行反编译,分析如下代码1
当demo1_22这个类被加载到方法区的时候,会把class的常量池信息加载到运行时常量池,此时string的对象还仅仅时符号,当加载到某一个string的对象才会变成真正对象,并把相应的数据存放到字符串常量池stringtable中。
加入如下代码2后分析:
String s4 = s1+s2 ;
首先会创建一个StringBuilder的对象,调用StringBuilder的 init()方法,初始化StringBuilder,然后调用加载s1,调用StringBuilder的append方法,把s1放入到StringBuilder中,s2同s1一样,最后调用toString方法,此方法会new一个新的字符串,把结果存入到s4的变量中,因为是new出来的,所以把s4变量放入堆里面。
s3在字符串常量池中,s4在堆中,所以不是一个对象。
加入如下代码3后分析:
String s5 = "a"+"b";
首先javac会在编译期间对s5进行优化,直接得到结果“ab”,所以,从第29条指令和30条指令可以发现,s5直接去字符串常量池中找到了 字符串ab ,然后把值给s5。
s5和s3都是同一个地址和对象,因为他们都指向了字符串常量池中的ab。
添加以下代码
s4.intern();
JDK1.8
作用:如果字符串常量池中不存在s4的字符串对象,就把字符串对象s4放入串池中,
此时,s4变成了串池中的对象。
如果存在就不进行放入,并且会把串池中存在的对象返回。
s4.intern();
JDK1.6
作用:如果字符串常量池中不存在s4的字符串对象,就把字符串对象s4复制一份放入串池中,
此时,s4还是堆中的对象,复制的才是串池中的对象。
如果存在就不进行放入,并且会把串池中存在的对象返回。
JDK1.8
可以看到,s6对象不等于s4对象而等于s3对象,说明了s6对象是串池中s3对象返回的,s4对象是堆中的。
在s4之前,串池中已经有了“ab”对象,所以调用 intern() 方法,s4也不会被放入到串池中,所以s4不会等于s6和s3。
总结:
常量池中得字符串仅仅是符号,第一次使用时才会变成对象
利用字符串常量池,可以避免重复创建字符串对象,节约空间和性能
字符串 变量 拼接是利用了Stringbuilder,常量 拼接会在编译器优化
可以使用 intern 方法 ,主动将堆中的字符串,放入到字符串常量池中,前提是串池中没有这个对象。
StringTable的位置
1.6之前StringTable在永久代中,存放在常量池里
1.7开始StringTable放在堆中。
StringTable的垃圾回收机制
当StringTable的内存紧张的时候,就会促发垃圾回收机制,回收最长时间没有使用的字符串常量。
StringTable性能调优
String