JVM理解:java特点、类的加载、双亲委派机制、JVM结构、java内存模型、堆栈的区别

1 java特点

  • 平台无关性(跨平台)
  • GC
  • 语言特性
  • 面向对象
  • 类库
  • 异常处理

跨平台如何实现
在这里插入图片描述
为什么JVM不直接将源码解析成机器码去执行呢
因为如果这样的话每次执行都需要各种检查,每次执行时语义的分析结果都不会被保留下来,要做重新编译,重新分析。这样整体的性能就会受到影响。因此出现中间过渡的字节码,可以生成.class文件后多次执行程序,不用进行各种校验。同时也可以将别的语言转换成字节码,让JVM调用执行,增强了平台的兼容扩展能力。

2 JVM如何加载.class文件

在这里插入图片描述
通过ClassLoader将class文件加载到内存(Runtime Data Area)再通过Executor Engine 对其字节码进行解析,最后提交个操作系统,由操作系统去执行

3 什么是反射

是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
例如
在这里插入图片描述
在这里插入图片描述

4 类的加载方式

4.1ClassLoader

在这里插入图片描述
ClassLoader种类

  • 启动类加载器(Bootstrap ClassLoader):加载核心库,这个类加载器负责将存放在<JAVA_HOME>\lib目录中的。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那直接使用null代替即可。
  • 扩展类加载器(Extension ClassLoader):加载扩展库,这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器
  • 自定义加载器(Customer ClassLoader)

4.2 类的双亲委派机制

在这里插入图片描述
当一个Hello.class这样的文件要被加载时。若不考虑我们自定义类加载器,首先会在AppClassLoader中通过findLoaderedClass()判断检查是否加载过,如果有那就直接放回。如果没有,那么会拿到父加载器,然后同样判断父加载器是否加载过,若还是没有,那么继续往上。到达Bootstrap classLoader后若还是没有,会判断Bootstrap classLoader能否加载,若不能,继续逐级往下。

双亲委派机制作用
1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

4.3 类从编译到执行的过程

在这里插入图片描述

4.4 LoadClass与ForName区别

隐式加载:new(隐式调用类加载器,将类加载进jvm中)
显示加载:LoadClass,ForName
首先需要了解类的装载过程
在这里插入图片描述
Classloader.loadClass得到的类时没有链接的(只完成第一步)
class.forName则是已经初始化完的
为什么需要两种方法呢?
1、比如在使用java连接数据库的时候需要加载驱动,这个驱动类中有一个static方法需要被执行。这个时候就只能用class.forName,因为要进行第三步初始化
2、比如在Spring容器中使用的延迟加载机制,先加载所有的类而不初始化,等到要用的时候再初始化,提升了效率。

5 java的内存模型

JDK8
在这里插入图片描述

5.1线程独占部分

  • 程序计数器

在这里插入图片描述

  • 虚拟机栈

在这里插入图片描述
在这里插入图片描述
为什么递归的时候可能出现java.StackOverflowError异常
例如
在这里插入图片描述
每次递归都会向栈中压入一个栈帧,当递归次数过多,超多了栈的最大深度就会发生异常。解决的方法有:降低递归次数,或者使用循环的形式。

java.lang.OutOfMemoryError异常
当虚拟机栈可以动态扩展时,无法从操作系统获取足够多的内存,就会报该错。
在windows系统执行该代码可能导致系统假死。
在这里插入图片描述

  • 本地方法栈
    与虚拟机栈类似,主要作用于标注了native的方法

5.2 线程共享部分

元空间(MetaSpace)与永久代(PermGen)
区别
元空间使用本地内存,永久代使用JVM的内存
以下异常将不会出现(本地内存有多打空间就能有多大不存在溢出的问题,但是也不会放任空间无限壮大,一般是根据需要动态调整)
在这里插入图片描述
在java8之后使用元空间替代了永久代
那么元空间的优势在哪呢?

  • 若字符串常量池存放在永久代中可能造成性能问题和内存溢出
  • 对于类和方法的大小信息难以确定,所以对于永久代大小的指定会有困难
  • 永久代会为GC带来不为必要的复杂度(每种类型的垃圾回收器都需要对其做特殊的处理)
  • 方便HotSpot与其他JVM如Jrockit的集成(oracle)

java堆(heap)该部分在javaGC部分笔记中有所记录 (看这里
在这里插入图片描述

6 java内存模型中堆和栈的区别

6.1 内存分配策略

按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.

  • 静态存储分配是指在编译时就能确定标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为 它们都会导致编译程序无法计算准确的存储空间需求.
  • 栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
  • 堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆 由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.

6.2 堆栈区别及关系

联系
对象、数组,都保存在堆中,在引用它们的时候需要在栈中定义变量保存他们在堆中的首地址。如图:
在这里插入图片描述
区别

  • 管理方式 :栈自动释放内存,堆需要GC回收
  • 空间大小:栈比堆小(堆一般用来存对象所以需要的空间更大)
  • 碎片相关:栈产生的碎片远远小于堆(垃圾回收器回收堆空间并不是及时的频繁的内存分配可能导致内存碎片逐渐的累计。内存本就是一个堆栈的数据结构,它们的操作都是一一对应的,并且栈帧并不像堆中对象那样结构复杂,所以栈产生的碎片远远小于堆)
  • 分配方式:栈支持动态分配(又编译器分配)和静态分配,堆只能动态分配(运行时的分配)
  • 栈的效率高内存块本身使用的就是堆栈的结构,使栈空间和底层结构更加符合,操作更加简单(入栈、出栈)。堆结构比较复杂但是灵活度高(大概率为双向链表)

元空间、堆、线程独占部分之间的联系–从内存角度分析
在这里插入图片描述

关于intern方法

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值