java virtual machine -java程序的运行环境(java二进制的字节码的运行环境)
- 一次编写,到处运行
- 自动内存管理,垃圾回收功能
- 数组下边越界检查
- 多态
Program Counter Register(程序计数器)
Java Virtual Machine Stacks(java虚拟机栈)
问题辨析
不会,占内存只是一次次的方法调用产生的栈祯内存,而栈祯内存在方法调用结束后会被弹出栈(会被自动的回收掉),垃圾回收只会回收堆内存中的无用对象,占内存不需要进行垃圾回收的处理
栈内存并不是越大越好的,例如虚拟内存有500M,我们每一个栈内存设置为1M,可以运行500个五百个线程,每个栈内存设置2M,则只能运行250个线程,所以栈内存设置的过大会导致运行线程的数量减少,栈内存设置的比较大只能可以进行更多次的方法递归调用,所以栈内存使用默认值就好。
- 如果方法内局部变量没有逃离方法的作用访问,他就是线程安全的
- 如果是局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全
什么情况下导致占内存溢出
定位:在Linux系统下
定位:Linux系统下
例如死锁
Native Method Stack
- java虚拟机调用本地方法的时候需要给本地方法提供的一个内存空间
- 本地方法是指java调用C++或C语言写的程序来与系统进行交互(因为java是无法直接与系统进行交互的,所以要借助一些C++和C语言编写的代码,这些用C++和C语言编写的代码被称为本地方法)
Heap堆
特点
堆内存溢出抛的异常:java.lang.OutOfMemoryError:Java heap space
-Xmx 内存大小:可以设置对内存的大小
jps工具
jmap工具
jconsole工具
jvisualvm
案例
说明占用内存的对象仍然在被使用
- 运行以下代码
public class demo01{ public static void main(String[] args) throws InterruptedException{ List<Student> students = new ArrayList<Student>(); for (int i = 0;i < 200; i++){ students.add(new Student()); } Thread.sleep(1000000000L); } } class Student{ private byte[] big = new byte[1024*1024]; }
- 在控制台输入jvisualvm,启动工具
- 双击要查看的进程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x8IfemFl-1583486219286)(D:\桌面\JVM.assets\1583201021827.png)]
- 进入后点击监视
- 点击堆dump
- 点击左侧的查找
- 列出来了前二十个
- 我们发现ArrayList占用了200多兆内存
- 我们点击ArrayList
- 查看里面的属性,找出占用内存的地方
* 演示永久代内存溢出 java.lang.OutOfMemoryError:PermGen space
* -XX:MaxPermSize=8m //设置永久代大小
* 演示元空间内存溢出 java.lang.OutOfMemoryError:Metaspace
* -XX:MaxMetaspaceSize=8m //设置元空间大小
场景
动态生成的类过多导致方法区内存溢出
使用java提供的javap命令可以将.class文件进行简单的反编译,加上-v参数,是显示详细信息
package com.lld.demo;
/**
* @ClassName demo03
* @Description TODO
* @Author LLD
* @Date 2020/3/3 11:27
* @Version 1.0
*/
//二进制字节码中包含了:类的基本信息,常量池,类方法的定义,包含了虚拟机的指令
public class demo03 {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
在运行时常量池中,会将#2,#3这种字符转换成真实的在内存中的地址
package com.lld.demo;
/**
* @ClassName demo06
* @Description TODO
* @Author LLD
* @Date 2020/3/3 13:27
* @Version 1.0
*/
public class demo06 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
}
}
分析下面代码中String s4 = s1+s2;的工作流程
判断s3与s4是否是同一个对象
package com.lld.demo;
/**
* @ClassName demo06
* @Description TODO
* @Author LLD
* @Date 2020/3/3 13:27
* @Version 1.0
*/
public class demo06 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
//等价于new StringBuilder.append("a").append("b").toString 等价于new String("ab")
String s4 = s1 + s2;
}
}
分析下面代码中String s4 = “a” + “b”;的工作流程
判断s3和s4是否是同一个对象
package com.lld.demo;
/**
* @ClassName demo06
* @Description TODO
* @Author LLD
* @Date 2020/3/3 13:27
* @Version 1.0
*/
public class demo07 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = "a" + "b";//javac在编译期间进行了优化,已经确定a和b是一个常量,将他们进行拼接不会出现别的变化,所以还是ab
}
}
常量池中的字符串仅是符号,第一次用到时才变成为对象
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是StringBuilder(1.8)
字符串常量拼接的原理是编译期优化
可以使用intern方法,主动将串池中还没有的字符串放入串池
package com.lld.demo;
/**
* @ClassName demo05
* @Description TODO
* @Author LLD
* @Date 2020/3/3 13:06
* @Version 1.0
*/
public class demo05 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
//问
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
System.out.println("-----------------------------------");
String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();
//问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2);
}
}
package com.lld;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName demo
* @Description TODO
* @Author LLD
* @Date 2020/3/4 14:25
* @Version 1.0
*在jdk1.6下设置:-XX:MaxPermSize=10m//设置永久代的内存大小只有10m
* 在jdk1.8下设置:-Xmx10m//设置堆空间的内存大小只有10m
* -XX:-UseGcOverheadLimit:前面用-UseGCOver..,表示关闭UseGcOverheadLimit(为了测试,具体作用不知)
*/
public class demo {
public static void main(String[] args) {
//创建一个list集合来存储字符串
List<String> list = new ArrayList<String>();
int i = 0;
for (int j = 0; j< 2600000;j++){
//String.valueOf(j):把基本数据类型转换成字符串,intern()放入串池
list.add(String.valueOf(j).intern());
i++;
}
System.out.println(i);
}
}
垃圾回收只会在内存紧张时触发,内存宽裕时不会触发
package com.lld;
/**
* @ClassName demo1
* @Description TODO
* @Author LLD
* @Date 2020/3/4 14:57
* @Version 1.0
* 演示StringTable的垃圾回收
* -Xmx10m:设置堆内存大小
* -XX:+PrintStringTableStatistics:打印字符串表的统计信息(通过其可以看到串池中字符串的个数以及字符串大小等信息)
* -XX:+PrintGCDetails -verbose:gc:打印垃圾回收的详细信息(如果发生了垃圾回收,就把垃圾回收的次数以及详细时间打印出来)
* -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
*/
public class demo1 {
public static void main(String[] args) {
}
}
package com.lld;
/**
* @ClassName demo1
* @Description TODO
* @Author LLD
* @Date 2020/3/4 14:57
* @Version 1.0
* 演示StringTable的垃圾回收
* -Xmx10m:设置堆内存大小
* -XX:+PrintStringTableStatistics:打印字符串表的统计信息(通过其可以看到串池中字符串的个数以及字符串大小等信息)
* -XX:+PrintGCDetails -verbose:gc:打印垃圾回收的详细信息(如果发生了垃圾回收,就把垃圾回收的次数以及详细时间打印出来)
* -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
*/
public class demo1 {
public static void main(String[] args) {
for (int i = 0;i < 100000; i++){
String.valueOf(i).intern();
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ivf4Qrcj-1583486219292)(D:\桌面\JVM.assets\1583307219373.png)]
StringTable的底层是一个hash表,hash表的性能跟他的大小密切相关,如果hash表的空间比较大,桶的个数就比较分散,hash碰撞的几率就会变少,查找的速度就会变快,反之,桶的个数就比较少,hash碰撞的几率就会增高,导致链表长度较长,查找的速度就会收到影响
设置一个较小的值
测试设置StringTable的最大和最小值
设置一个较大的值测试
用到的四个参数解释
代码
package com.lld;
/**
* @ClassName demo02
* @Description TODO
* @Author LLD
* @Date 2020/3/4 23:22
* @Version 1.0
*/
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 演示串池大小对性能的影响
* -Xms500m:为jvm启动时分配的内存,比如-Xms200m,表示分配200M
* -Xmx500m:为jvm运行过程中分配的最大内存,比如-Xms500m,表示jvm进程最多只能够占用500M内存
* -XX:+PrintStringTableStatistics:在JVM进程退出时,打印出StringTable的统计信息输出到gclog中.
* -XX:StringTableSize=1009:配置字符串常量池中的StringTable大小,默认:60013 (Number of buckets in the interned String table) ,StringTable数据结构是hashtable,这个值就是hashtable的size大小,建议设置成大一点的质数
*/
public class demo02 {
public static void main(String[] args) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "utf-8"))) {
String line = null;
long start = System.nanoTime();
while (true) {
line = reader.readLine();
if (line == null) {
break;
}
line.intern();
}
System.out.println("cost:" + (System.nanoTime() - start) / 1000000);
}
}
}
如果字符串存在大量重复,可以将字符串对象入池,以减少重复,减少内存的使用
不属于java虚拟机的内存,属于系统内存
Direct Memory
案例
package com.lld;
/**
* @ClassName demo03
* @Description TODO
* @Author LLD
* @Date 2020/3/5 9:48
* @Version 1.0
*/
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 演示 ByteBuffer 作用
*/
public class demo03 {
static final String FROM = "G:\\study video\\黑马最新49期视频\\30-SSM分布式案例-互联网商城-阶段项目-必看(共197集)\\11.单点登录系统实现&cookie跨域问题详解\\09.登录的表现层的开发__rec.avi";
static final String TO = "G:\\a.mp4";
static final int _1Mb = 1024 * 1024;
public static void main(String[] args) {
io(); // io 用时:1535.586957 1766.963399 1359.240226
directBuffer(); // directBuffer 用时:479.295165 702.291454 562.56592
}
private static void directBuffer() {
long start = System.nanoTime();
try (FileChannel from = new FileInputStream(FROM).getChannel();
FileChannel to = new FileOutputStream(TO).getChannel();
) {
ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
while (true) {
int len = from.read(bb);
if (len == -1) {
break;
}
bb.flip();
to.write(bb);
bb.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
}
private static void io() {
long start = System.nanoTime();
try (FileInputStream from = new FileInputStream(FROM);
FileOutputStream to = new FileOutputStream(TO);
) {
byte[] buf = new byte[_1Mb];
while (true) {
int len = from.read(buf);
if (len == -1) {
break;
}
to.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("io 用时:" + (end - start) / 1000_000.0);
}
}
代码
package com.lld;
/**
* @ClassName demo04
* @Description TODO
* @Author LLD
* @Date 2020/3/5 10:00
* @Version 1.0
*/
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* 演示直接内存溢出
*/
public class demo04 {
static int _100Mb = 1024 * 1024 * 100;
public static void main(String[] args) {
List<ByteBuffer> list = new ArrayList<>();
int i = 0;
try {
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
list.add(byteBuffer);
i++;
}
} finally {
System.out.println(i);
}
// 方法区是jvm规范, jdk6 中对方法区的实现称为永久代
// jdk8 对方法区的实现称为元空间
}
}
当一个对象被引用一次,引用计数就加一,被引用两次,引用计数就为二,当一个变量停止引用,引用计数就减一,当引用计数为零时,表明该对象没有被引用了,就可以将该对象当一个垃圾进行回收
据说Python早期就是用引用计数法
根对象:表示不可能被回收的对象
在垃圾回收时首先对堆内存进行扫描,被根对象直接或者间接引用的对象不能进行回收,反之,则进行回收
回收过程
那些对象可以作为GC Root对象?
通过MAT工具
栈祯内局部变量所引用的对象都可以作为根对象
强引用
软引用(实际使用看P54的视频)
仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用
对象
可以配合引用队列来释放软引用自身
软引用的使用方法
List<SoftReference<Byte[]>> list = new ArrayList<SoftReference<Byte[]>>();
//list --强引用--> SoftReference --弱引用--> Byte
* 演示软引用
* -Xmx20m :-设置堆内存大小
* -XX:+PrintGCDetails :–打印GC详细信息
* -verbose:gc : -在控制台输出GC情况
因为有了引用队列,当软引用指向的对象被回收后,该软引用就会进入引用队列,在下面我们将进入引用队列的数据在list集合中进行了移除
弱引用
List<WeakReference<Byte[]>> list = new ArrayList<WeakReference<Byte[]>>();
// List --强引用--> WeakReference --弱引用-->Byte
虚引用
终结器引用
Mark Sweep
优点:速度快
缺点:容易产生内存碎片,当对象被释放后,不会重新对内存进行整理,导致一个较大的对象存储不下,但是实际内存空间是足够的
图解
Mark Compact
优点:解决了标记清除造成内存碎片的缺点
缺点:速度较慢,对象需要移动,对象移动后,内存地址发生改变,引用该对象的一些变量的值也相应的需要改变
图解
Copy
优点:没有内存碎片产生
缺点:占用双倍内存空间
图解
理解
工作流程
含义 | 参数 |
---|---|
堆初始大小 | -Xms |
堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
新生代大小 | -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) |
幸存区比例(动态) | -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
幸存区比例 | -XX:SurvivorRatio=ratio |
晋升阈值 | -XX:MaxTenuringThreshold=threshold |
晋升详情 | -XX:+PrintTenuringDistribution |
GC详情 | -XX:+PrintGCDetails -verbose:gc |
FullGC 前 MinorGC | -XX:+ScavengeBeforeFullGC |
定义:Garbage First
2004 论文发布
2009 JDK 6u14 体验
2012 JDK 7u4 官方支持
2017 JDK 9 默认
适用场景
相关 JVM 参数
-XX:InitiatingHeapOccupancyPercent=percent(默认45%)
会对E,S,O进行全面回收
- XX:MaxGCPauseMillis=ms
为了达到回收时间短的目标,会选择性回收垃圾最多的老年代区域
SerialGC
ParallelGC
CMS
G1
并发收集失败(当收集垃圾的速度比不上新产生垃圾的速度时,算作并发收集失败,并发收集退化为串行)以后,才会进行Full GC
pre -write barrier(写屏障) + satb_mark_queue(队列)
XX:+UseStringDeduplication
String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
XX:+ClassUnloadingWithConcurrentMark 默认启用
并发标记必须在堆空间占满前完成,否则退化为 FullGC
JDK 9 之前需要使用 - XX:InitiatingHeapOccupancyPercent
JDK 9 可以动态调整
一项比较高级的技能