11. Jvm运行机制

一. JVM与操作系统的关系 

1.JVM(Java Virtual Machine)

java虚拟机,它能识别.class后缀的文件,且能解析它的指令,最终调用操作系统上的函数完成我们想要的操作;

2.翻译

java文件使用javac编译成.class文件后,还需要使用Java命令去主动执行它,操作系统并不认识这些.class文件,所有jvm就是一个翻译;

有了JVM这个抽象层后,java就可实现跨平台运行了,jvm只需保证正确执行.class文件,即可在Linux,windows,MacOs上运行了。

二. JVM的主要的内部结构

1.JVM 内部原理(JDK1.7之前)

2.HotSpot VM 内存管理(JDK1.8之后将永久区移除)

3.JVM运行时数据区

1>定义:Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分为若干个不同的数据区域

2>类型:程序计数器、虚拟机栈、本地方法栈、Java堆、方法区(运行时常量池)、直接内存

线程共享区域(方法区/堆)

方法区(静态数据共享):

类信息/常量/静态变量/即时编译器编译后代码

<=jdk1.7 永久代, >=jdk1.8 元空间(机器内存,挤压推空间)

Java堆(Heap)(动态数据共享):

对象实例(几乎所有) / 数组 Java堆的大小参数设置如下

-Xms:堆的最小值;-Xmx:堆的最大值;-Xmn:新生代大小;

-XX:NewSize-XX:MaxNewSize新生代最小/大值;

线程隔离数据区(虚拟机栈/本地方法栈/程序计数器)

本地方法栈(Native Method Stack):

当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法,虚拟机规范无强制规定,各版本虚拟机自由实现,HotSpot直接把本地方法栈和虚拟机栈合二为一;

虚拟机栈(VM Stack):

存储当前线程运行方法所需的数据,指令,返回地址;(Java方法)

栈帧:

1.局部变量表 (八大基础数据类型, +引用地址)

2.操作数栈 (存放方法执行的操作数, 栈(后进先出))

3.动态链接 (java的多态性,需动态分配主体确定)

4.完成出口 (return时的返回地址)

栈的大小默认为1M,可用参数 –Xss调整大小,例如-Xss256k

Java解释执行时栈(操作数栈) -- 兼容性好,效率偏低

C是寄存器(硬件)运算 --运算快, 可移植性差;

程序计数器:

程序计数器是一小块内存,主要用来记录各个线程执行的字节码指令的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。基本不拓展,故JVM中唯一不会OOM的内存区域;

由于 Java 是多线程语言,当执行的线程数量超过 CPU 核数时,线程之间会根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,

或者是其它 原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。

区域

特征

作用

配置参数

异常

程序计数器

线程私有,占内存小,生命周期与线程相同

记录线程执行字节码指令地址

虚拟机栈

线程私有,生命周期与线程相同,

使用连续的内存空间;

Java方法执行的内存模型,

存储局部变量表,操作栈,

动态链接,方法出口等信息;

-Xss

StackOverflowError

OutOfMemoryError

java堆

线程共享,生命周期与虚拟机相同,

可不使用连续的内存地址(但数组连续);

保存对象实例,所有对象实例

(包括数组)都要在堆上分配

-Xms, -Xsx,-Xmn

OutOfMemoryError

方法区

线程共享,生命周期与虚拟机相同,

可以不使用连续的内存地址

存储已被虚拟机加载的类信息常量、静态变量、即时编译器编译后的代码等数据

JDK1.7之前 HotSotVm:

-XX:PermSize:16M

-XX:MaxPermSize:64M

JDK1.8 使用元数据区

-XX:MetaspaceSize

-XX:MaxMetaspaceSize

OutOfMemoryError

静态常量池

方法区的一部分 class data

字面量(全局变量值) 及 符号引用 (java.lang.String)

运行时常量池

类加载--运行时数据区--方法区(逻辑区域)

符号引用-->直接引用(hash值)

