首页 技术 正文
技术 2022年11月15日
0 收藏 643 点赞 3,852 浏览 8067 个字

从java编程语言说起。。。

1. Java编程语言简介

1.1 编程语言概述

  • 系统级和应用级

    • 系统级:C,C++,go,erlang
    • 应用级:C#,Java,Python,Perl,Ruby,php
      • 虚拟机:jvm(java虚拟机)、pvm(python虚拟机)
      • 动态网站:asp.net,jsp
  • 编程语言的类别:(程序=指令+数据)
    • 面向过程:以指令为中心,围绕指令组织数据
    • 面向对象:以数据为中心,围绕数据组织指令
  • 动态网站
    • 客户端动态
    • 服务器动态:CGI
  • webapp server
    • jsp:tomcata,jboss,jetty
    • php:php-fpm

1.2 Java编程语言

1.2.1 Java的特性

  • 面向对象(完全面向对象)、多线程、结构化错误处理
  • 垃圾收集、动态链接、动态扩展

1.2.2 JVM、JRE、JDK

JVM(Java Virtual Machine),Java虚拟机。
它只能识别 .class类型的文件,能够将class文件中的字节码指令进行识别并调用操作系统上的API完成动作。JVM是Java能够跨平台的核心。

JRE(Java Runtime Environment),Java运行时环境。
它主要包含两个部分:jvm的标志实现、java的一些基本类库。它相对于jvm来说,多出来的是一部分的java类库。

JDK(Java Development Kit),Java开发工具包。
JDK是整个java开发的核心,它集成了JRE和一些好用的小工具,如javac、java、jar等。

三者的嵌套关系:JDK > JRE > JVM

1.2.3 Java的执行过程

JVM内存结构详解

  • Java体系结构

    • Java编程语言
    • Java Class文件格式
    • Java API
    • Java VM
  • JVM的核心组成部分
    • Class Loader(类加载器)
    • 执行引擎

1.2.4 Java的三个技术流派

  • J2SE ==> Java 2 SE
  • J2EE ==> Java 2 EE
  • J2ME ==> Java 2 ME

1.3 Java 2 EE 平台

1.3.1 Servlet

Servlet是运行在web服务器上的程序,它是作为http客户端的请求和http服务器上数据库或应用程序之间的中间层,与CGI所实现的效果类似。

Servlet Container:将html标签硬编码在应用程序中,如println(“<h1>”)

1.3.2 JSP

JSP(Java Server Pages),是JavaWeb服务器端的动态资源,与html页面作用类似,显示数据和获取数据。

JSP=html+Java脚本(代码片段)+JSP动态标签

JSP图示:(JSP能自动将代码转换为Servlet格式)

JVM内存结构详解

1.3.3 Java Bean

Java Bean一般情况下指的是实体类,所有属性为private,提供默认构造方法和getter,setter,如果一个JavaBean需要在不同的JVM的进程中进行传递,还需要实现Serializable接口。

1.3.4 EJB

