赞
踩
java8API地址https://docs.oracle.com/javase/8/docs/api/
jvm8 地址https://docs.oracle.com/javase/specs/jvms/se8/html/
(1)通过一个类的全限定名获取定义此类的二进制字节流。
(2)将字节流所代表的静态存储结构转为方法区的运行时数据结构。
(3)在内存中生成代表此类的java.lang.Class对象,作为方法区中这个类的访问入口。
**确保字节流文件信息符合虚拟机规范,**保证类加载准确性,有格式、元数据、字节码验证等。
为类变量分配内存,初始化零值。(Static修饰的变量)
这里不包含final修饰的static,因为final修饰的会在编译时就分配了。准备阶段显示初始化。
类变量分配在方法区,实例变量随对象一起分配在堆内存。这里不会为实例变量分配初始化。
将常量池中的符号引用转为直接引用。
执行类构造器方法()的过程。此方法不需要定义,时虚拟机视角下的。此时会给类变量显示赋值。
一个类只能被加载一次。会被同步加锁。
什么情况下使用
类加载器收到类加载请求,首先向上找其父类,直到找到引导类,看起是否可以加载,不能则从引导类往下的子类进行尝试。
这样做的目的是类的加载首先去java核心类库中找是否有,核心类库的优先级最高,依次往下。
保证核心API源代码的保护,引导类加载器只加载核心API,不加载自定义的与核心API同名或同包名的API。
jvm中比较两个calss是否为同一类,存在两个必要条件:
灰色的PC程序计数器、本地方法栈、虚拟机栈是属于线程私有的。
红色的方法区和堆是属于进程的,同一个进程内的线程共享该内存。
线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行执行。
在Hotspot中,java线程与操作系统的本地线程是有直接映射关系。一一对应。
作用: 用来存储指向下一条指令的地址。行号的表示。
生命周期 : 线程私有,生命周期与线程一致。
执行:任何时间,一个线程只执行一个方法,程序计数器会保存当前正在执行方法的JVM指令地址。若执行本地方法则为空。
无OutMemoryError情况:Java虚拟机规范中唯一一个不会内存溢出的内存。
由栈帧数据结构组成。一个栈帧代表一个方法。
变量类型分类
按照数据类型分类 : 基本数据类型、引用数据类型
按照变量声明位置: 成员变量(类变量和实例变量)使用前都进行默认初始化赋值、局部变量。
long 和double数据类型占两个栈单位深度。
堆内存分为:新生代、老年代。二者内存默认占比1:2。
新生代:Eden区、from区、to区。三者内存的默认占比 8:1:1.
堆内存中有一个TLAB区是线程私有的,内存很小,对象分配内存优先在TLAB中,放不下在去Eden区,更大的则去老年代。
对象可分配的内存空间:堆内存和栈内存。
什么时候分配在栈内存中?
通过内存逃逸分析,将未逃逸的对象在栈上分配。
对象的作用域仅在当前发方法中有效。
默认逃逸分析是打开的。只有Server模式的虚拟机才有,64位虚拟机默认是Server的。
开启逃逸分析的命令:-XX:+DoEscapeAnalysis
关闭逃逸分析的命令:-XX:-DoEscapeAnalysis
发生逃逸的情况:将对象return返回到,调用的方法外的对象等。可能被方法外使用的对象都被视为逃逸。
栈上内存分配的优点:1. 执行时间短,2.没有发生GC。
允许将对象拆分成标量,分配在栈上(就是当对象未发生逃逸,使用局部变量替换对象属性)。
默认标量替换是开启的。
开启标量替换的命令:-XX:+EliminateAllocations
关闭标量替换的命令:-XX:-EliminateAllocations
标量:无法在分解的数据。
聚合量:可分解的数据。
java中的基本数据类型就是标量,类就是聚合量。
oracle的Hotspot虚拟机中的逃逸分析采用的就是标量替换,其实对象还是分配在堆中。
与java堆一样,也是线程共享的内存区域。
与java堆一样,方法区在jvm启动时创建,可以是不连续的内存空间。
与java堆一样,大小可以选择固定或者可扩展的。
方法区的大小决定系统可以保存多少个类。堆大小决定系统可以保存多少个对象。
方法区在逻辑上属于堆的一部分,但是其简单实现可能不会选择进行垃圾回收或者压缩。对于Hotspot虚拟机,方法区被称为非堆。
在jdk1.7之前hotspot采用虚拟机中设置的内存(又被称为永久代),容易出现oom,其受限于虚拟机内存。jdk1.8开始采用元空间代替永久代,元空间是系统内存,不占用虚拟机设置的内存,不容易出现oom,其受限于本地内存。
jdk1.7之前的设置虚拟的方法区大小命令:-XX:MaxPermSize=10m -XX:MaxPermSize=10m。虚拟机默认方法区大小32位机是64M,64位机是82M.超过这个最大值则会抛出OOM。
jdk1.8开始设置虚拟机的方法区大小命令:-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m。默认值依赖平台系统,window下是21M,-XX:MaxMetaspaceSize默认是-1,没有大小限制。
MetaspaceSize建议设置的大一些。一旦方法区的分配大小触及MetaspaceSize的值,就会在方法区触发Full GC对没有用的类进行回收(这些类的类加载器不再存活)。若MetaspaceSize设置过低,会导致频繁进行FULL GC。建议根据实际项目需求,检测其类多少,设定MetaspaceSize较高一些。
用来存储虚拟机加载了的类型信息(类信息、接口、注解、枚举等)、运行时常量池、静态变量、即时编译器JIT编译后的代码缓存等。
常量池和运行时常量池
常量池:是字节码中的一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等类型。虚拟机位每个已加载的类型(类或者接口)都维护一个常量池表,该表中数据项和数组一样,通过索引去访问。
运行时常量池:类加载器将字节码中常量池加载到方法区,将间接引用和符号引用转为直接引用,就是运行时常量池。
JDK1.7之前将字符串常量池放在永久代,永久代回收效率低,只有在full GC时才会触发,儿full GC只有在老年代不足、永久代不足时才会触发。
我们开发过程中会有大量创建字符串,回收效率低导致永久代内存不足,所以把其放到堆中能及时回收(放在新生代)。
静态变量随Class实例对象放在堆中,实例变量随实例对象放在堆中,局部变量随方法放在栈帧中的局部变量表中。
方法区常量池中的垃圾回收:只要常量没有被任何地方引用,就可以被回收。
方法区中类型信息的垃圾回收:不在被使用的类,就可以被回收。但是需要满足三个苛刻的条件(1.该类所有实现对象都被回收,即堆中不存在该类的对象和其子类的对象;2.该类的类加载器被回收(通常很难实现);3.该类的Class没有在任何地方引用,不能在任何地方有通过反射访问该类的方法)。
OOM不同于Exception,OOM可能是代码没有问题,是虚拟机内存分配大小的问题。
分析OOM的问题,一般是heap space堆内存,需要先判断是内存泄漏还是内存溢出。
实例对象在堆中主要有两部分,对象头(运行时元数据、类型指针),实例数据。
java的对象保存的堆上,访问定位通过栈帧上局部变量表中的引用变量(记录了对象的内存地址)去访问。
对象的两种访问方式
方法一:句柄访问,在堆内存中会专门开辟一个空间,用来存放句柄(映射栈帧中引用变量和堆中对象的地址)。该方式的缺点(浪费内存,访问效率低),优点(在内存回收,整理内存空间时,移动对象,堆栈帧中引用变量的映射地址不需要改变,栈帧中引用变量的映射地址比较稳定。)
方法二:直接地址,栈帧中引用变量的映射地址就是堆中对象的内存地址。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。