三. 辨析堆和栈

1.功能

以栈帧的方式存储方法调用的过程,并存储方法调用过程中的基本数据类型(byte,int,short,long,boolean double float char)及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;

而堆内存用来存Java中的对象,无论是成员变量、局部变量、还是类变量,它们指向的对象都存储在堆中;

2.线程独享还共享

栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可理解为线程私有内存;

堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问;

3.空间大小

栈内存要远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError问题;

4.内存溢出

1>栈溢出

参数:-Xss1m, 具体默认值需要查看官网:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BABHDABI

HotSpot 版本中栈的大小是固定的,是不支持拓展的;

java.lang,StrackOverflowError 一般的方法调用时很难出现的,如果出现了可能会是无限递归;

虚拟机栈带给我们的启示: 方法的执行因为要打包成栈帧,所以天生要比实现同样功能的循环慢,所以树的遍历算法中,递归和非递归(循环来实现)都有存在的意义,递归代码简洁,非递归代码复杂但是速度快.

2>堆溢出

内存溢出:申请内存空间,超出最大堆内存空间。如果是内存溢出,则通过调大 -Xms, -Xmx参数, 再从代码上检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行时的内存消耗。

3>方法区溢出

1 运行时常量池溢出

2方法区中保存的Class对象没有被及时回收掉或者Class信息占用的内存超过了我们配置。

4>本机直接内存溢出

直接内存的容量可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常;

由直接内存导致的内存溢出,一个比较明显的特征是在HeapDump文件中不会看见有什么明显的异常情况,如果发生了OOM,同时Dump文件很小,可考虑重点排查下直接内存方面的原因。

5>解决方案:

  1. 编译优化技术: 方法内联

方法内联优化行为,就是把目标方法的代码原封不动的"复制"到调用的方法中,避免真实的方法调用而已;

public class MethodDeal {
    public static void main(String[] args ){
         //max(1,2); //调用max方法: 虚拟机 --入栈(max 栈帧)
         //boolean i =max(1,2);
        // 方法内联 1>2;
        boolean i2 =1>2; //减少一次 栈帧入栈
    }
    public static boolean max(int a, int b){ return a>b; }
}

  1. 栈的优化技术: 栈帧之间数据共享

在一般的模型中,两个不同的栈帧的内存区域是独立的,但是大部分的JVM在实现中会进行一些优化,使得两个栈帧出现一部分重叠。(主要体现在方法中有参数传递的情况),让下面栈帧的操作数栈和上面栈帧的部分局部变量重叠在一起,这样做不但节约了一部分空间,更加重要的是在进行方法调用时就可以直接公用一部分数据,无需进行额外的参数复制传递了。

5.直接内存--堆外内存

直接内存:

不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的区域;

若使用NIO,该区域会被频繁使用,在Java堆内可用directByteBuffer对象直接引用并操作;

这块内存不受java堆大小限制,但受本机总内存限制,可通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),故也会出现OOM异常;

四. 线程独享内存结构

每个线程都会生成的结构(线程) JVM 系统线程/程序计数器(PC)/栈/本地栈/栈的限制/栈帧/局部变量表/操作数栈/动态连接

1>线程

线程是程序里的一个运行单元,JVM允许一个应用有多个线程并行执行,在JVM里每个线程都与操作系统的本地线程直接映射,

在一个JAVA线程准备好了所有的状态后(线程本地存储,缓存分配,同步的对象,栈以及程序计数器),操作系统中的本地线程

也同时创建,当java线程终止后,本地线程也会回收,操作系统负责把所有线程调度到任何一个可用的CPU上,一旦本地线程

初始化成功,它就会调用Java线程中的run()方法,当run()方法返回,发生未捕获异常,Java线程终止,本地线程就会决定

是否JVM也应该被终止(是否是最后一个非守护线程),当线程终止后,本地线程和Java线程持有的资源都会被释放;

2>JVM 系统线程

