sssanay
Главная страница
Статьи о создании сайта
Статьи о раскрутке сайта
Учебник по Java Script
Юмор
ssss
 
 
 
 

Язык и архитектура Java

Итоги

Освещены следующие стороны Java как объектно-ориентированного языка программирования.
 

  • Классы определяют шаблон, по которому создаются конкретные объекты
  • Поля данных объекта определяют состояние объекта
  • Объекты обмениваются сообщениями между собой. Получение сообщения приводит к вызову одного из методов
  • Методы определяют поведение объекта данного класса. Методы для разных классов могут иметь одно и то же имя, но различное содержание.

 

Нейтральный к архитектуре, переносимый и интеллектуальный

Нейтральность к архитектуре

Достигается прежде всего стандартизацией "бинарного формата кодов". Промежуточный код не зависит от конкретной аппаратной платформы, операционной системы и типа оконного интерфейса. Для того, чтобы программы, написанные на Java, могли работать на данной аппаратно-программной платформе, достаточно, чтобы для нее была создана лишь соответствующая виртуальная машина.
Байт-коды
Компилятор Java производит не "машинные коды" подобно тому, как это делает, например, компилятор языка С. Вместо этого генерируются так называемые байт-коды: высокоуровневые машиннонезависимые коды для абстрактной машины, которая должна быть реализована в виде интерпретатора Java и run-time системы.
Сама по себе идея байт-кодов не нова, они широко используются в различных системах начиная с середины семидесятых годов. Особенности Java байт-машины следующие:
 

  • набор ее кодов легко не только интерпретировать, но и эффективно скомпилировать "на лету" непосредственно в машинные коды для любой современной аппаратной платформы
  • коды содержат избыточную информацию, которая позволяет проверить их на безопасность выполнения

Переносимость на другие архитектуры

Кроме независимости кодов от конкретной архитектуры Java жестко специфицирует формат базовых типов данных. Без этого одна и та же программа, скомпилированная для разных аппаратных платформ, вела бы себя по-разному. Например, стандарт С/С++ не предусматривает конкретного представления для целого типа int. Предполагается, что этому типу соответствует основной формат машинного слова для данной архитектуры. В результате программа, написанная для 32-разрядного процессора, чаще всего переносится на 16-разрядную архитектуру с очень большими усилиями.
Таким образом, решение зафиксировать форматы базовых типов данных в Java вполне естественно. Каждая Java-машина обязана реализовать их следующим образом:
 

byte		8-bit two's complement

short		16-bit two's complement

int		32-bit two's complement

long		64-bit two's complement

float		32-bit IEEE 754 floating point

double		64-bit IEEE 754 floating point

char		16-bit Unicode character

Выбор именно такого набора базовых типов и их формата обусловлен тем, что практически любой современный центральный процессор поддерживает эти форматы.
Более того, перенос самой среды может быть осуществлен достаточно просто. Компилятор Java сам написан на этом языке. Виртуальная машина написана на ANSI C в соответствии со стандартом POSIX. Спецификация языка не содержит ссылок типа "в зависимости от конкретной реализации".
 

Интеллектуальность

Система Java предназначена для создания программного обеспечения, которое должно быть интеллектуальным, предельно надежным и безопасным по множеству параметров. Особое внимание уделяется как ранней диагностике возможных проблем, так и поздней, во время выполнения кодов.
Жесткая проверка на этапе компиляции и во время выполнения
Компиляция с языка Java предусматривает жесткую проверку исходных текстов, множество ошибок может быть выявлено уже на этом этапе.
Одним из преимуществ языка С++ как строго типизованного языка является возможность раннего выявления некоторых категорий ошибок. Однако во многом этот язык наследует свойства С, позволяя нарушать требования строгого объявления функций и методов. Язык Java требует явного объявления прототипов и не поддерживает характерных для С неявных преобразований.
Значительное число проверок, производимых компилятором, повторяются виртуальной машиной непосредственно перед выполнением приложения. Линкер получает всю информацию о прототипах методов и на основе ее производит такую же проверку, как и компилятор, позволяя избежать расхождений в версиях между отдельными модулями.
Наиболее существенное отличие языка Java от С или С++ заключается в том, что архитектура Java не позволяет случайно или намеренно повредить память программы. Вместо арифметики указателей Java использует полноценные объекты для массивов и строк, что позволяет контролировать индексы доступа к ним во время выполнения. Кроме того, невозможны превращения между целыми числами и указателями.
Естественно, что все это не может полностью гарантировать программиста от любых ошибок, однако, Java устраняет целый класс их, существенно облегчая задачу разработчика.
 

 