EJB(Enterprise JavaBean),企业级JavaBean,普通Java Bean 的区别是,JavaBean的使用可以不需要容器,EJB的运行一般需要EJB容器(即应用服务器,如JBoss/Weblogic/Websphere…

1.3.5 JMS

JMS(Java Message Service),Java消息服务接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

Java消息服务的规范包括两种消息模式,点对点和发布者/订阅者。

1.3.6 JMX

JMX(Java Management Extensions),是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,用户可以在任何Java应用程序中使用这些代理和服务实现管理。

1.4 Web Container

1.4.1 包含三个容器

  • JDK
  • Servlet
  • JSP

图示:

JVM内存结构详解

1.4.2 实现

  • 商业实现:

    • WebSphere(IBM)
    • WebLogic (BEA –> Oracle)
    • Oc4j
    • Glassfish
    • Geronimo
    • JOnAS
    • JBoss
  • 开源实现:
    • Tomcat
    • jetty
    • resin

2. JVM运行时区域简介

2.1 JVM运行时区域图示

运行为多个线程,这些是为这些线程所使用的
OutOfMemoryError(OOM)内存溢出

JVM内存结构详解

2.2 Java内存结构

  • Java虚拟机在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域,每个区域都有各自的作用
  • 分析JVM内存结构,主要就是分析JVM运行时数据存储区域
  • JVM的运行时数据区主要包括:堆、栈、方法区、程序计数器
  • JVM的优化问题主要在线程共享的数据区中:堆、方法区

图示:

JVM内存结构详解

2.3 各区域简介

2.3.1 方法区(共享)

  • 线程共享的内存区域
  • 用于存储被虚拟加载的类信息、常量、静态变量等
  • 该区域也被称为:永久代(或者叫非堆内存区域)

2.3.2 堆(共享)

  • Java堆是jvm所管理的内存中最大的一部分,主要包含了java运行中所存放的对象
  • Java堆也是GC管理的主要区域,主流的算法都基于分代收集算法方式进行:
    • 新生代

      • Eden Space
      • Survivor Space 存活区
    • 老年代(Tenured Gen)
  • 堆内存是共享的,所有的java线程共享的内存局域
  • 堆中存放的是类实例化出来的对象

2.3.3 Java栈(线程私有)

  • 每一个线程都有自己的栈,线程启动起来就自动给它创建一个栈,这个虚拟机栈中描述的是java方法执行的内存模型
  • 每个方法被执行时,都会给它创建一个栈帧,用于存储线程自己的局部变量
  • Java栈内存为线程私有,存放线程自己的局部变量等信息

2.3.4 PC寄存器(Program Counter Register)(线程私有)

  • 程序计数器,线程独占的内存空间
  • 一般是非常小的内存空间

2.3.5 本地方法栈(线程私有)

  • Native Method Stacks
  • 它不是通过虚拟机栈为虚拟机执行java方法,而是为虚拟机所使用到的本地方法提供服务
    • 这里的本地方法对于Windows和Linux是不同的,它表明的是它真正能够在那个主机上所能执行的特有方法
    • 所以具体的实施方案是依赖于平台的

2.3.6 GC 垃圾回收器(面向于堆内存空间)

  • 主要目的就是回收那些不用了的对象

2.4 运行时数据区域图示

JVM内存结构详解

3. 栈(JVM栈 & 本地方法栈)

JVM中的栈包括Java虚拟机栈本地方法栈

  • Java虚拟机栈为JVM执行Java方法服务
  • 本地方法栈则为JVM使用到的Native方法服务

3.1 本地方法栈

  • JDK中有很多方法是使用Native修饰的,Native方法不是以Java语言实现的,而是以本地语言实现的(比如C或C++)
  • Native方法是与操作系统直接交互的,比如通知垃圾收集器进行垃圾回收的代码System.gc(),就是使用native修饰的

3.2 JVM栈

3.2.1 栈为何物?

  • 对栈的定义:

    • 限定仅在表头进行插入和删除操作的线性表
  • 栈的特性:
    • 压栈(入栈)和弹栈(出栈)都是对栈顶元素进行操作的,所以栈是先进后出的
    • 栈是线程私有的,它的生命周期与线程相同,每个线程都会分配一个栈的空间,即每个线程又有独立的栈空间

图示:JVM内存结构详解

3.2.2 栈中存储的是神马?

  • 栈帧是栈的元素,每个方法在执行时都会创建一个栈帧
  • 栈帧中存储了局部变量表、操作数栈、动态链接方法出口等信息
  • 每个方法从调用到运行结束的过程,就对应着一个栈帧在栈中压栈到出栈的过程

图示:

JVM内存结构详解

3.2.3 JVM栈中存储的内容详解

3.2.3.1 局部变量表

  • 栈帧中,由一个局部变量表存储数据

    • 局部变量表中存储了基本数据结构(boolean、byte、char、short、int、float、long、double)的局部变量(包括参数)
    • 和对象的引用(String、数组、对象等),但是不存储对象的内容
  • 局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小
  • 局部变量的容量以变量槽(Variable Slot)为最小单位,每个变量槽最大存储32位的数据类型
    • 对于64位的数据类型(long、double),JVM会为其分配两个连续的变量槽来存储
  • JVM通过索引定位的方式使用局部变量表,索引的范围从0开始至局部量表表中最大的Slot数量
    • 普通方法与static方法在第0个槽位的存储有所不同,非static方法的第0个槽位存储方法所属对象实例的引用

图示:

JVM内存结构详解

3.2.3.2 局部变量表的Slot复用:

1. 为了尽可能的节省栈帧空间,局部变量表中的Slot是可以复用的

  • 方法中定义的局部变量,其作用域不一定会覆盖整个方法
  • 当方法运行时,如果已经超出了某个变量的作用域,即变量失效了,那这个变量对应的Slot就可以交给其他变量使用

2. Slot复用示例:

public void test(boolean flag)
{
if(flag)
{
int a = 66;
}
int b = 55;
}

  • 过程分析:

    • 当虚拟机运行test方法时,就会创建一个栈帧,并压入到当前线程的栈中
    • 当运行到int a=66时,在当前栈帧的局部变量中创建一个Slot存储变量a,
    • 当运行到int b=55时,此时已经超出变量a的作用域了(变量a的作用域在{}所包含的代码块中)
      • 此时a就失效了,变量a占用的Slot就可以交给b来使用,这就是Slot复用
    • Slot复用虽然节省了栈帧空间,但是会伴随一些额外的副作用,Slot的复用会直接影响到系统的垃圾收集行为

3. Slot复用的原理分析:

写法一:

public class TestDemo {    public static void main(String[] args){        byte[] placeholder = new byte[64 * 1024 * 1024];        System.gc();
}
}

  • 分析:

    • 上述代码先向内存中填充了64M的数据,然后通知虚拟机进行垃圾回收
    • 虚拟机没有回收这64M内存,
    • 因为执行System.gc()方法时,变量placeholder还在作用域范围内,
    • 虚拟机是不会回收的,它还是有效的

写法二:

public class TestDemo {    public static void main(String[] args){
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
System.gc();
}
}

  • 分析:

    • 当运行到System.gc()方法时,变量placeholder的作用域已经失效了

      • 但是虚拟机还是没有回收placeholder变量占用的64M内存
    • 因为虽然限定了placeholder的作用域,但是之后并没有任何对局部变量表的读写操作
      • placeholder变量在布局变量表中占用的Slot没有被其他变量所复用
      • 所以作为GC Roots一部分的局部变量表仍然保持着对它的关联,所以placeholder变量没有被回收

写法三

public class TestDemo {    public static void main(String[] args){
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
int a = 0;
System.gc();
}
}

  • 分析:

    • 此时placeholder变量占用的64M内存空间被回收了
    • 运行到int a=0时,已经超过了placeholder变量的作用域,此时placeholder在局部变量表中占用的Slot可以交给其他变量使用
      • 而变量a正好复用了placeholder占用的Slot,至此局部变量表中的Slot已经没有placeholder的引用了
      • 虚拟机就回收了placeholder占用的64M内存空间

placeholder变量能否被回收的关键:

  • 局部变量表中的Slot是否还存有关于placeholder对象的引用

3.2.3.3 操作数栈:

  • 操作数栈是一个先进先出栈,操作数栈的元素可以是任意的Java数据类型
  • 方法刚开始执行时,操作数栈是空的,在方法执行过程中,通过字节码指令对操作数栈进行压栈和出栈的操作
  • 通常进行算术运算的时候是通过操作数栈来进行的,又或者是在调用其他方法的时候通过操作数栈进行参数传递
  • 操作数栈可以理解为栈帧中用于计算的临时数据存储区

3.2.3.4 栈中可能出现的异常

  • StackOverflowError:栈溢出错误

    • 如果一个线程在计算时所需要用到栈大小  >  配置允许最大的栈大小,
    • 那么Java虚拟机将抛出StackOverflowError
  • OutOfMemoryError:内存不足
    • 栈进行动态扩展时如果无法申请到足够内存,会抛出OutOfMemoryError异常

3.2.3.5 设置栈参数:

  • 使用-Xss设置栈大小,通常几百k就够用了
  • 由于栈是线程私有的,线程数越多,占用栈空间越大
  • 栈决定了函数调用的深度,这也是慎用递归调用的原因
    • 递归调用时,每次调用方法都会创建栈帧并压栈
    • 当调用一定次数之后,所需栈的大小已经超过了虚拟机运行配置的最大栈参数,就会抛出StackOverflowError异常

4. Java堆(Java Heap)

4.1 Java堆概述

4.1.1 java堆简介

  • 堆是java虚拟机所管理的内存中最大的一块存储区域,堆内存被所有线程共享
  • 堆中主要存放的是使用new关键字创建的对象,所有对象实例以及数组都要在堆上分配
  • 垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间(收集的是对象占用的空间而不是对象本身)

4.1.2 java堆的空间

  • 年轻代(Young Generation)

    • 伊甸园(Eden区)
    • 幸存区(Survivor区)
      • From Survivor空间(S0,存活区1)
      • To Survivor空间(S1,存活区2)
  • 老年代(Old Generation)
  • 永久代(Permanent Generation)

4.2 Java堆的参数设置

4.2.1 Java堆内存图示

4.2.2 Java堆内存空间的调整参数

4.3 垃圾回收(GC)

4.3.1 年轻代和老年代的内存清理机制

  • 年轻代:mark(标记)—> compact(打包)—> 清除

    • 年轻代存储“新生对象”,新创建的对象存储在年轻代中
    • 当年轻代内存占满之后,会触发Minor GC,清理年轻代内存空间
  • 老年代
    • 老年代存储长期存活的对象和大对象
    • 年轻代中存储的对象,结果多次GC后仍然存活的对象会移动到老年代中存储
    • 老年代空间占满后,会触发Major GC(Full GC)
  • Full GC
    • Full GC是清理整个堆空间,包括年轻代和老年代
    • 如果Full GC之后,堆中仍然无法存储对象,就会抛出OutOfMemoryError异常

4.3.2 Java中垃圾回收的过程

4.3.2.1 Eden区

  • 当一个实例被创建了,首先会被存储在堆内存年轻代的Eden区中

4.3.2.2 Survivor区(S0和S1)

  • 作为年轻代GC(Minor GC)周期的一部分,存活的对象从Eden区被移动到Survivor区的S0中

    • 类似的,垃圾回收器会扫描S0,然后将存活的实例移动到S1中
  • 死亡的实例被标记为垃圾回收
    • 根据垃圾回收器选择的不同,要么被标记的实例都会不停的从内存中移除,要么回收过程会在一个单独的进程中完成

4.3.2.3 老年代(Old or tenured generation)

  • 老年代是内存中的第二块逻辑区

    • 当垃圾回收器执行Minor GC周期时,在S1 Survivor区中的存活实例将会被晋升到老年代,而未被引用的对象被标记为回收
  • 老年代GC(Major GC)
    • Major GC扫描老年代的垃圾回收过程
    • 相对于Java垃圾回收过程,老年代是实例生命周期的最后阶段
    • 如果实例不再被引用,那么它们会被标记为回收,否则它们会继续留在老年代中

4.3.2.4 内存碎片

  • 一旦实例从堆内存中被删除,其位置就会变空并且可用于未来实例的分配
  • 这些空出的空间将会使整个内存区域碎片化,为了实例的快速分配,需要进行碎片整理
  • 基于垃圾回收器的不同选择,回收的内存区域要么被不停地被整理,要么在一个单独的GC进程中完成

4.3.3 Minor GC和Full GC的触发条件

  • Minor GC触发条件

    • 当Eden区满时,触发Minor GC
  • Full GC触发条件
    • 调用System.gc时,系统建议执行Full GC,但是不一定执行
    • 老年代空间不足
    • 方法区空间不足
    • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    • 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

4.3.4 JVM下发生的停顿现象

4.3.4.1 Stop The World

  • Java中Stop-The-World机制简称STW

    • 是在执行垃圾收集算法时,Java应用程序的其他线程都被挂起(除了垃圾收集帮助器之外)
  • Java中一种全局暂停现象,全局停顿,
    • 所有Java代码停止,native代码可以执行,但不能与JVM交互,这些现象多半是由于gc引起

4.3.4.2 VM Thread(JVM里的特殊线程)

  • 专门用来执行一些特殊的VM Operation,比如分派GC,thread dump等

    • 这些任务,都需要整个Heap,以及所有线程的状态是静止的,一致的才能进行
    • 所以JVM引入了安全点(Safe Point)的概念,想办法在需要进行VM Operation时,通知所有的线程进入一个静止的安全点
  • 除了GC,其他触发安全点的VM Operation包括:
    1. JIT相关,比如Code deoptimization, Flushing code cache
    2. Class redefinition (e.g. javaagent,AOP代码植入的产生的instrumentation)
    3. Biased lock revocation 取消偏向锁
    4. Various debug operation (e.g. thread dump or deadlock check)

4.3.5 于Tomcat而言

  • catalina.sh中有两个环境变量:

    • CATALINA_OPTS:仅对启动运行tomcat实例的java虚拟机有效
    • JAVA_OPTS:对本机上的所有java虚拟机有效

5. PC寄存器 & 方法区

5.1 程序计数器(PC寄存器)

  • 程序计数器(Porgram Counter Register)是一块较小的内存空间

    • 可以看做是当前线程所执行字节码的行号指示器,指向下一个将要执行的指令代码,由执行引擎来读取下一条指令
  • 线程的执行与程序计数器:
    • 一个线程的执行,是通过字节码解释器改变当前线程的计数器的值,来获取下一跳需要执行的字节码指令,从而确保线程的正确执行
    • 为了确保线程切换后(上下文切换)能恢复到正确的执行位置,每个线程都有一个独立的程序计数器,各个线程的计数器互不影响,独立存储
  • 程序计数器记录的内容:
    • 如果线程执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址
    • 如果执行的是Native方法,计数器值为Undefined
  • 注意:
    • 程序计数器是线程私有的内存
    • 程序计数器不会发生内存溢出(OutOfMemoryError即OOM)问题

5.2 方法区

5.2.1 方法区(Method Area)

  • 方法区同Java堆一样是被线程共享的区域

    • 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码
  • 静态变量+常量+类信息(版本、方法、字段等)+运行时常量池存在方法区中
    • 该区域也被称为:永久代(或者叫非堆内存区域)
  • 常量池是方法区的一部分
  • 注意:
    • JDK1.8 使用元空间 MetaSpace 替代方法区,元空间并不在 JVM中,而是使用本地内存
    • 元空间两个参数:
      • MetaSpaceSize:初始化元空间大小,控制发生GC阈值
      • MaxMetaspaceSize : 限制元空间大小上限,防止异常占用过多物理内存

5.2.2 常量池

  1. 常量池中存储编译器生成的各种字面量和符号引用

    • 字面量:字面量就是Java中常量的意思,比如文本字符串,final修饰的常量等
    • 符号引用:符号引用包括类和接口的全限定名,方法名和描述符,字段名和描述符等
  2. 常量池的作用
    • 优点:常量池避免了频繁的创建和销毁对象而影响系统性能,实现了对象的共享
  3. 基本数据类型比较的是数值,而引用数据类型比较的是内存地址

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,498
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,911
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,745
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,499
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,138
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,302