JVM

类加载过程

  • 加载:

    将编译成的字节码文件加载到JVM内存中, 并生成一个代表该类的 java.lang.Class 对象

  • 验证:

    检查字节码是否规范,

    确保二进制字节流格式符合预期(比如说是否以 cafe bene 咖啡北鼻开头)。

    是否所有方法都遵守访问控制关键字的限定。

    方法调用的参数个数和类型是否正确。

    确保变量在使用之前被正确初始化了。

    检查变量是否被赋予恰当类型的值

  • 准备:

    对类变量(也称为静态变量,static 关键字修饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)

    public static int value1 = 123;在此阶段初始值为0

    public static final int value2 = 123;在此阶段初始值为123

  • 解析:

    将常量池中的符号引用转化为直接引用

    直接引用通过对符号引用进行解析,找到引用的实际内存地址

  • 初始化:

    为变量赋予代码中的值

java中的native

  • 作用: 扩展java的功能, 使java可以调用其他语言的代码完成任务. 比如用户上传一个视频文件,需要后台给视频加上水印,或者后台分离视频流和音频流,这个事Java就做不了,只能交给C语言去处理,然后Java调用C语言的接口. 因为涉及操作系统底层的事件,Java是处理不了的

  • 一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现

  • Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

  • JNI的书写步骤如下:

    • a.编写带有native声明的方法的Java类
    • b.使用javac命令编译编写的Java类
    • c.使用javah -jni ****来生成后缀名为.h的头文件
    • d.使用其他语言(C、C++)实现本地方法
    • e.将本地方法编写的文件生成动态链接库

一个jni的实例

环境为linux, windows由于gcc编译时的一些问题, 搜寻未果

  • HelloWorld.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class HelloWorld {
    public native void hello();
    static {
    System.loadLibrary("hello");
    }
    public static void main(String[] args) {
    new HelloWorld().hello();
    }
    }

    用于调用其他语言, 本例中为c

  • javac HelloWorld.java, 生成HelloWorld.class

  • javah -jni HelloWorld, 生成HelloWorld.h

  • HelloWorld.c

    1
    2
    3
    4
    5
    6
    7
    #include <jni.h> 
    #include "HelloWorld.h"
    #include <stdio.h>
    JNIEXPORT void JNICALL Java_HelloWorld_hello(JNIEnv * env, jobject obj) {
    printf("Hello Worlddd!\n");
    return;
    }

    被调用的c代码

  • 生成动态链接库文件 libhello.so

    1
    2
    3
    4
    gcc -Wall -fPIC -c HelloWorld.c -I ./ -I $JAVA_HOME/include/linux/ -I $JAVA_HOME/include/ 
    # 生成HelloWorld.o
    gcc -Wall -rdynamic -shared -o libhello.so HelloWorld.o
    # 生成libhello.so
  • export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH, 暂时将当前目录添加进LD_LIBRARY_PATH

  • java HelloWorld

image-20220210121002921
  • 最终: HelloWorld.class libhello.so两个文件

==静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关==

  • 存放8大基本类型, 对象引用, 实例的方法
  • 程序运行, main最先进栈, 如果main调用方法a(), a()进栈…方法结束, 方法出栈
  • 程序正在执行的方法, 一定在栈顶

堆(heap)

  • 一个JVM只有一个堆内存, 大小可以调节

  • 划分

    • 新生区(new)(young)(==PSYoungGen==)
      • 伊甸园区(==eden==)
      • 幸村区0区(==from==)
      • 幸村区1区(==to==)
    • 养老区(old)(==ParOldGen==)
    • 永久区(permanent)(元空间 ==Metaspace== (JDK8以后))
  • GC主要发生在伊甸园区和养老区

  • OOM, 堆内存溢出

  • 新生区

    • 类诞生 成长甚至死亡的地方
    • 所有的对象都是在伊甸园new出来的
    • 伊甸园满后, 一次轻GC, 存活下来的到幸存0区, 0区和1区是交换的
  • 元空间

    • 包含方法区,方法区中包含常量池
    • 还包含运行时的一些环境

OOM分析

  • 调大堆内存, 若还溢出, 说明代码可能有问题

    idea->Edit Configurations…-Xms1024m -Xmx1024m -XX:+PrintGCDetails

    image-20220213133451121

  • Jprofiler

    • idea 安装插件Jprofiler, 用于产生dump文件, 内含java对象信息
    • 本机安装客户端, 用于查看dump文件
    • 还需增加虚拟机配置-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

GC

  • 自动回收, 可以手动建议JVM进行回收, 但回不回收由JVM决定
  • 大部分的回收发生在新生代
  • 轻GC(普通GC)发生在新生代和幸存区(满的时候), 重GC是全局

GC算法

  • 标记清除法
    • 第一次扫描, 对活着的对象进行标记
    • 第二次扫描, 对标记的对象进行清除
    • 优点: 不会浪费额外的空间
    • 缺点: 两次扫描浪费时间, 会产生内存碎片
  • 标记整理(压缩)法
    • 标记清除法基础上还来扫描, 移动对象, 避免产生内存碎片
    • 可以标记清除几次再来压缩, 以减少开销
  • 复制算法
    • 适用于对象存活率低的场景(新生区)
    • 当幸存0区和1区都有对象时, 会将其中一个区的对象复制到另一个区, 保证有一个是空的, 称为to
    • 默认经历过15次GC后, 对象还没有死, 就会进入养老区
    • 优点: 没有内存碎片
    • 缺点: 浪费一半空间(0区 1区)
  • 引用计数器
    • 记录每个对象的引用次数, 当次数为0时, 清除

GC算法对比

  • 内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
  • 内存整齐度:复制算法=标记压缩算法>标记清除算法
  • 内存利用率:标记压缩算法=标记清除算法>复制算法

分代收集算法

  • 年轻代:存活率低->复制算法

  • 老年代:区域大, 存活率高->标记清除 标记压缩混合实现

JMM-java memory module

Java如何调用C程序,JNI技术_追风人的博客-CSDN博客_java调用c语言