深入理解java虚拟机笔记

目录

第一章

第二章 自动内存管理

运行时数据区域

        程序计数器

        Java堆和栈

        Java中对象到底存在堆中还是栈中?

        Java方法区和元空间

        运行时常量池

       静态常量池和运行时常量池的区别

        1.Java堆中的名词

        2.TLAB

        3.对象的创建过程(象限于普通Java对象,不包括数组和Class对象等)

        4.java的成员变量(全局变量)到底存在堆里还是方法区里?

        5.Java中静态成员及常量池的存储位置

对象

        对象的创建

        对象的内存布局

        对象的访问定位

        1.什么是句柄?

        2.oom可能发生在哪些区域上?

​编辑

实战:OutOfMemoryError异常

第3章 垃圾收集器与内存分配策略


第一章

        1.JVM是Java虚拟机;JRE是Java程序运行的标准环境,它包含JVM和Java类库API中的Java SE API子集;JDK是Java程序开发的最小环境,它包含Java程序设计语言、Java虚拟机、Java类库。

第二章 自动内存管理

运行时数据区域

        程序计数器

        为了让每个线程正常工作就提出了程序计数器(Programe Counter Register),每个线程都有自己的程序计数器,来存储字节码文件的行号,这样当线程执行切换的时候就可以在上次执行的基础上继续执行,仅仅从一条线程线性执行的角度而言,代码是一条一条的往下执行的,这个时候就是程序计数器;JVM就是通过读取程序计数器的值来决定下一条需要执行的字节码指令,进而进行选择语句、循环、异常处理等;

        举例
        比如老王正在看片,他看到三十五分钟的时候,突然他的QQ好友苍老师给他开视频,这时候肯定打断他看片了,假设苍老师和他视频完了,他肯定要接着他那35分钟的进度去继续看,这时候他怎么知道我看到35分钟了?这时候程序计数器就起了作用,他负责管理进度。

        特点
        线程私有
        JVM规范中唯一没有规定OutOfMemoryError情况的区域
        如果正在执行的是Native 方法,则这个计数器值为空


        首先,为什么是线程私有?
        Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,也就是说,在同一时刻一个处理器内核只会执行一条线程,处理器切换线程时并不会记录上一个线程执行到哪个位置,所以为了线程切换后依然能恢复到原位,每条线程都需要有各自独立的程序计数器。

        为什么没有规定OutOfMemoryError?
        如上文,程序计数器存储的是字节码文件的行号,而这个范围是可知晓的,在一开始分配内存时就可以分配一个绝对不会溢出的内存。

        为什么执行Native方法,值为空?
Native方法大多是通过C实现并未编译成需要执行的字节码指令,也就不需要去存储字节码文件的行号了。
 

        Java堆和栈

        Java中对象到底存在堆中还是栈中?

        创建一个对象的时候,到底是在栈中分配还是在堆中分配需要看2个方面:对象类型和在Java中存在的位置。

        如果是基本数据类型,byte、short、int、long、float、double、char,如果是在方法中声明,则存储在栈中,其它情况都是在堆中(比方说类的成员变量就在堆中);
        除了基本数据类型之外的对象,JVM会在堆中创建对象,对象的引用存于虚拟机栈中的局部变量表中;
        并不是所有的对象都在堆中存储,可以走栈上分配,在不在栈上分配取决于Hotspot的一个优化技术:“逃逸分析”。
        逃逸分析中有方法逃逸和线程逃逸

方法逃逸:
        就是当一个对象在方法中定义后,它可能被外部方法访问到,比如说通过参数传递到其它方法中
线程逃逸:
        就是当一个对象在方法中定义后,它可能赋值给其它线程中访问的变量
        如果不满足逃逸分析就会在栈上分配
        栈上分配的好处就是方法出栈后内存就释放了,不需要通过gc来进行垃圾回收

        需要注意的是:如果执行方法频次比较低的话,是不会触发栈上分配的

        Java方法区和元空间

        链接:方法区、永久代、元空间的区别_永久代和方法区的区别_沐雨金鳞的博客-CSDN博客

        运行时常量池

       静态常量池和运行时常量池的区别

        class文件中常量池保存的是字符串常量,类和接口名字,字段名,和其他一些在class中引用的常量。每个class都有一份。

        运行时常量池保存的是从class文件常量池构建的静态常量引用和符号引用。每个class都有一份。

        1.Java堆中的名词

        Java堆中经常会出现"新生代""老年代"、"永久代"、"Eden空间"、"From Survivor空间"、"To