后台系统线程不包括main(主线程)及main线程创建的线程,其主要包含以下几个:

1)Attach Listener()

Attach Listener线程是负责接收到外部的命令,而对该命令进行执行的并且吧结果返回给发送者。 通常我们去会用

一些命令要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有

初始化,那么,则会在用户第一次执行jvm命令时,得到启动。

2)Signal Dispatcher(信号分发)

前面我们提到第一个Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后会交给signal dispatcher

线程去进行分发到各个不同的模块处理命令,并且返回处理结果,该线程也是在第一次接收外部jvm命令时进行初始化的。

3)CompilerThread0(编译线程)

用来调用JITing,实时编译装卸class。通常,jvm会启动多个线程来处理这部分工作,线程名称后面的数字也会累加,

例如:CompilerThread1

4)ConcurrentMark-SweepGCThread(并发标记清除垃圾回收器)

就是通常所说的CMS GC该线程主要针对于老年代垃圾回收。

启用该垃圾回收器,需要在jvm启动参数中加上:-XX:+UseConcMarkSweepGC;

5)DestroyJavaVM(卸载Java虚拟机)

执行main()线程在main执行完后调用JNI中的jni_DestroyJavaVM()方法唤起DestroyJavaVM线程。

JVM在Jboss服务器启动之后就会唤起DestroyJavaVM线程,处于等待状态,等待其它线程(java线程和native线程)

退出时通知它卸载JVM。线程退出时都会判断自己当前是否是整个JVM中最后一个非deamon(守护)线程,如果是则通知

DestroyJavaVM线程卸载JVM。

*1.线程退出时判断自己不为最后一个非守护线程,那么调用thread->exit(false),并抛出thread_end事件,jvm不退出。

*2.如果线程退出时判断自己为最后一个非守护线程,那么调用before_exit()方法,抛出两个事件:

事件1:thread_end线程结束事件、事件2:VM的death事件。然后调用thread->exit(true)方法,接下来把线程

从active list卸下,删除线程等等一系列工作执行完成后,则通知正在等待的DestroyJavaVM线程执行卸载JVM操作。

6)ContainerBackground Processor

它是一个守护线程,在jboss服务器在启动的时候就初始化了,主要工作是定期去检查有没有Session过期.过期则清除.

7)Finalizer线程

这个线程也是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象的finalize()方法;

*1.只有当开始一轮垃圾收集时,才会开始调用finalize()方法;因此并不是所有对象的finalize()方法都会被执行;

*2.该线程也是守护线程,因此如果虚拟机中没有其他非守护线程,不管该线程是否执行完finalize()方法,JVM也会退出;

*3.JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,

由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收;

*4.JVM为什么要单独用一个线程来执行finalize()方法呢?如果JVM的垃圾收集线程自己来做,很有可能由于在该方法

中误操作导致GC线程停止或不可控,这对GC线程来说是一种灾难;

8)Gang worker#0

VM用于做新生代垃圾回收(monir gc)的一个线程。#号后面是线程编号,例如:Gang worker#1

9)Java2D Disposer

这个线程主要服务于awt的各个组件。说起该线程的主要工作职责前,需要先介绍一下Disposer类是干嘛的。

Disposer提供一个addRecord方法。如果你想在一个对象被销毁前再做一些善后工作,那么,你可以调用Disposer#

addRecord方法,将这个对象和一个自定义的DisposerRecord接口实现类,一起传入进去,进行注册。

Disposer类会唤起“Java2D Disposer”线程,该线程会扫描已注册的这些对象是否要被回收了,如果是,则调用该对象

对应的DisposerRecord实现类里面的dispose方法。Disposer实际上不限于在awt应用场景,只是awt里面的很多组件

需要访问很多操作系统资源,所以,这些组件在被回收时,需要先释放这些资源。

10)Low MemoryDetector

这个线程是负责对可使用内存进行检测,如果发现可用内存低,分配新的内存空间。

