文章目录
1.什么是JVM
2.Java的内存结构
Java依靠JVM屏蔽的底层的操作系统,避免了和操作系统直接打交道,通过JVM进行内存分别和回收等一些列操作;它的结构布局如下:
3.程序计数器(JVM)
程序计数器是Java依托操作系统CPU的寄存器实现的,是用于记录JVM下一条指令的执行地址。
它的作用如下图所示:
特点:
- 是线程私有的;
- JVM内存结构中唯一不会存在内存溢出的区域;
4.虚拟机栈(JVM)
每个线程运行时所需要的内存称为Java虚拟机栈。
特点:
- 每个线程运行时都会开辟Java虚拟机栈,而栈又是由栈帧组成,对应着方法调用时所需要的内存;
- 栈帧里面存者局部变量,方法参数,返回值等信息;
Java虚拟机栈的设置:
注意事项:
由于物理内存一定,调整虚拟机栈的大小会影响线程的并发数,因为虚拟机栈的大小越大,并发线程数就越少,所以会影响线程的并发数。
5.堆(JVM)
通过New关键字创建的对象使用的内存称为堆。
特点:
- 堆内存是线程共享的,需要考虑线程安全问题;
- 有垃圾回收机制;
修改堆内存大小:
6.本地方法栈(JVM)
由于Java不直接跟操作系统底层打交道,在实现有关功能时需要调用本地方法,调用本地方法所需要的空间叫本地方法栈。
7.方法区(JVM)
方法区是用来存放跟类相关的信息,如:类加载器、常量,静态变量,类方法、运行时常量池、接口。
注意点:
1.JDK1.6的方法区实现叫做“永久代”,使用的是堆内存(和老年代垃圾回收是捆绑在一起的)。
2.JDK1.8的方法区实现叫“元空间”,使用的是操作系统的内存。
3.调整方法区大小
特点:
- 方法区是线程共享的需要考虑线程安全问题;
- JDK1.6以前叫“永久代”,有垃圾回收机制,JDK1.8叫“元空间”,使用的是本地内存,并将运行时常量池移动到堆内存,使用的YongGC就可以回收内存;
8.直接内存
直接内存并不是Java虚拟机运行的数据区的一部分,已不是Java虚拟机规范中定义的内存区域;常见于NIO操作时,用于数据的缓冲区。
1:传统的阻塞IO操作原理图:
Java程序首先会调用系统的本地方法,将数据读取到系统缓冲区,然后再从系统缓冲区读取到Java堆的缓冲区。
2:使用直接内存进行IO操作时:
Java程序首先会调用系统的本地方法,将数据读取到直接内存(缓冲区),再从直接内存读取到Java堆的缓冲区。
3:代码示例:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class Test0927_ZhiJieNeiCun {
private static File file=new File("D:\\Daliy_DownLoad\\Java_Kuang_Jar\\JVM\\a.txt");
private static File newFile=new File("D:\\TestIO.txt");
public static void main(String[] args) throws IOException {
io();//传统IO拷贝
direcBuffer();
}
private static void io() throws IOException {
FileInputStream in=new FileInputStream(file);
FileOutputStream os=new FileOutputStream(newFile);
int len=0;
byte[] data=new byte[1024];
while ((len=in.read(data))!=-1){
os.write(data,0,len);
}
}
private static void direcBuffer() throws IOException {
//FileChannel是直接把输入和输出流建立联系,效率更高
FileChannel in=new FileInputStream(file).getChannel();
FileChannel os=new FileOutputStream(newFile).getChannel();
ByteBuffer bf=ByteBuffer.allocate(1024); //分配直接内存
int len=0;
while ((len=in.read(bf))!=-1){
os.write(bf);
}
}
}
4:直接内存的应用和释放
import sun.misc.Unsafe;
import java.io.IOException;
import java.lang.reflect.Field;
/**
* 直接内存分配的底层原理:Unsafe
*/
public class Demo1_27 {
static int _1Gb = 1024 * 1024 * 1024;
public static void main(String[] args) throws IOException {
Unsafe unsafe = getUnsafe();
// 分配内存
long base = unsafe.allocateMemory(_1Gb); //相当于地址
unsafe.setMemory(base, _1Gb, (byte) 0);
System.in.read();
// 释放内存
unsafe.freeMemory(base);
System.in.read();
}
public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
return unsafe;
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
5:2 分配和回收原理
- 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法。
- ByteBuffer的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存。
9.运行时常量池
1:看一段程序代码:
public class TestStringTable0927 {
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); //false ==>s3:在编译时会产生优化等价于 s3="ab",而s4=new String("ab");
System.out.println(s3==s5); //true
System.out.println(s3==s6); //true ==> s3="ab",s6等于S4入池方法后所以是相等的
}
}
2:图解其内存布局:
使用Java反编译查看字节码:
1:利用工具反编译
2:使用Java的指令查看字节码:
3:查看相关的字节码信息:
4:根据字节码信息分析内存布局:
10.线程运行诊断
- 用top定位哪个进程对cpu的占用过高;
- ps H -eo pid,tid,%cpu | grep 进程id;(用ps命令进一步定位是哪个线程引起的cpu占用过高);
- jstack 进程id 可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号;
示例:
1:top命令查看进程
2:ps H -eo pid,tid,%cpu|grep 进程Id
3:jstack进程进去查看线程(十进制–》十六进制)