JVM组成

JVM是什么?

JVM(Java Virtual Machine):Java程序的运行环境(java二进制字节码的运行环境)

好处:

1.一次编写,到处运行

Java代码是如何做到一次编写,到处运行?

计算机的最底层是计算机硬件(如cpu,内存条),再往上是操作系统(如windows,linux),JVM就是运行在操作系统之上的,正是因为JVM,Java才成为一个跨平台的语言,因为JVM屏蔽了操作系统的差异,真正运行代码的并不是这些操作系统,而是JVM,所以才能做到一次编写,到处运行。

2.自动内存管理,垃圾回收机制

对于从事C、C++程序开发的开发人员来说,需要自行管理内存,所以很容易由于编码不当导致内存泄露等问题

而JAVA虚拟机的垃圾回收机制,则大大减轻了程序员的负担,减少了程序员出错的几率

JVM的运行流程是什么?

1.Java Source 就是Java的源文件,将源文件编译为Java Class字节码文件

2.类加载到子系统 ,在加载的过程中会将Java代码转换成字节码

3.运行数据区会将字节码加载到内存,只有加载到内存程序才能运行

4.执行引擎将字节码翻译成底层系统指令 ,在这个过程中解释器会解释字节码的信息即时编辑器会针对代码进行优化GC垃圾回收主要针对运行数据区堆空间

5.由于Java代码有些时候并不能实现某些功能,所以需要借助系统提供的某些接口,也就是本地方法接口(由C或C++实现)

JVM的组成

什么是程序计数器?

程序计数器(PC Register): 线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址

线程私有就不存在线程的安全问题

字节码文件中,就详细说明了代码的执行过程

我们可以通过下面的命令,查看字节码的反汇编信息,其中就详细记录了代码的执行过程,

javap-v xx.class 打印堆栈大小,局部变量的数量和方法的参数。
查看字节码文件中代码执行

(1)先编译一下该类

(2)执行命令

(3)查看反汇编生成的字节码文件

java程序的二进制字节码文件

程序计数器就是在线程的切换时,记录代码的执行行号

案例

线程一先执行,执行行号为10 ,时间片的时间到了,切换到线程二执行,此时程序计数器需要保存线程一执行的终止行号,以便下一次的执行。线程二执行到行号9,线程二时间片到了,线程二的程序计数器会保存线程二的终止行号,然后线程一再次开始,从程序计数器记录的行号10,继续往下执行,执行到了行号20,再次进行切换。。。

2.什么是Java堆

Java堆 是一个线程共享的区域:主要用来保存对象实例,数组 等,当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError的异常

堆的年轻代和老年代

年轻代 被划分为三部分,Eden区两个大小严格相同的Survivor区

根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到老年代区间。

老年代主要保存生命周期长的对象,一般是一些老的对象

什么是元空间?

元空间: 保存类信息,静态变量,常量,编译后的代码

java1.7 和 java1.8的堆的区别

java8将堆中的方法区/永久代放到了本地内存,也就是元空间中

1.7 中有一个永久代,存储的是类信息、静态变量、常量、编译后的代码
1.8 移除了永久代,把数据存储到了本地内存的元空间中,防止内存溢出

什么是虚拟机栈?

Java Virtual machine Stacks(java 虚拟机栈)

  • 每个线程运行时所需要的内存,称为虚拟机栈,先进后出
  • 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
案例:

栈帧1中的方法,调用栈帧2中的方法,栈帧2方法调用栈帧3的方法

则活动栈帧的顺序:栈帧1-->栈帧2-->栈帧3-->栈帧2-->栈帧1

垃圾回收是否涉及栈内存?

垃圾回收主要指就是堆内存,当栈帧弹栈以后,内存就会释放,并不需要垃圾回收。

栈内存分配越大越好吗?

未必,默认的栈内存通常为1024k。栈帧过大会导致线程数变少,例如,机器总内存为512m,目

前能活动的线程数则为512个,如把栈内存改为2048k,那么能活动的栈帧就会减半。

方法内的局部变量是否是线程安全的?
案例:

