初识JVM

JVM的位置:jre中包含jvm。

双亲委派机制

双亲委派机制:是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。

在IDE中编写的Java源代码会被编译器编译成.class文件,然后再由ClassLoader(类加载器)将这些class文件加载到JVM中执行。

JVM中提供了三层的ClassLoader:

  • Bootstrap classLoader: 主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
  • ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
  • AppClassLoader:主要负责加载应用程序的主函数类。

沙箱安全机制

Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境,沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源的访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏,沙箱主要限制系统资源访问,那系统资源包括什么?CPU,内存,文件系统,网络。不同级别的沙箱对这些资源的访问权限也可以不一样。

所有的java程序运行都可以指定沙箱,可以定制安全策略。

在Java中将执行程序分为本地代码和远程代码两种,本地代码默认视为可信任的,可以访问一切本地资源;而远程代码则被视为不受信任的,对于未授信的远程代码在早期的Java实现中,安全依赖于沙箱机制。

如此严格的安全机制给程序的拓展带来了障碍,当远程代码需要访问本地资源的时候就无法实现。因此在Java1.1中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问。在Java1.2版本中,再次改进了安全机制,不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。

当前最新的安全机制实现,则引入了域(Domain)的概念,虚拟机会把所有的代码加载到不同的系统域和应用域,系统域专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域,对应不一样的权限,存在于不同域的中的类文件,就具有了当前域的全部权限。

沙箱的基本组件

  • 字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范,这样可以帮助Java程序实现内存保护,但并不是所有的类文件都会经过字节码校验,比如核心库。

  • 类装载器(ClassLoader):类装载器在三个方面对Java沙箱起作用。

    • 防止恶意代码去干涉善意代码。
    • 守护被信任的类库边界。
    • 将代码归入保护域,确保代码可以进行哪些操作。

    虚拟机为不同的类加载器载入的类提供了不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护,它们互相之间甚至不可见。

    类装载器采用的机制是双亲委派机制。

    • 由最内层JVM自带的类加载器开始加载,外层恶意同名类得不到加载从而无法使用。
    • 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问的内层类,破坏代码就自然无法实现。
  • 存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制策略的设定,也可以由用户指定。

  • 安全管理器(security manager):是核心API和操作系统之间的主要接口,实现权限控制,比存取控制器优先级高。

  • 安全软件包(security package):java.cecurity下的类和拓展包下的类,允许用户为自己的类增加新的安全特性,包括:

    • 安全提供者
    • 消息摘要
    • 数字签名
    • 加密
    • 鉴别

Native

native:凡是带native关键字的,说明Java的作用范围达不到了。会去调用底层c语言的库,进入本地方法栈,调用本地方法本地接口(JNI)。

JNI作用:拓展Java的使用,融合不同的编程语言为Java所用。

native method stack作用:登记native方法,在(Execution Engine)执行引擎执行的时候加载native libraies(本地库)。

PC计数器

程序计数器:program counter register。

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。

方法区

方法区是被所有的线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,就是所有定义的方法的信息都保存在该区域,此区域属于共享空间

静态变量,常量,类信息(构造方法,接口),运行时的常量池存在方法区中,但是实例变量存在堆内存中,与方法区无关。

(jdk1.8已经将方法区去掉了,将方法区移动到直接内存)

JDK1.8为什么要移除方法区

1)永久代来存储类信息、常量、静态变量等数据不是个好主意, 很容易遇到内存溢出的问题.JDK8的实现中将类的元数据放入 native memory, 将字符串池和类的静态变量放入java堆中. 可以使用MaxMetaspaceSize对元数据区大小进行调整;

2)对永久代进行调优是很困难的,同时将元空间与堆的垃圾回收进行了隔离,避免永久代引发的Full GC和OOM等问题;

栈:数据结构(先进后出),栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存释放。

对于栈来说,不存在垃圾回收问题。

栈:主要存储八大基本数据类型和对象引用。

栈运行原理:栈帧用于存储局部变量表,动态链接,方法出口等信息,方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程。

堆(Heap):一个JVM只有一个堆内存,堆内存的大小是可以调节的。

堆:此内存区域唯一的目的就是存放对象实例。所有的对象实例都在这里分配内存