Survivor空间"等名词这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个Java虚拟机具体实现的固有内存布局。

        2.TLAB

        如果从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。

        TLAB (Thread Local Allocation Buffer,线程本地分配缓冲区)是 Java 中内存分配的一个概念,它是在 Java 堆中划分出来的针对每个线程的内存区域,专门在该区域为该线程创建的对象分配内存。它的主要目的是在多线程并发环境下需要进行内存分配的时候,减少线程之间对于内存分配区域的竞争,加速内存分配的速度。TLAB 本质上还是在 Java 堆中的,因此在 TLAB 区域的对象,也可以被其他线程访问。

        如果没有启用 TLAB,多个并发执行的线程需要创建对象、申请分配内存的时候,有可能在 Java 堆的同一个位置申请,这时就需要对拟分配的内存区域进行加锁或者采用 CAS 等操作,保证这个区域只能分配给一个线程。

        启用了 TLAB 之后(-XX:+UseTLAB, 默认是开启的),JVM 会针对每一个线程在 Java 堆中预留一个内存区域,在预留这个动作发生的时候,需要进行加锁或者采用 CAS 等操作进行保护,避免多个线程预留同一个区域。一旦某个区域确定划分给某个线程,之后该线程需要分配内存的时候,会优先在这片区域中申请。这个区域针对分配内存这个动作而言是该线程私有的,因此在分配的时候不用进行加锁等保护性的操作。

        3.对象的创建过程象限于普通Java对象,不包括数组和Class对象等

        略,请自行查阅资料。

        4.java的成员变量(全局变量)到底存在堆里还是方法区里?

        两者都有存在,方法区里存有类信息,常量,静态变量,编译后的class文件等,既然是类信息,那么一个class类中的方法名,类名,成员变量名称等都属于该类的信息,也是存在于方法区中,也就是一个类中的成员变量名称是存在于方法区中的,但是在我们new一个对象后,这个对象上包含的成员变量的值是放入堆中的,也就是new的这个对象包含着这个对象的成员变量的信息(也就是值),但是这个对象上不会包含该类的方法,因为方法是共享的。

        示例:“_内存_”类各个参数信息的存储地方

        5.Java中静态成员及常量池的存储位置

        Java中,静态成员的存储位置
        JDK1.8以前
        在JDK1.8以前,静态成员存储在方法区(永久代)中,此时方法区的实现叫做永久代

        JDK1.8以后
        在JDK1.8以后,永久代被移除,此时方法区的实现更改为元空间,但由于元空间主要用于存储字节码文件,因此静态成员的存储位置从方法区更改到了堆内存中

        Java中,常量池的存储位置
        JDK1.6及以前
        在JDK1.6及以前,常量池存储在方法区(永久代)中

        JDK1.7
        在JDK1.7中,方法区被整合到堆内存中,常量池存储在堆内存中

        JDK1.8以后
        在JDK1.8后,方法区从堆内存中独立出来,常量池存储在方法区中(但此时永久代被移除,方法区的实现更改为元空间

对象

        对象的创建

        对象的内存布局

        对象的访问定位

        1.什么是句柄?

        句柄(handle)是C++程序设计中经常提及的一个术语。 它并不是一种具体的、固定不变的数据类型或实体,而是代表了程序设计中的一个广义的概念。 句柄一般是指获取另一个对象的方法——一个广义的指针,它的具体形式可能是一个整数、一个对象或就是一个真实的指针,而它的目的就是建立起与被访问对象之间的唯一的联系 。

        2.oom可能发生在哪些区域上?

        堆内存,堆内存不足是最常见的发送OOM的原因之一。
        如果在堆中没有内存完成对象实例的分配,并且堆无法再扩展时,将抛出OutOfMemoryError异常,抛出的错误信息是“java.lang.OutOfMemoryError:Java heap space”。
当前主流的JVM可以通过-Xmx和-Xms来控制堆内存的大小,发生堆上OOM的可能是存在内存泄露,也可能是堆大小分配不合理。

        Java虚拟机栈和本地方法栈,这两个区域的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务,在内存分配异常上是相同的。
在JVM规范中,对Java虚拟机栈规定了两种异常:
        a. 如果线程请求的栈大于所分配的栈大小,则抛出StackOverFlowError错误,比如进行了一个不会停止的递归调用;
        b. 如果虚拟机栈是可以动态拓展的,拓展时无法申请到足够的内存,则抛出OutOfMemoryError错误。

        直接内存:直接内存虽然不是虚拟机运行时数据区的一部分,但既然是内存,就会受到物理内存的限制。在JDK1.4中引入的NIO使用Native函数库在堆外内存上直接分配内存,但直接内存不足时,也会导致OOM。

        方法区:随着Metaspace元数据区的引入,方法区的OOM错误信息也变成了“java.lang.OutOfMemoryError:Metaspace”

        对于旧版本的Oracle JDK,由于永久代的大小有限,而JVM对永久代的垃圾回收并不积极,如果往永久代不断写入数据,例如String.Intern()的调用,在永久代占用太多空间导致内存不足,也会出现OOM的问题,对应的错误信息为“java.lang.OutOfMemoryError:PermGen space”

实战:OutOfMemoryError异常

第3章 垃圾收集器与内存分配策略

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值