如果方法内局部变量没有逃离方法的作用范围,它是线程安全的

如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

栈内存溢出的情况

栈帧过多导致栈内存溢出,典型问题:递归调用

栈帧过大导致栈内存溢出

堆栈的区别是什么?

1.栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的。堆会GC垃圾回收,而栈不会。

2.栈内存是线程私有的,而堆内存是线程共有的。

3.两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常,

栈空间不足:java.ang.StackOverFlowError。

堆空间不足:java.ang.OutOfMemoryError。

什么是方法区?

**方法区(Method Area)**是各个线程共享的内存区域

主要存储类的信息、运行时常量池

虚拟机启动的时候创建,关闭虚拟机时释放

如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError:Metaspace

方法区的内存溢出

将元空间的内存大小改为 8M

执行结果:方法区的内存太小

什么是常量池?

可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息

javap -v Application.class

查看字节码结构(类的基本信息、常量池、方法定义)

类信息

常量池

根据字节码文件查询常量池表

什么是运行时常量池?

常量池是 *class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实内存地址,从而执行指令。

什么是直接内存?

**直接内存:**并不属于IM中的内存结构,不由M进行管理。是虚拟机的系统内存,常见于 NI0 操作时,用于数据缓冲区,它分配回收成本较高,但读写性能高

例子
java 复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class DirectMemoryDemo {
    static final String FROM = "E:\\bak1\\01-java成神之路.mp4";
    static final String TO = "E:\\bak2\\abc.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io();
        directBuffer();
    }

    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) / 1e9 + " 秒");
    }

    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) / 1e9 + " 秒");
    }
}
IO VS NIO
常规IO的数据拷贝流程
  • Java程序(用户态)调用read方法发起从磁盘文件读取数据的请求;
  • 操作系统从内核态将磁盘文件的数据读取到系统缓存区;
  • 内核态把系统缓存区的数据拷贝到Java堆内存中的缓冲区;
  • Java程序在用户态对Java堆内存中的数据进行处理;
  • Java程序调用 `write` 方法发起将数据写入目标位置的请求;
  • 内核态将Java堆内存缓冲区中的数据拷贝回系统缓存区;
  • 操作系统从内核态将系统缓存区的数据写入目标位置(如另一个磁盘文件)。
NIO数据拷贝流程
  • Java程序(用户态)调用 `read` 方法发起从磁盘文件读取数据的请求;
  • 操作系统从内核态将磁盘文件的数据直接读取到直接内存(也叫堆外内存);
  • Java程序在用户态可以直接访问和处理直接内存中的数据;
  • Java程序调用 `write` 方法发起将数据写入目标位置的请求;
  • 操作系统从内核态将直接内存中的数据写入目标位置(如另一个磁盘文件)。

二者区别:

常规IO数据在内核态的系统缓存区和用户态的Java堆内存之间多次拷贝,至少有4次数据拷贝操作**(读2次,写2次)**;

NIO使用直接内存,减少了数据在内核态和用户态之间的拷贝次数,通常只需2次数据拷贝**(读1次,写1次)**,提升了性能。

相关推荐
陈老师还在写代码8 小时前
在服务器部署JVM后,如何评估JVM的工作能力,比如吞吐量
运维·服务器·jvm
鲨鱼 Fish12 小时前
JVM春招快速学习指南
java·jvm
兀行者(做个有情怀的java程序员)1 天前
什么是Java虚拟机(JVM)?它的作用是什么?
java·开发语言·jvm
北执南念1 天前
Synchronized使用
android·java·jvm
java小罗_江西南昌1 天前
17.垃圾回收器
java·jvm
计算机小白一个2 天前
蓝桥杯 Java B 组 - 第 1 周复习总结
java·开发语言·jvm·算法·蓝桥杯
众乐乐_20082 天前
JVM栈帧中|局部变量表、操作数栈、动态链接各自的任务是什么?
java·开发语言·jvm
阿乾之铭2 天前
JVM(Java 虚拟机)
jvm
emanjusaka2 天前
Java 中堆内存和栈内存上的数据分布和特点
java·jvm·heap·stack