堆内存中分为三个区域

  • 新生区:

    • 伊甸园区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代),当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
    • 幸存区0区:保留了一次MinorGC过程中的幸存者。
    • 幸存区1区:上一次GC的幸存者,作为这一次GC的被扫描者。
  • 老年区:存放稳定的对象(年龄到达设定的值 ,一般为15)。
  • 永久区:常驻内存的,存放Java运行时的一些环境或类信息(这个区不存在垃圾回收,关闭JVM就释放这个区的内存)。
    • Java17之前:永久代,常量池在方法区;
    • jdk1.7:去永久代,常量池在堆中;
    • jdk1.8:无永久代,常量池在元空间(元空间逻辑上存在,物理上不存在);
  1. package com.jvm;
  2. public class Test02 {
  3. public static void main(String[] args) {
  4. //返回虚拟机试图使用的最大内存
  5. long max = Runtime.getRuntime().maxMemory();//字节
  6. //jvm的初始化总内存
  7. long total = Runtime.getRuntime().totalMemory();
  8. System.out.println("max="+max+"字节\t"+(max/(double)1024/1024)+"M");
  9. System.out.println("total="+total+"字节\t"+(total/(double)1024/1024+"M"));
  10. }
  11. }

工具分析OOM

在一个项目中,出现了OOM,使用的内存分析工具(MAT,Jprofile)。

作用:分析Dump内存文件,快速定位内存泄漏;获得堆中的数据,获得大的对象等。

  1. package com.jvm;
  2. import java.util.ArrayList;
  3. //dump文件
  4. public class Test03 {
  5. byte[] array = new byte[1*1024*1024];
  6. public static void main(String[] args) {
  7. ArrayList<Object> list = new ArrayList<>();
  8. int count = 0;
  9. try{
  10. while (true){
  11. list.add(new Test03());
  12. count+=1;
  13. }
  14. }catch (Exception e){
  15. System.out.println("count:"+count);
  16. e.printStackTrace();
  17. }
  18. }
  19. }

GC算法

GC回收大部分都是在新生区。

什么时候触发GC

​ (1)程序调用System.gc时可以触发

​ (2)系统自身来决定GC触发的时机(根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程)

GC又分为 minor GC 和 Full GC (也称为 Major GC )

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

a.调用System.gc时,系统建议执行Full GC,但是不必然执行

b.老年代空间不足

c.方法区空间不足

d.通过Minor GC后进入老年代的平均大小大于老年代的可用内存

e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

GC常用的算法:引用计数法,标记清除算法,标记压缩算法,复制算法分代收集算法

目前主流的JVM(HotSpot)采用的是分代收集算法。

引用计数法:当一个对象的引用为0时会清除该对象(使用较少)。

复制算法

复制算法:该算法将新生区内存平均分成两部分,每次只使用其中的一部分,当这部分内存快满的时候,将其中的幸存者复制到另一个内存上,将之前的内存清空。

  • 优点:不存在内存碎片,只需移动栈顶指针,按顺序分配内存即可。
  • 缺点:每次只使用一半的内存。
  • 每交换一次年龄加1,到达默认年龄15后,幸存者进入老年区(-XX: -XX:MaxTenuringThreshold=30设置进入老年代的年龄)。

标记清除算法

标记清除法:用在老年代中,为对象存储一个标记位,标记存活的对象,对死亡的对象执行清除操作。

  • 优点:不需要额外的空间,不需要移动对象。
  • 缺点:两次扫描效率比较低(全栈遍历),没有移动对象会产生内存碎片。

标记压缩算法

标记压缩算法是标记清除算法的一个改进版,再次扫描,将存活的对象向一端移动,

  • 优点:没有内存碎片。
  • 缺点:存活的对象较多时,会进行多次的移动操作,效率低。

分代收集算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

GC算法总结

内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)

内存整齐度:复制算法 = 标记压缩算法 > 标记清楚算法(内存碎片)

内存利用率:标记压缩算法 = 标记清除算法 > 复制算法

所以JVM采用分代收集算法!!

JMM

Java memory model:Java内存模型。