11)Reference Handler

JVM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,它主要用于处理引用对象本身

(软引用、弱引用、虚引用)的垃圾回收问题。

12)SurrogateLockerThread

这个线程主要用于配合CMS垃圾回收器使用,它是一个守护线程,

其主要负责处理GC过程中Java层的Reference(软,弱引用)与jvm内部层面的对象状态同步。

这里拿WeakHashMap做例子,将一些关键点先列出来:

*1.我们知道HashMap用Entry[]数组来存储数据的,WeakHashMap也不例外,内部有一个Entry[]数组。

*2.WeakHashMap的Entry比较特殊,它的继承体系结构为Entry->WeakReference->Reference。

*3.Reference里面有一个全局锁对象:Lock,它也被称为pending_lock,注意:它是静态对象。

*4.Reference 里面有一个静态变量:pending。

*5.Reference 里面有一个静态内部类:ReferenceHandler的线程,它在static块里面被初始化并且启动,

启动完成后处于wait状态,它在一个Lock同步锁模块中等待。

*6.另外,WeakHashMap里面还实例化了一个ReferenceQueue列队,这个列队的作用,后面会提到。

*7.下面我们把他们串起来。假设,WeakHashMap对象里面已经保存了很多对象的引用。 JVM在进行CMS GC的时候,

会创建一个ConcurrentMarkSweepThread(简称CMST)线程去进行GC,ConcurrentMarkSweepThread线程被

创建的同时会创建一个SurrogateLockerThread(简称SLT)线程并且启动它,SLT启动之后,处于等待阶段。

CMST开始GC时,会发一个消息给SLT让它去获取Java层Reference对象的全局锁:Lock。直到CMS GC完毕之后,

JVM会将WeakHashMap中所有被回收的对象所属的WeakReference容器对象放入到Reference的pending属性当中

每次GC完毕之后,pending属性基本上都不会为null了),然后通知SLT释放并且notify全局锁:Lock。 此时

激活了ReferenceHandler线程的run方法,使其脱离wait状态,开始工作了。ReferenceHandler这个线程会将

pending中的所有WeakReference对象都移动到它们各自的列队当中,比如当前这个WeakReference属于某个

WeakHashMap对象,那么它就会被放入相应的ReferenceQueue列队里面(该列队是链表结构)。当我们下次从

WeakHashMap对象里面get、put数据或者调用size方法的时候,WeakHashMap就会将ReferenceQueue列队中的

WeakReference依依poll出来去和Entry[]数据做比较,如果发现相同的,则说明这个Entry所保存的对象已经

被GC掉了,那么将Entry[]内的Entry对象剔除掉。

每个执行线程都包含以下部分:

3>程序计数器(PC)

用于存放当前非native指令(或者字节码)的地址,如果当前方法是native的,那么这个PC便是无用的,所有CPU自带程计,

通常来说,程序计数器在每次指令执行后自增,它会维护下一个将要执行的指令的地址,JVM通过程序计数器来追踪指令执行

的位置,在方法区中,程序计数器实际上是指向一个内存地址;每一个线程都有一个程序计数器;

4>栈

每个线程都有自己的栈,它维护了在该线程上正在执行的每个方法的栈帧,栈是后进先出的数据结构,所以当前执行的方法

在栈顶,每当一个方法被调用时,一个新的栈帧就会被创建并放在栈顶。当方法正常返回或发生了未捕获的异常,栈帧就会

从栈里移除。栈是不能被直接操作的,尤其是栈帧对象的入栈和出栈,因此栈帧对象有可能在堆里分配并且内存不需要连续。

1.每个线程包含一个栈区,栈中只保存基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中

2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。

3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

数据结构层面的堆和栈

数据结构里面:stack,中文翻译为堆栈,其实指的是栈,这里讲的是数据结构的栈,不是内存分配里面的堆和栈。

栈是先进后出的数据的结构,好比你碟子一个一个堆起来,最后放的那个是堆在最上面的。

