初识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:无永久代,常量池在元空间(元空间逻辑上存在,物理上不存在);
package com.jvm;

public class Test02 {
public static void main(String[] args) {
//返回虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory();//字节
//jvm的初始化总内存
long total = Runtime.getRuntime().totalMemory();
System.out.println("max="+max+"字节\t"+(max/(double)1024/1024)+"M");
System.out.println("total="+total+"字节\t"+(total/(double)1024/1024+"M"));
}
}

工具分析OOM

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

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

package com.jvm;

import java.util.ArrayList;
//dump文件
public class Test03 {
byte[] array = new byte[1*1024*1024]; public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
int count = 0;
try{
while (true){
list.add(new Test03());
count+=1;
}
}catch (Exception e){
System.out.println("count:"+count);
e.printStackTrace();
} }
}

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. buuctf-misc-[BJDCTF 2nd]圣火昭昭-y1ng 1

    开局一张图片,flag全靠猜,那这个是不是和outguess工具有关呢?于是我们显示查看了图片的详细信息 看到是新佛曰,于是我们用新佛曰论禅解密:http://hi.pcmoe.net/buddha. ...

  2. 【机器学习 Azure Machine Learning】使用VS Code登录到Linux VM上 (Remote-SSH), 及可直接通过VS Code编辑VM中的文件

    问题描述 在平常的工作习惯中,如果使用VS Code做脚本的开发,是一个非常好用的工具,现在也可以通过VS Code的不同方式来连接到Linux VM中(ssh), 第一种是VS Code的Termi ...

  3. mac 搭建 Robot Framework

    前提介绍,我的mac上python2和python3是都要有的,然后大家可以看看我其他的文章,这些文章虽然很多都是连接,是别人的博客或者资料,但都是自己试过没有问题的,只是比较懒然后就没有自己写. r ...

  4. CDH+Kylin三部曲之三:Kylin官方demo

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. liunx 免密登录远程主机

    #!/bin/bash #Program: # no password login in hosts #History: # hbl 2017/12/9 1.0.0v function auto-lo ...

  6. Redis学习笔记(六)——数据结构之Set

    一.介绍 Redis的Set是string类型的无序集合.集合成员是唯一的,这就意味着集合中不能出现重复的数据. Redis中集合是通过哈希表实现的,所以添加.删除.查找的复杂度都是O(1). 集合中 ...

  7. C 语言编程习惯总结

    笔者能力有限,如果文中出现错误的地方,还请各位朋友能够给我指出来,我将不胜感激,谢谢~ 引言 编程习惯的培养需要的是一个长期的过程,需要不断地总结,积累,并且我们需要从意识上认识其重要性,一个良好的编 ...

  8. 面试官:看你简历说写精通ThreadLocal,这几道题你都会吗?

    问题 和Synchronized的区别 存储在jvm的哪个区域 真的只是当前线程可见吗 会导致内存泄漏么 为什么用Entry数组而不是Entry对象 你学习的开源框架哪些用到了ThreadLocal ...

  9. Gym102012A Rikka with Minimum Spanning Trees

    题意 \(T\) 组数据,每组数据给定一个 \(n\) 个点,\(m\) 条边,可能含有重边自环的图,求出最小生成树的个数与边权和的乘积,对 \(10^9+7\) 取模. \(\texttt{Data ...

  10. 【事件中心 Azure Event Hub】Event Hub日志种发现的错误信息解读

    问题描述 使用Event Hub消费事件时,出现的各种客户端错误的解读.(再后期遇见新的错误信息,会持续添加进此说明) 一:再Linux中运行Event Hub消费端程序,出现Too many ope ...