Интерпретируемый и динамический

Для разработчика, использующего в своей работе обычные компилируемые языки, цикл разработки обычно выглядит следующим образом: редактировать текст -- скомпилировать его -- собрать программу линкером -- загрузить -- довести ее до "падения" -- рассмотреть обломки -- начать все с начала.
Кроме того, приходится постоянно следить за тем, какие из исходных текстов подлежат перекомпиляции. Для этого обычно используются дополнительные инструменты (например, популярная утилита make), часто не связанные с конкретным языком программирования и использующие крайне консервативный подход -- перекомпилировано должно быть все, что теоретически могло быть затронуто изменением. По мере того, как исходные тексты приложения разрастаются до сотен тысяч строк, взаимозависимости связывают части проекта крепче и крепче, скорость разработки приближается к нулю.
Система Java в силу своей интерпретируемой и динамической природы значительно более подходит для целей быстрой разработки надежных программ.
Как уже было отмечено выше, на выходе компилятора Java мы получаем байт-коды для Виртуальной Машины Java. Полная спецификация виртуальной машины открыта и общедоступна. Она может быть реализована практически на любой из современных программно-аппаратных платформ. После этого программы на языке Java могут быть собраны из любых мест в сети и работать на этой платформе так же, как и на любой другой.
Процесс сборки программы (linking) существенно ускорен по сравнению с обычными компилируемыми системами. Он представляет собой подгрузку необходимых классов и производится инкрементально, т.е. недостающие части подгружаются по мере надобности, что также приводит к сокращению времени цикла разработки.
 

Динамическая загрузка и связывание

То, что Java является интерпретатором, позволяет расширять систему динамически. Отдельные классы загружаются лишь по мере необходимости и могут быть собраны из различных мест в сети. Перед запуском на выполнение коды проходят жесткую проверку.
В настоящее время объектно-ориентированный подход стал общепринятым. В качестве языка программирования при этом обычно выбирают С++. Однако этот язык обладает определенным недостатком, который известен под названием "проблемы хрупкости базового класса" (fragile superclass problem).
"Проблема хрупкости базового класса" в С++
Эта проблема возникает как побочный эффект реализации модели С++. Каждый раз, когда Вы добавляете новый метод или переменную в класс, все остальные модули приложения, использующие этот класс, требуют перекомпиляции. В противном случае программа успешно собирается, а при запуске так же успешно разваливается. Даже при использовании специальных утилит типа make неточное отслеживание взаимозависимостей между классами является неиссякающим источником ошибок. Эта проблема "хрупкости базового класса" также часто именуется как проблема "постоянной перекомпиляции". Избежать ее можно путем разнообразных уловок, обычно связанных с отказом от прямого использования объектно-ориентированных свойств языка.
Решение "проблемы хрупкости базового класса"
В системе программирования Java эта проблема решается в несколько этапов. Во-первых, компилятор не разрешает ссылок вплоть до численных значений. Напротив, символьная информация передается вместе с байт-кодами для проверки и интерпретации. Окончательное связывание имен производится интерпретатором в момент загрузки класса. После этого ссылки уже "прописаны", как непосредственные указатели, и интерпретатор может работать с нормальной скоростью.
Во вторых то, как объект должен выглядеть в памяти машины, определяется не компилятором, а самим интерпретатором. Добавление в класс новых переменных или методов не требует изменений в остальных кодах.
Понятие интерфейса в языке Java
Под интерфейсом (interface) в Java понимается спецификация дополнительного набора методов, которые "обязан знать" объект. Идея заимствована из языка Objective C, где такая спецификация называется протоколом (protocol). Интерфейс не включает в себя модифицируемых переменных или выполняемых кодов. Класс может реализовать любое количество интерфейсов, без всех трудностей организации иерархии классов при множественном наследовании в С++.
Представление в исполняемом модуле
Классы в Java реально представлены в работающей системе. Существует выделенный класс по имени Class, экземпляры которого создаются виртуальной машиной и содержат информацию о всех классах в системе. Для любого объекта возможно найти соответствующий ему объект, представляющий его класс. Класс может сообщить свое имя и ссылку на своего непосредственного предшественника в иерархии. Возможен также поиск классов по имени.
 