JVM简单入门的更多相关文章

  1. Java的多线程 简单入门

    Java的多线程 简单入门 首先能够先搞清楚什么是程序.进程.线程,以及它们之间的关系: 定义: 一 程序仅仅是一组指令的有序集合.它是静态的 二 进程是具有一定独立功能的程序关于某个数据集合上的一次 ...

  2. 【基准测试】JMH 简单入门

    JMH 简单入门 什么是 JMH JMH 是 Java Microbenchmark Harness 的缩写.中文意思大致是 "JAVA 微基准测试套件".首先先明白什么是&quo ...

  3. 用IntelliJ IDEA创建Gradle项目简单入门

    Gradle和Maven一样,是Java用得最多的构建工具之一,在Maven之前,解决jar包引用的问题真是令人抓狂,有了Maven后日子就好过起来了,而现在又有了Gradle,Maven有的功能它都 ...

  4. [原创]MYSQL的简单入门

    MYSQL简单入门: 查询库名称:show databases; information_schema mysql test 2:创建库 create database 库名 DEFAULT CHAR ...

  5. Okio 1.9简单入门

    Okio 1.9简单入门 Okio库是由square公司开发的,补充了java.io和java.nio的不足,更加方便,快速的访问.存储和处理你的数据.而OkHttp的底层也使用该库作为支持. 该库极 ...

  6. emacs最简单入门,只要10分钟

    macs最简单入门,只要10分钟  windwiny @2013    无聊的时候又看到鼓吹emacs的文章,以前也有几次想尝试,结果都是玩不到10分钟就退出删除了. 这次硬着头皮,打开几篇文章都看完 ...

  7. 【java开发系列】—— spring简单入门示例

    1 JDK安装 2 Struts2简单入门示例 前言 作为入门级的记录帖,没有过多的技术含量,简单的搭建配置框架而已.这次讲到spring,这个应该是SSH中的重量级框架,它主要包含两个内容:控制反转 ...

  8. Docker 简单入门

    Docker 简单入门 http://blog.csdn.net/samxx8/article/details/38946737

  9. Springmvc整合tiles框架简单入门示例(maven)

    Springmvc整合tiles框架简单入门示例(maven) 本教程基于Springmvc,spring mvc和maven怎么弄就不具体说了,这边就只简单说tiles框架的整合. 先贴上源码(免积 ...

随机推荐

  1. 利用Docker搭建开发环境

    一. 前言 随着平台的不断壮大,项目的研发对于开发人员而言,对于外部各类环境的依赖逐渐增加,特别是针对基础服务的依赖.这些现象导致开 发人员常常是为了简单从而直接使用公有的基础组件进行协同开发,在出现 ...

  2. 笔记之Utility.DataAccess

    挤出时间看了一些代码,做一些笔记,备忘!!! 现在ORM随处可见,为什么不要已有的ORM而要手动写SQL呢?这肯定是有因为滴,存在必合理嘛! 自认为关于性能.维护.Maybe还有其他的,欢迎大家拍砖! ...

  3. Helium文档4-WebUI自动化-write写入

    前言 write方法是模拟在输入框中写入数据 write入参说明 def write(text, into=None):   """   :param text: The ...

  4. 如何理解码分复用中的码分多址CDMA?

    如何理解CDMA? 推荐参考大神文章 https://blog.csdn.net/dog250/article/details/6420427 (码分多址(CDMA)的本质-正交之美) 首先我们先看& ...

  5. 技术实操丨HBase 2.X版本的元数据修复及一种数据迁移方式

    摘要:分享一个HBase集群恢复的方法. 背景 在HBase 1.x中,经常会遇到元数据不一致的情况,这个时候使用HBCK的命令,可以快速修复元数据,让集群恢复正常. 另外HBase数据迁移时,大家经 ...

  6. Java接口的初始化

    背景 接口与类真正有所区别的是前面讲述的四种"有且仅有"需要开始初始化场景中的第三种:当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全 ...

  7. Spring 事务失效

    隔离级别 在 TransactionDefinition.java 接口中,定义了"四种"的隔离级别枚举: /** * [Spring 独有]使用后端数据库默认的隔离级别 * * ...

  8. Python 从入门到精通:一个月就够了

    毫无疑问,Python 是当下最火的编程语言之一.对于许多未曾涉足计算机编程的领域「小白」来说,深入地掌握 Python 看似是一件十分困难的事.其实,只要掌握了科学的学习方法并制定了合理的学习计划, ...

  9. 用node.js给C#写一个数据表的实体类生成工具

    虽然微软提供了T4模板,但是我感觉非常难用.哪儿比得上直接用脚本来写模板来的爽. 因为要给一个老项目做周边的工具,需要连接到数据库. 我习惯性用EntityFrameworkCore来做,因为毕竟从出 ...

  10. ssm整合之springmvc.xml文件

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...