java内存模型笔记

java虚拟机运行时的数据区

image

程序计数器

  • 内存空间小

  • 每个线程有一个程序计数器,各个程序计数器互不影响,独立存储,所以叫做“线程私有”

  • 可以看做线程的字节码行号,分支,循环,跳转,异常处理,线程恢复都需要

  • 如果是java方法 计数器指的是正在执行的虚拟机字节码的地址

  • 如果是native 方法,计数器为空 (native是非java代码编写的,比如C,C++, 它们无法在java编译时生成字节码,即JVM获取不到native实现,只能通过系统指令去调用native方法),C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的

image

  • native 方法使用教程
public class Test {
    public native static void Hello();

    public static void main(String[] args) {
        System.load("C:" + File.separator + "JavaNativeForC.dll");
        Hello();
    }
}

上面 hello() 就是本地化方法
主函数加载了c++的动态链接库
然后运行java 命令生成 .h文件

javac Main.java
javah -jni Main
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Test */

#ifndef _Included_Test
#define _Included_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Test
 * Method:    Hello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Test_Hello
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

将Test.h开头的#include <jni.h>改为#include “jni.h”。
直接去c++ 实现 h声明的方法 生成动态链接库即可

java虚拟机栈 (描述方法执行的内存模型)

image.png

  • 和程序计数器一样是线程私有的
  • 生命周期和线程一样
  • 每个方法执行时,都会创建一个栈帧,用来存局部变量,操作数栈,动态链接,方法出口等信息
  • 当进入方法,帧中分配的局部变量空间是确定的,在运行期间不会改变局部变量表大小
  • 当前虚拟机可以动态扩展栈深度

本地方法栈

  • 与虚拟机栈相似
  • 本地方法栈主要针对native服务

Java堆

  • 内存管理最大的一块
  • 堆可以细分为新生代和老年代

方法区

  • 各个线程共享的区域
  • 存储已经被加载的类信息,常量,静态变量

运行时常量池

  • 是方法区的一部分
  • Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。(字面量就是比如说int a = 1; 这个1就是字面量。又比如String a = “abc”,这个abc就是字面量。) (在java中,一个java类将会编译成一个class文件。在编译时,java类并不知道引用类的实际内存地址,因此只能使用符号引用来代替。比如org.simple.People类要引用org.simple.Tool类,在编译时People类并不知道Tool类的实际内存地址,因此只能使用符号org.simple.Tool(假设)来表示Tool类的地址。而在类装载器装载People类时,此时可以通过虚拟机获取Tool类 的实际内存地址,因此便可以既将符号org.simple.Tool替换为Tool类的实际内存地址,及直接引用地址。)

直接内存直接内存

在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。

对象的创建

指针碰撞

假设java堆内内存是绝对规整的,分配内存时只是把指针移到与对象大小相等的那段距离。

空闲列表

假设java堆内内存不是规整的,虚拟机会维护一个列表,记录哪块内存快可以使用,在分配的时候从列表中找到一块足够大的空间划分给对象。

java是否规整由所采用垃圾收集器是否带有垃圾压缩功能决定的

使用Serial,ParNew等待Compact过程收集器时,通常采用空闲列表,使用 CMS这种基于Mark-Sweep算法收集器,通常采用空闲列表*

多人修改指针出出现并发问题,解决这个问题有2中方案

  • 使用CAS配上失败重试的方式保证更新操作的原子性
  • 把内存分配按照线程划分在不同的空间进行,每个线程在javal里预先分配一小块内存称为(本地线程分配缓冲,Theard Local Allocation Buffer,TLAB)

内存分配完成需要将内存空间置为0值,这个操作保证对象的实例字段不用初始化就可以使用,程序能访问到这些对象的0值

对象的内存布局

对象头(2部分)

存储对象自身的运行时的数据

存储哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳
image.png

类型指针

通过指针获取是哪个类的实例

实例数据

程序代码中定义的各种字段内容,父类继承和子类有的都需要记录,这部分存储顺序会受到虚拟机分配策略的影响,和在java中定义顺序的影响 HotSpot 虚拟机默认的分配策略为longs/doubles、 ints、 short/chars、 bytes/booleans、oops (Ordinary Object Pointers) 相同宽度的字段会会被分配到一起

对齐填充

HotSot VM 要求对象必须是8字节的整数倍,不足就填充

对象的访问定位

句柄访问

通过句柄访问

直接指针访问

直接指针访问
垃圾收集的时候,要移动对象,使用句柄的话,不需要修改reference,直接修改句柄即可
直接指针访问好处就是速度快,一次定位
目前主要还是使用直接指针访问,由于java对象访问非常频繁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值