栈数据结构比较简单。heap翻译为堆,是 一种有序的树。

5>本地栈

并不是所有的JVM都支持本地方法,如果一个JVM已经实现了使用C-linkage 模型来支持Java本地调用,

那么这个本地方法栈将会是一个C 栈。在这种情况下,参数的顺序以及返回值和传统的c程序在本地栈下几乎是一样的。

一个native方法通常(取决于不同的JVM实现)会回调JVM,并且调用一个Java方法。这种native到Java的调用会发生在栈里

(通常指Java栈)。这个线程会离开这个本地栈并且在栈上创建一个新的栈帧。

6>栈的限制

栈可以是动态的大小,也可以是指定的大小。如果一个线程需要一个大一点的栈,可能会导致StackOverflowError异常。

如果一个线程需要一个新的栈帧而又没有足够的内存来分配,就会发生OutOfMemoryError异常。

7>栈帧

JVM为每个方法调用创建一个新的栈帧并推到每个方法调用的栈顶。当方法正常返回或者遇到了未捕获的异常,将栈帧移除,

每个栈帧包含:局部变量表,返回值,操作数栈,当前方法所在的类的运行时常量池引用;

8>局部变量表

局部变量表包含了这个方法执行期间所有用到的变量,包括this引用,所有方法参数以及其他的局部声明变量。

对于类方法(比如静态方法)来说,所有方法参数下标都是从0开始,然而,对于实例方法来说这个0是留给this的。

boolean/byte/char/long/short/int/float/double/reference

基本数据类型 | 引用

在局部变量表里,除了long和double,所有类型都是占了一个槽位,它们占了2个连续槽位,因为他们是64位宽度。

9>操作数栈

操作数栈用于字节码指令执行期间,就像通用寄存器在CPU里使用一样。大部分JVM的字节码各自操作出栈,入栈,复制,

交换,或者执行操作,使其生产和消费各种数据。因此,在字节码里,指令将值在局部变量表和操作数栈之间频繁移动。

示例

一个简单的变量初始化导致两个字节码在操作数栈里交互影响。

int i;

编译后得到下面字节码:

0: iconst_0 // 将 0 入栈到操作数栈的顶端。

1: istore_1 // 从操作数栈顶端弹出并保存到局部变量

想要了解更多关于局部变量表和操作数栈,运行时常量池之间的交互,请看下面的“class文件结构”。

10>动态链接

每个栈帧都包含运行时常量池的引用。这个引用指向了这个栈帧正在执行的方法所在的类的常量池,它对动态链接提供了支持。

C/C++ 代码通常编译成一个对象文件,然后多个文件被链接起来生成一个可用的文件比如一个可执行文件或者动态链接库。

在链接阶段,符号引用在每个对象文件里被替换成一个和最终执行相关的实际的内存地址。在Java里,这个链接过程在

运行时是自动发生的,当Java文件被编译时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里。

一个符号引用是一个逻辑引用并不是一个实际的指向一个物理内存地址的引用。不同的JVM实现能选择什么时候去解决

符号引用,它通常发生在class文件加载后的验证,加载完成,立即调用或者静态解析等阶段,另外一种发生的时候是当

符号引用第一次被使用,也叫做延迟或者延期解析。无论如何当每个引用第一次使用的时候,JVM必须保证解析发生,

并抛出任何解析错误。绑定是一个字段,方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次,因为符号

引用是完全替换的。如果符号引用关联到某个类,而这个类却还没准备好,就会引发类加载。每个直接引用被保存为

偏移地址而不是和变量或者方法在运行时的位置相关的存储结构。

五. 线程间共享内存结构

堆/内存管理/堆外内存/即时(JIT)编译/方法区/class文件结构/类加载器/快速类加载

/方法区的位置/类加载器的引用/运行时常量池/异常表/符号表/内部字符串(String Table);

1>堆

堆区:

1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)

2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