Итоги

Интерпретируемая и динамическая природа языка Java предоставляет разработчику определенные преимущества:
 

  • интерпретирующее окружение позволяет быстрое создание прототипов без обычного цикла перекомпиляции и сборки
  • среда динамически расширяема, т.к. классы подгружаются на лету по мере необходимости
  • характерная для С++ проблема "хрупкого базового класса" решена в силу того, что расположение элементов объекта в памяти определяется не на этапе компиляции, а на этапе выполнения

 

Безопасность в Java

По мере стремительного роста использования глобальных сетей в спектре услуг, простирающемся от электронного распространения программного обеспечения и объектов multimedia до электронных платежей, безопасность становится ключевой проблемой. Мы коснемся того, как компилятор Java и run-time предотвращают создание и проникновение "диверсионных" кодов.
Компилятор и run-time включают в себя несколько уровней обороны против потенциально опасных программ. В общем случае система исходит из предположения, что доверять нельзя никому. Следующие несколько секций касаются проблемы более детально.
 

Резервирование и распределение памяти

Во-первых, решение о распределении памяти принимает не компилятор, а run-time система. Оно может зависеть от особенностей архитектуры конкретной системы.
Во-вторых, язык не поддерживает указателей. Символические ссылки на объекты разрешаются интерпретатором на этапе выполнения. Выделение памяти и работа со ссылками находятся полностью под управлением системы и не доступны непосредственно из программы.
Отложенное до последнего момента размещение структур в памяти не позволяет определить реальное положение полей класса по его описанию.
 

Процесс проверки байт-кодов

Несмотря на то, что компилятор гарантирует, что коды не нарушают требований безопасности, если они были получены из других точек сети возникает следующая проблема: коды могут быть созданы не компилятором Java, а другими средствами. Или они могут быть намеренно модифицированы после создания. Поэтому run-time система подвергает полученные коды тщательной проверке.
Проверка включает в себя несколько этапов, начиная с контроля целостности формата полученного файла до анализа каждого фрагмента кодов на предмет выполнения следующих правил:
 

  • нет незаконных манипуляций с указателями
  • нет попыток нарушения прав доступа
  • объекты используются в строгом соответствии с их типами, например, объекты класса InputStream используются только как InputStream и никак иначе.

Верификатор байт-кодов
Верификатор байт-кодов (bytecode verifier) сканирует байт-коды, извлекает информацию о типах объектов в каждой точке выполнения фрагмента кода.
На приведенном рисунке изображен путь от исходных текстов на языке Java через компилятор Java, верификатор кодов до интерпретатора. Важно отметить, что загрузчик и верификатор байт-кодов не делают никаких предположений относительно происхождения кодов -- получены они с локальной файловой системы или с другого континента. Верификатор гарантирует, что любой код, прошедший проверку, может быть использован интерпретатором без риска повредить его (интерпретатор), а именно:
 

  • не может произойти переполнение или "исчерпание" стека
  • параметры для инструкций байт-машины имеют нужный тип
  • доступ к полям и методам объектов не нарушает объявленных в классе правил (public, private, protected)

