定义:
Program Counter Register 程序计数器(寄存器)
作用:
记住下一条jvm指令的执行地址
getstatic #20 // PrintStream out = System.out;
astore_1 // --
aload_1 // out.print1n(1);
iconst_1 // --
invokevirtual #26 // --
aload_1 // out.println(2);
iconst_2 // --
invokevirtual #26 // --
aload_1 // out.println(3);
iconst_3 // --
invokevirtual #26 // --
aload_1 // out.println(4);
iconst_4 // --
invokevirtual #26 // --
aload_1 // out.println(5);
iconst_5 // --
invokevirtual #26 // --
return
特点:
定义:
Java Virtual Machine Stacks (Java虚拟机栈)
问题辨析
1.垃圾回收是否涉及栈内存?
2.栈内存分配越大越好吗?
3.方法内的局部变量是否线程安全?
栈内存溢出
栈帧过多导致栈内存溢出
栈帧过大导致栈内存溢出
/*演示栈内存溢出java.lang.StackOverflowError*/
public class Demo1_1 {
private static int count;
public static void main(String[] args) {
try {
method1();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(count);
}
}
private static void method1(){
count++;
method1();
}
}
线程运行诊断
案例1:cpu占用过多
定位
案例2:程序运行很长时间没有结果
定义:
Heap 堆
特点:
堆内存溢出
import java.util.ArrayList;
import java.util.List;
/*
演示堆内存溢出java.lang.OutOfError:Java heap space
*/
public class Demo1_2 {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "hello";
while (true) {
list.add(a);
a=a+a;
i++;
} catch (Throwable e) {
e.printStackTrace();
System.out.println(i);
}
}
}
堆内存诊断
/*演示堆内存*/
public class Demo1_3 {
public static void main(String[] args) throws InterruptedException {
System.out.println("1...");
Thread.sleep(30000);
byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb
System.out.println("2...");
Thread.sleep(30000);
array = null;
System.gc();
System.out.println("3...");
Thread.sleep(1000000L);
}
}
案例
垃圾回收后,内存占用仍然很高
定义
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html
组成
方法区内存溢出
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
/*
*演示证元空间内存溢出
*-XX:MaxMetaspaceSize=8m
*/
public class Demo04 extends ClassLoader{//可以用来加载类的二进制字节码
public static void main(String[] args) {
int j = 0;
try {
Demo04 test = new Demo04();
for (int i = 8; i < 10000; i++ , j++) {
// ClassWriter 作用是生成类的二进制字节码
ClassWriter cw = new ClassWriter(0);
//版本号,public,类名,包名,父类,接口
cw.visit(Opcodes.V1_8,Opcodes.ACC_PUBLIC,"Class" +i,null,"java/lang/object",null);
//返回 byte[]
byte[] code = cw.toByteArray();
//执行了类的加载
test.defineClass("Class" + i,code,0,code.length);//Class 对象
}
}finally{
System.out.println(j);
}
}
}
1.8以前会导致永久代内存溢出
1.8之后会导致元空间内存溢出
场景
运行时常量池
StringTable
jvm-StringTable详解_爱搞技术的吴同学的博客-CSDN博客_java stringtablesize
先看几道面试题:
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); //false
System.out.println(s3 == s5); //true
System.out.println(s3 == s6); //true
String x2 = new String("c") + new String("d");//new String("cd")
String x1 = "cd"; //"cd"
x2.intern();
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2); //false
StringTable叫做字符串常量池,用于存放字符串常量,这样当我们使用相同的字符串对象时,就可以直接从StringTable中获取而不用重新创建对象。
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
// 在堆中创建字符串对象"ab"
// 将字符串对象"ab"的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true
StringTable 中保存的是字符串对象的引用,字符串对象的引用指向堆中的字符串对象。
StringTable特性
StringTable位置
StringTable垃圾回收
代码:
/**
演示 StringTable 垃圾回收
-Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
*/
public class Demo {
public static void main(String[] args) throws InterruptedException {
int i = 0;
try {
} catch (Throwable e){
e.printStackTrace();
}finally{
System.out.println(i);
}
}
StringTable性能调优
可参见博客:JVM专题(八)-StringTable调优
定义:
直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通脱一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
直接内存(堆外内存)与堆内存比较
代码验证:
package org.example;
import java.nio.ByteBuffer;
/**
* 直接内存 与 堆内存的比较
*/
public class ByteBufferCompare {
public static void main(String[] args) {
allocateCompare(); //分配比较
operateCompare(); //读写比较
}
/**
* 直接内存 和 堆内存的 分配空间比较
*
* 结论: 在数据量提升时,直接内存相比非直接内的申请,有很严重的性能问题
*
*/
public static void allocateCompare(){
int time = 10000000; //操作次数
long st = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
//ByteBuffer.allocate(int capacity) 分配一个新的字节缓冲区。
ByteBuffer buffer = ByteBuffer.allocate(2); //非直接内存分配申请
}
long et = System.currentTimeMillis();
System.out.println("在进行"+time+"次分配操作时,堆内存 分配耗时:" + (et-st) +"ms" );
long st_heap = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
//ByteBuffer.allocateDirect(int capacity) 分配新的直接字节缓冲区。
ByteBuffer buffer = ByteBuffer.allocateDirect(2); //直接内存分配申请
}
long et_direct = System.currentTimeMillis();
System.out.println("在进行"+time+"次分配操作时,直接内存 分配耗时:" + (et_direct-st_heap) +"ms" );
}
/**
* 直接内存 和 堆内存的 读写性能比较
*
* 结论:直接内存在直接的IO 操作上,在频繁的读写时 会有显著的性能提升
*
*/
public static void operateCompare(){
int time = 1000000000;
ByteBuffer buffer = ByteBuffer.allocate(2*time);
long st = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
// putChar(char value) 用来写入 char 值的相对 put 方法
buffer.putChar('a');
}
buffer.flip();
for (int i = 0; i < time; i++) {
buffer.getChar();
}
long et = System.currentTimeMillis();
System.out.println("在进行"+time+"次读写操作时,非直接内存读写耗时:" + (et-st) +"ms");
ByteBuffer buffer_d = ByteBuffer.allocateDirect(2*time);
long st_direct = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
// putChar(char value) 用来写入 char 值的相对 put 方法
buffer_d.putChar('a');
}
buffer_d.flip();
for (int i = 0; i < time; i++) {
buffer_d.getChar();
}
long et_direct = System.currentTimeMillis();
System.out.println("在进行"+time+"次读写操作时,直接内存读写耗时:" + (et_direct - st_direct) +"ms");
}
}
输出:
在进行10000000次分配操作时,堆内存 分配耗时:12ms
在进行10000000次分配操作时,直接内存 分配耗时:8233ms
在进行1000000000次读写操作时,非直接内存读写耗时:4055ms
在进行1000000000次读写操作时,直接内存读写耗时:745ms
可以自己设置不同的time 值进行比较
分析:
从数据流的角度,来看
非直接内存作用链:
本地IO –>直接内存–>非直接内存–>直接内存–>本地IO
直接内存作用链:
本地IO–>直接内存–>本地IO
直接内存使用场景
有很大的数据需要存储,它的生命周期很长
适合频繁的IO操作,例如网络并发场景
分配和回收原理