堆用于在运行时分配类实例和数组,数组和对象可能永远不会存储在栈上,因为一个栈帧并不是设计为在创建后会随时改变

大小,栈帧仅保存引用,这个引用指向对象或数组在堆中的位置,与局部变量表(每个栈帧里)中的基本数据类型和引用不同,

对象总是被存储在堆里,所以他们在方法结束后不会被移除,仅仅在垃圾收集的时候才会被移除; 类似于链表

*为了支持垃圾收集,堆被分为三个部分:

*1.年轻代-----常常又被划分为Eden区和Survivor区 其中Survivor区有两个Form Survivor/ To Survivor

*2.老年代(也被叫做年老代)

*3.持久代

2>内存管理

对象和数组不会被明确的释放,只有垃圾回收器来自动释放他们

1.GC工作流程:(存活的进入下一代,无引用的直接置空)(分代回收)

*1.新生成的对象在Eden区完成内存分配;

    *2.当Eden区满了,再创建对象,会因为申请不到空间,触发minor GC,进行young(eden+From survivor)区的垃圾回收。

(为什么是eden+From survivor:两个survivor中始终有一个survivor是空的,空的那个被标记成To Survivor)

  minorGC时,Eden不能被回收的对象被 复制 到空的survivor(也就是放到ToSurvivor,同时Eden肯定会被清空)

另一个survivor(From Survivor)里不能被GC回收的对象也会被 复制 到这个survivor(To Survivor),

始终保证一个survivor是空的。(MinorGC完成之后,To Survivor 和 From Survivor的标记互换)

  *3.如果发现存放对象的那个survivor满了,则这些对象被复制到old区,或者survivor区没有满,但是有些对象已经

足够Old(通过XX:MaxTenuringThreshold参数来设置)默认是 15 岁,这些对象就会成为老年代。 

但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。

   

*4.Old区被放满的之后,进行完整的垃圾回收,即 Full GC

    FullGC时,整理的是Old Generation里的对象,把存活的对象放入到Permanent Generation里。

2.好了看下Minor GC、Major GC和Full GC之间的区别:

   1)Minor GC:

年轻代的回收过程称为 Minor GC,当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。

所以分配率越高,越频繁执行 Minor GC,内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。

Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记,扫描,压缩,清理操作,所以Eden 和 Survivor

区不存在内存碎片。写指针总是停留在所使用内存池的顶部。执行 Minor GC 操作时,不会影响到永久代;

从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。

所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,

停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,

永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,

Minor GC 执行时暂停的时间将会长很多

2)Major GC vs Full GC

Major GC 是清理老年代。

Full GC 是清理整个堆空间—包括年轻代和老年代。

首先,许多 Major GC 是由 Minor GC 触发的,所以很多情况下将这两种 GC 分离是不太可能的。

另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。

3>堆外内存

1.持久代:方法区和内部字符串

2.代码缓存:用于编译和保存已经被JIT编译器编译成本机机器代码的方法

4>即时JIT编

在JVM中,java字节码被解释运行,但是它没有直接运行本地代码快。为了提高性能,Oracle Hotspot VM会寻找字节码的

”热点”区域,它指频繁被执行的代码,然后编译成本地代码。这些本地代码会被保存在堆外内存的代码缓存区。

Hotspot用这种方式,尽力去选择最合适的方式来权衡编译本地代码的时间和直接解释执行代码的时间。

5>方法区

方法区存储的是每个class的信息,

1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

3.方法区是逻辑概念,永久代和元空间是具体实现,HotPort中jdk1.7以前为永久代,jdk1.8以后是元空间;

所有线程都共享同样的方法区,所以访问方法区的数据和动态链接的过程都是线程安全的。

如果两个线程尝试访问一个类的字段或者方法而这个类还没有加载,这个类就一定会首先被加载而且仅仅加载一次,

这2个线程也一定要等到加载完后才会继续执行。

6>String 的创建分配内存地址

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值