Правила безопасности при загрузке

В ходе выполнения программы может потребоваться загрузка дополнительных классов. После того как, полученный код прошел проверку на валидность байт-кодов, он поступает в загрузчик кодов. Для загрузчика все пространство имен загружаемых классов может быть подразделено на отдельные области (name spaces). Причем классы, полученные локально (заслуживающие безусловного доверия), и классы, присланные по сети из остального мира (и потенциально враждебные), находятся в разных пространствах имен.
При разрешении ссылки на какой-либо класс он ищется прежде всего в локальном пространстве. Это не позволяет "внешним" кодам подменить один из базовых классов в системе.
 

Безопасность в сетевом пакете

Сетевой пакет Java включает в себя поддержку различных сетевых протоколов (FTP, HTTP, Telnet и т.д.). Это -- передовая линия защиты от вторжения по сети.
Осторожность при установке прав сетевого доступа в локальную систему может быть доведена до параноидальной. Вы можете
 

  • запретить сетевой доступ вообще
  • разрешить доступ только к тому узлу, с которого был получен код
  • разрешить доступ только к узлам за пределами firewall, если код также получен с той стороны firewall
  • разрешить любой сетевой доступ

Итоги

Система Java достаточно безопасна, чтобы жить в сетевом окружении. Нейтральность к архитектуре и переносимость делают ее достаточно привлекательной для создания распределенных по сети приложений.
 

 

Многопотоковость в Java

Современного пользователя компьютера все чаще раздражает ситуация, когда программа способна выполнять в один момент времени лишь одну задачу. Реальный мир наполнен событиями, происходящими одновременно и независимо. Пользователь требует от компьютера адекватной реакции.
К сожалению, написание программ, отвечающих этим требованиям, значительно сложнее, чем написание программ, выполняющихся последовательно. Они могут быть созданы с использованием С или С++, однако делать это сложнее, т.к. отсутствует поддержка в самом языке, а также большинство существующих на сегодняшний день внешних библиотек часто не могут быть использованы в таких программах в силу того, что они не удовлетворяют так называемому thread-safe условию.
Термин thread-safe означает, что каждая функция данной библиотеки может быть использована одновременно несколькими потоками.
Основная проблема при прямом управлении потоками состоит в том, что Вы никогда не можете быть полностью уверены, что поставили все нужные замки (locks) и вовремя их освободили. При преждевременном завершении процедуры или при возникновении исключительной ситуации замок может остаться неснятым, что обычно приводит к блокировке программы (deadlock).
Встроенная многопотоковость -- существенная черта архитектуры Java. Стандартная библиотека включает в себя класс Thread, с методами, позволяющими стартовать новый поток, завершить его работу и проверить текущее состояние потока.
Интеграция примитивов синхронизации непосредственно в язык упрощает работу с ними.
Потоки в Java вытесняющие (pre-emptive), а также могут выполняться в режиме разделения времени (time-sliced), но только на платформах, которые поддерживают это. В системах, в которых такая поддержка отсутствует, после того, как поток был запущен, он может быть прерван только другим потоком с более высоким приоритетом. Если ваше приложение требует больших периодов вычислений, рекомендуется явно отдавать управление другим потокам (вызовом Thread.yield()).
 

Интегрированная синхронизация потоков

Система Java содержит поддержку многопотоковости как на уровне синтаксиса языка, так и на уровне библиотек и системных вызовов.
На уровне самого языка методы, объявленные с признаком synchronized, гарантировано не будут выполняться одновременно для данного объекта. Методы запускаются под управлением монитора (monitor). Каждый класс и объект имеют свой собственный монитор. Если объект находится в состоянии выполнения одного из синхронизованных методов, попытка вызвать этот метод или любой другой синхронизованный метод для этого же объекта из другого потока приостанавливается до того момента, когда выполнение метода каким-то образом завершится (обычным образом или в результате возбуждения исключительной ситуации).

1 \ 2 \ 3 

 



Сайт управляется системой uCoz