JVM原理
JVM:JAVA二进制字节码的运行环境
优点:
1.一次编写到处运行
2.自动内存管理,垃圾自动回收
基本概念
程序计数器:Java程序变为JVM指令后,记录下一次执行指令
栈帧:每个方法运行时需要的内存
stackoverflowError:栈溢出
outofmemoryError :Java heap space:堆溢出
outofmemoryError : PermGen space:永久代溢出
JVM与JDK的关系
常见JVM
程序寄存器(PC)
1.程序寄存器使用的是CPU的寄存器
2.用来记录下次执行的jvm指令的地址
3.线程私有
4.JVM中唯一不会内存溢出的区域
栈(stack)
栈:线程运行时所需要的内存
栈帧:每个方法运行时需要的内存
活动栈帧:当前正在执行的方法所占用的内存
指定栈的大小: -Xss size linux默认1024KB
线程栈溢出诊断(linux环境下)
jstack 进程号
堆(Heap)
通过new关键字创建的对象使用堆内存
特点:
1.线程共享
2.有垃圾回收回收机制
堆指定大小: -Xmx size
堆内存诊断
jsp和jmap
1.jps:查询进程id
2.jmap -heap 进程id:查看运行时一瞬间堆内存状况
F:\projecttest\springbootdome2>jps
10784 Launcher
1872
10868 UserController
10536
7196 Launcher
9756 Jps
F:\projecttest\springbootdome2>jmap -heap 10868
Attaching to process ID 10868, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2122317824 (2024.0MB)
NewSize = 44564480 (42.5MB)
MaxNewSize = 707264512 (674.5MB)
OldSize = 89653248 (85.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 112721920 (107.5MB)
used = 81065136 (77.30973815917969MB)
free = 31656784 (30.190261840820312MB)
71.91603549691133% used
From Space:
capacity = 1572864 (1.5MB)
used = 65536 (0.0625MB)
free = 1507328 (1.4375MB)
4.166666666666667% used
To Space:
capacity = 1572864 (1.5MB)
used = 0 (0.0MB)
free = 1572864 (1.5MB)
0.0% used
PS Old Generation
capacity = 89653248 (85.5MB)
used = 773000 (0.7371902465820312MB)
free = 88880248 (84.76280975341797MB)
0.8622108147158261% used
1764 interned Strings occupying 158080 bytes.
jconsole图形化界面
F:\projecttest\springbootdome2>jconsole
jvisualvm
F:\projecttest\springbootdome2>jvisualvm
抓取当前快照
查询堆中内存占用排名前20的对象,然后点击“是”
查看占用堆内存最大的对象
找到占用内存最大的属性
MAT(Eclipse中使用)
jps 查看进程
jmap -dump:format=b,file=XXX.bin 进程id 生成dump文件file=生成的文件名
jprofiler (IDEA中使用)
下载插件启动:https://www.ej-technologies.com/download/jprofiler/files
双击“jprofiler_windows-x64_10_1_1.exe”程序安装
注册码(任选其一)
L-GXdJph7lkG-1CvkTwHlvk#741
L-KMGcqlMxTo-Fkd1ultFTt#3810
A-TgIjrDbn41-5w05ktrJFf#18128
A-3rk5EoAR9t-vdItuCJtVi#2898
S-DJMhaqMnvW-36Sb5jcGYO#31259
L-qOQRsFcEcF-LqVM1lqxQm#1437
A-3VIC9ISIit-SkVGccmWta#689
L-dpoWt86zUZ-tzJNLsXY75#4229
S-yYk9XkHyyY-3GVAtuBBkR#1104
S-QH9BAugD8L-EZ2KbOTiIL#31225
安装完成后再IDEA中添加启动文件
设置堆溢出时生成jprofiler文件
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
运行程序当出现OutOfMemoryError报错就会生成jprofiler文件
生成文件就在src的同级目录下
打开文件
方法区
1.6版本方法区概念上属于永久代是堆的一部分
1.8版本以后方法区属于元空间占用的是本地内存
常量池
常量池就是一张表,虚拟机指令根据这张表找到要执行的类名,方法名,参数类型等
反编译查看常量池中的内容
F:\projecttest\springbootdome2\target\classes\com\example\demo\controller>javap -v UserController.class
运行时常量池
当类被加载时,他的常量池信息就会被放到运行时常量池中,并且将符号地址变为真实地址
StringTable(串池)
java1.7以前串池是放在常量池中
Java1.7以后串池是放在堆内存中
StringTable垃圾回收
-Xmx10m 堆内存大小
-XX:+PrintStringTableStatistics 打印串池日志
-XX:+PrintGCDetails -verbose:gc 打印垃圾回收日志
public static void main(String[] args) {
try {
}catch (Throwable e){
e.printStackTrace();
}
}
向串池中加入100条数据
public static void main(String[] args) {
try {
for(int i=0;i<100;i++){
String.valueOf(i).intern();
}
}catch (Throwable e){
e.printStackTrace();
}
}
向串池中加入20000条数据
public static void main(String[] args) {
try {
for(int i=0;i<20000;i++){
String.valueOf(i).intern();
}
}catch (Throwable e){
e.printStackTrace();
}
}
垃圾回收日志多了一条
结论:StringTable会被垃圾回收
StringTable调优
设置StringTable的大小
-XX:StringTableSize=1009
测试一
使用IDEA查看串池中的数量
public static void main(String[] args) {
System.out.println("1"); //串池中的数量2503
System.out.println("2"); //串池中的数量2504
System.out.println("3"); //串池中的数量2505
System.out.println("1"); //串池中的数量2505
System.out.println("2"); //串池中的数量2505
System.out.println("3"); //串池中的数量2505
}
结论:串池中如果已经存在字符串,就会直接使用串池中的字符串,不会生成新的字符串
测试二
public static void main(String[] args) {
//在串池创建常量["a","b"]
//s=StringBuilder.append("a").append("a").toString();
//堆 new String("a"),new String("b"),new String("ab")
String s=new String("a")+new String("b");
//将new String("ab")放入串池中,如果“ab”不存在就会放入,如果存在就不会放入,返回串池中的对象s2
String s2 = s.intern();
//此时串池中的常量["a","b","ab"],所以"ab"不会再创建,x就是串池中已经存在的"ab"
String x="ab";
//true
System.out.println(s2==x);
//true
System.out.println(s==x);
}
public static void main(String[] args) {
String x="ab";
//串池中的常量["a","b","ab"]
//s=StringBuilder.append("a").append("a").toString();
//堆 new String("a"),new String("b"),new String("ab")
String s=new String("a")+new String("b");
//将new String("ab")放入串池中,如果“ab”不存在就会放入,如果存在就不会放入,返回串池中的对象s2
//此时串池中的常量["a","b","ab"],所以s不会被放入串池中
String s2 = s.intern();
//true
System.out.println(s2==x);
//false
System.out.println(s==x);
}
结论:串池中如果已经存在字符串,就会直接使用串池中的字符串,不会生成新的字符串
直接内存(DirectBuffer)
1.直接内存不属于JVM,属于系统内存
2.读写性能好,但是回收成本高
3.不受JVM管理
ByteBuffer.allocateDirect(int byte_length) :使用直接内存
原理
普通缓存:java不能直接访问系统内存,只能先将系统的缓存数据读到java的缓存区才可以使用
直接内存:java直接访问内存
垃圾回收
是否可以被回收
引用计数法
当一个对象被引用一次,计数器就加1,当某个变量不在引用就减1,当引用数为0时就可以被垃圾回收器回收
缺点:当两个对象互相引用就无法被垃圾回收器回收
可达性分析算法
如果被根对象直接或间接引用就不能被回收,如果没有被根对象直接或间接引用就可以被回收
四种引用
垃圾回收算法
标记清除算法
优点:速度快
缺点:空间不连续,产生内存碎片
标记整理算法
优点:节省内存
缺点:整理内存会消耗资源,速度慢
复制算法
将内存划分from和to两块区域
标记没有引用的对象
将存在引用的对象复制到to区,然后删除from区标记的对象
将to区变为from区,将from区变为to区
优点:不会产生碎片
缺点:占用双倍空间
分代回收
大对象直接存入老年代
垃圾回收器
串行(SerialGC)
吞吐量优先(ParallelGC)
响应时间优先(CMS)
Garbage First(G1)
jdk9默认垃圾回收器
jdk8使用G1需要添加启动参数:-XX:+UseG1GC
特点:
1.年轻代、老年代是独立且连续的内存块;
2.年轻代收集使用单eden、双survivor进行复制算法;
3.老年代收集必须扫描整个老年代区域;
4.都是以尽可能少而块地执行GC为设计原则。
回收过程
向伊甸园区存入数据
新生代垃圾回收后
幸存区对象标记数量达到16次后
当老年代的内存占堆内存的45%初始标记
混合收集